diff --git a/.github/check-changelog.sh b/.github/check-changelog.sh new file mode 100755 index 00000000..7e9e5dd3 --- /dev/null +++ b/.github/check-changelog.sh @@ -0,0 +1,10 @@ +#!/usr/bin/env bash +set -euo pipefail + +FRAGMENTS=$(find changelog.d -type f ! -name '.gitkeep' | wc -l) +if [ "$FRAGMENTS" -eq 0 ]; then + echo "::error::No changelog fragment found in changelog.d/" + echo "Add one with: echo 'Description.' > changelog.d/\$(git branch --show-current)..md" + echo "Types: added, changed, fixed, removed, breaking" + exit 1 +fi diff --git a/.github/workflows/code_changes.yaml b/.github/workflows/code_changes.yaml deleted file mode 100644 index cd64a7ef..00000000 --- a/.github/workflows/code_changes.yaml +++ /dev/null @@ -1,60 +0,0 @@ -# Workflow that runs on code changes to the master branch. - -name: Code changes -on: - push: - branches: - - main - - paths: - - src/** - - tests/** - - .github/** - workflow_dispatch: - -jobs: - Lint: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - # Check formatting with ruff - - name: Install ruff - run: pip install ruff - - - name: Run ruff format check - run: ruff format --check . - - - name: Run ruff check - run: ruff check . - - - name: Run ruff format check - run: ruff format --check . - Test: - runs-on: macos-latest - permissions: - contents: "read" - id-token: "write" - strategy: - fail-fast: false - matrix: - python-version: ['3.13', '3.14'] - steps: - - name: Checkout repo - uses: actions/checkout@v4 - - name: Install uv - uses: astral-sh/setup-uv@v5 - - - name: Set up Python - uses: actions/setup-python@v5 - with: - python-version: ${{ matrix.python-version }} - allow-prereleases: true - - - name: Install package - run: uv pip install -e .[dev] --system - - name: Install JB - run: uv pip install "jupyter-book>=2.0.0a0" --system - - name: Run tests with coverage - run: make test - env: - HUGGING_FACE_TOKEN: ${{ secrets.HUGGING_FACE_TOKEN }} diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml deleted file mode 100644 index 0cdb6d39..00000000 --- a/.github/workflows/docs.yml +++ /dev/null @@ -1,49 +0,0 @@ -# This file was created automatically with `myst init --gh-pages` 🪄 💚 -# Ensure your GitHub Pages settings for this repository are set to deploy with **GitHub Actions**. - -name: Deploy documentation -on: - push: - # Runs on pushes targeting the default branch - branches: [main] -env: - # `BASE_URL` determines, relative to the root of the domain, the URL that your site is served from. - # E.g., if your site lives at `https://mydomain.org/myproject`, set `BASE_URL=/myproject`. - # If, instead, your site lives at the root of the domain, at `https://mydomain.org`, set `BASE_URL=''`. - BASE_URL: /${{ github.event.repository.name }} - -# Sets permissions of the GITHUB_TOKEN to allow deployment to GitHub Pages -permissions: - contents: read - pages: write - id-token: write -# Allow only one concurrent deployment, skipping runs queued between the run in-progress and latest queued. -# However, do NOT cancel in-progress runs as we want to allow these production deployments to complete. -concurrency: - group: 'pages' - cancel-in-progress: false -jobs: - deploy: - name: Deploy to GitHub Pages - environment: - name: github-pages - url: ${{ steps.deployment.outputs.page_url }} - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - name: Setup Pages - uses: actions/configure-pages@v3 - - uses: actions/setup-node@v4 - with: - node-version: 18.x - - name: Install MyST - run: npm install -g mystmd - - name: Build HTML Assets - run: cd docs && myst build --html - - name: Upload artifact - uses: actions/upload-pages-artifact@v3 - with: - path: './docs/_build/html' - - name: Deploy to GitHub Pages - id: deployment - uses: actions/deploy-pages@v4 diff --git a/.github/workflows/pr_code_changes.yaml b/.github/workflows/pr_code_changes.yaml index a2cc1192..bdffb04a 100644 --- a/.github/workflows/pr_code_changes.yaml +++ b/.github/workflows/pr_code_changes.yaml @@ -7,14 +7,21 @@ on: - src/** - tests/** - .github/** + - changelog.d/** workflow_dispatch: jobs: + check-changelog: + name: Check changelog fragment + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Check for changelog fragment + run: .github/check-changelog.sh Lint: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - # Check formatting with ruff - name: Install ruff run: pip install ruff @@ -62,4 +69,4 @@ jobs: - name: Run tests with coverage run: make test env: - HUGGING_FACE_TOKEN: ${{ secrets.HUGGING_FACE_TOKEN }} \ No newline at end of file + HUGGING_FACE_TOKEN: ${{ secrets.HUGGING_FACE_TOKEN }} diff --git a/.github/workflows/push.yaml b/.github/workflows/push.yaml new file mode 100644 index 00000000..c96bb4fd --- /dev/null +++ b/.github/workflows/push.yaml @@ -0,0 +1,151 @@ +# Unified workflow for pushes to main. +# +# Phase 1 (normal push): Lint + Test → Docs + Versioning +# Versioning commits with message "Update package version", triggering Phase 2. +# +# Phase 2 (sentinel commit): Lint + Test → Publish to PyPI + GitHub Release + +name: Push to main + +on: + push: + branches: + - main + workflow_dispatch: + +permissions: + contents: write + pages: write + id-token: write + +jobs: + # ── Shared gates (always run) ───────────────────────────── + Lint: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Install ruff + run: pip install ruff + - name: Run ruff format check + run: ruff format --check . + - name: Run ruff check + run: ruff check . + + Test: + runs-on: macos-latest + strategy: + fail-fast: false + matrix: + python-version: ['3.13', '3.14'] + steps: + - name: Checkout repo + uses: actions/checkout@v4 + - name: Install uv + uses: astral-sh/setup-uv@v5 + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: ${{ matrix.python-version }} + allow-prereleases: true + - name: Install package + run: uv pip install -e .[dev] --system + - name: Install policyengine + run: uv pip install policyengine --system + - name: Run tests with coverage + run: make test + env: + HUGGING_FACE_TOKEN: ${{ secrets.HUGGING_FACE_TOKEN }} + + # ── Phase 1: Docs + Versioning (skip on sentinel commit) ── + Docs: + name: Deploy documentation + runs-on: ubuntu-latest + needs: [Lint, Test] + if: github.event.head_commit.message != 'Update package version' + concurrency: + group: 'pages' + cancel-in-progress: false + environment: + name: github-pages + url: ${{ steps.deployment.outputs.page_url }} + env: + BASE_URL: /${{ github.event.repository.name }} + steps: + - uses: actions/checkout@v4 + - name: Setup Pages + uses: actions/configure-pages@v3 + - uses: actions/setup-node@v4 + with: + node-version: 18.x + - name: Install MyST + run: npm install -g mystmd + - name: Build HTML Assets + run: cd docs && myst build --html + - name: Upload artifact + uses: actions/upload-pages-artifact@v3 + with: + path: './docs/_build/html' + - name: Deploy to GitHub Pages + id: deployment + uses: actions/deploy-pages@v4 + + Versioning: + runs-on: ubuntu-latest + needs: [Lint, Test] + if: github.event.head_commit.message != 'Update package version' + steps: + - name: Checkout repo + uses: actions/checkout@v4 + with: + token: ${{ secrets.POLICYENGINE_GITHUB }} + - name: Setup Python + uses: actions/setup-python@v5 + with: + python-version: '3.13' + - name: Build changelog + run: pip install yaml-changelog towncrier && make changelog + - name: Preview changelog update + run: ".github/get-changelog-diff.sh" + - name: Update changelog + uses: EndBug/add-and-commit@v9 + with: + add: "." + message: Update package version + + # ── Phase 2: Publish (only on sentinel commit) ──────────── + Publish: + runs-on: ubuntu-latest + needs: [Lint, Test] + if: github.event.head_commit.message == 'Update package version' + env: + GH_TOKEN: ${{ secrets.POLICYENGINE_GITHUB }} + steps: + - name: Checkout repo + uses: actions/checkout@v4 + - name: Install uv + uses: astral-sh/setup-uv@v5 + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: '3.13' + - name: Install package + run: uv pip install -e .[dev] --system + - name: Install policyengine + run: uv pip install policyengine --system + - name: Publish a git tag + run: ".github/publish-git-tag.sh" + - name: Build package + run: python -m build + - name: Publish a Python distribution to PyPI + uses: pypa/gh-action-pypi-publish@release/v1 + with: + user: __token__ + password: ${{ secrets.PYPI }} + skip_existing: true + - name: Create GitHub Release + run: | + VERSION=$(python .github/fetch_version.py) + gh release create "$VERSION" \ + --title "v$VERSION" \ + --notes "See [CHANGELOG.md](https://github.com/PolicyEngine/policyengine.py/blob/main/CHANGELOG.md) for details." \ + --latest diff --git a/.github/workflows/versioning.yaml b/.github/workflows/versioning.yaml deleted file mode 100644 index dcb70429..00000000 --- a/.github/workflows/versioning.yaml +++ /dev/null @@ -1,75 +0,0 @@ -# Workflow that runs on versioning metadata updates. - -name: Versioning updates -on: - push: - branches: - - main - - paths: - - changelog.d/** - - .github/** - workflow_dispatch: - -jobs: - Versioning: - if: (github.event.head_commit.message != 'Update package version') - runs-on: ubuntu-latest - steps: - - name: Checkout repo - uses: actions/checkout@v4 - with: - repository: ${{ github.event.pull_request.head.repo.full_name }} - ref: ${{ github.event.pull_request.head.ref }} - token: ${{ secrets.POLICYENGINE_GITHUB }} - - name: Setup Python - uses: actions/setup-python@v5 - with: - python-version: 3.13 - - name: Build changelog - run: pip install yaml-changelog towncrier && make changelog - - name: Preview changelog update - run: ".github/get-changelog-diff.sh" - - name: Update changelog - uses: EndBug/add-and-commit@v9 - with: - add: "." - message: Update package version - Publish: - runs-on: ubuntu-latest - if: (github.event.head_commit.message == 'Update package version') - env: - GH_TOKEN: ${{ secrets.POLICYENGINE_GITHUB }} - steps: - - name: Checkout repo - uses: actions/checkout@v4 - - name: Install uv - uses: astral-sh/setup-uv@v5 - - - name: Set up Python - uses: actions/setup-python@v5 - with: - python-version: '3.13' - - name: Install package - run: uv pip install -e .[dev] --system - - name: Install policyengine - run: uv pip install policyengine --system - - name: Install Wheel and Pytest - run: uv pip install wheel setuptools pytest==5.4.3 --system - - name: Publish a git tag - run: ".github/publish-git-tag.sh" - - name: Build package - run: make - - name: Publish a Python distribution to PyPI - uses: pypa/gh-action-pypi-publish@release/v1 - with: - user: __token__ - password: ${{ secrets.PYPI }} - skip_existing: true - - name: Create GitHub Release - run: | - VERSION=$(python .github/fetch_version.py) - gh release create "$VERSION" \ - --title "v$VERSION" \ - --notes "See [CHANGELOG.md](https://github.com/PolicyEngine/policyengine.py/blob/main/CHANGELOG.md) for details." \ - --latest diff --git a/changelog.d/consolidate-ci-cd.changed.md b/changelog.d/consolidate-ci-cd.changed.md new file mode 100644 index 00000000..c2c08813 --- /dev/null +++ b/changelog.d/consolidate-ci-cd.changed.md @@ -0,0 +1 @@ +Consolidate CI/CD workflows into a unified push workflow with two-phase sentinel pattern, enforce changelog fragments on PRs