From 4440e4fa5dfd64046b5584497165948774076229 Mon Sep 17 00:00:00 2001 From: firstmate crewmate Date: Thu, 6 Aug 2026 10:05:43 +1000 Subject: [PATCH] ci: gate PyPI publish on full CI + approved environment Extract the publish steps into a reusable workflow_call workflow (release.yml) so a tag push reruns the exact lint/type/test/build+twine gate on the release SHA before publishing, and publish now waits on the pypi environment's required-reviewer approval instead of a bare tag push being the trust boundary. python-publish.yml becomes a thin caller scoped to vX.Y.Z tags, and release.yml is parameterized so sibling repos can call it directly instead of copying the job. --- .github/workflows/python-publish.yml | 87 ++++-------------------- .github/workflows/release.yml | 99 ++++++++++++++++++++++++++++ AGENTS.md | 2 +- 3 files changed, 111 insertions(+), 77 deletions(-) create mode 100644 .github/workflows/release.yml diff --git a/.github/workflows/python-publish.yml b/.github/workflows/python-publish.yml index f2be779..c52e34f 100644 --- a/.github/workflows/python-publish.yml +++ b/.github/workflows/python-publish.yml @@ -1,82 +1,17 @@ name: Publish Python 🐍 distribution 📦 to PyPI -on: push +on: + push: + tags: + - "v*.*.*" jobs: - build: - name: Build distribution 📦 - runs-on: ubuntu-latest - - steps: - - uses: actions/checkout@v4 - - name: Set up Python - uses: actions/setup-python@v4 - with: - python-version: "3.x" - - name: Install pypa/build - run: >- - python3 -m - pip install - build - --user - - name: Build a binary wheel and a source tarball - run: python3 -m build - - name: Store the distribution packages - uses: actions/upload-artifact@v4 - with: - name: python-package-distributions - path: dist/ - - publish-to-pypi: - name: >- - Publish Python 🐍 distribution 📦 to PyPI - if: startsWith(github.ref, 'refs/tags/') # only publish to PyPI on tag pushes - needs: - - build - runs-on: ubuntu-latest - environment: - name: pypi - url: https://pypi.org/p/tesla_fleet_api + release: + name: Release + uses: ./.github/workflows/release.yml + with: + package-dir: tesla_fleet_api + pypi-project-url: https://pypi.org/p/tesla_fleet_api permissions: id-token: write - - steps: - - name: Download all the dists - uses: actions/download-artifact@v4 - with: - name: python-package-distributions - path: dist/ - - name: Publish distribution 📦 to PyPI - uses: pypa/gh-action-pypi-publish@release/v1 - - github-release: - name: >- - Upload distribution to GitHub Release - needs: - - publish-to-pypi - runs-on: ubuntu-latest - - permissions: - contents: write # IMPORTANT: mandatory for making GitHub Releases - - steps: - - name: Download all the dists - uses: actions/download-artifact@v4 - with: - name: python-package-distributions - path: dist/ - - name: Create GitHub Release - env: - GITHUB_TOKEN: ${{ github.token }} - run: >- - gh release create - '${{ github.ref_name }}' - --repo '${{ github.repository }}' - --generate-notes - - name: Upload artifacts to GitHub Release - env: - GITHUB_TOKEN: ${{ github.token }} - run: >- - gh release upload - '${{ github.ref_name }}' dist/** - --repo '${{ github.repository }}' + contents: write diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..d503240 --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,99 @@ +name: Release gate & publish + +on: + workflow_call: + inputs: + package-dir: + description: Import package directory to lint/type-check (passed to ruff/pyright) + required: true + type: string + python-version: + description: Python version to build/test/publish with + required: false + type: string + default: "3.13" + pypi-project-url: + description: PyPI project URL shown on the environment deployment + required: true + type: string + environment-name: + description: Protected GitHub environment gating the publish step + required: false + type: string + default: pypi + +jobs: + gate: + name: Full CI gate + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Install uv + uses: astral-sh/setup-uv@v5 + with: + enable-cache: true + - name: Set up Python ${{ inputs.python-version }} + run: uv python install ${{ inputs.python-version }} + - name: Install dependencies + run: uv sync --python ${{ inputs.python-version }} + - name: Ruff + run: uv run ruff check ${{ inputs.package-dir }} tests + - name: Pyright + run: uv run pyright ${{ inputs.package-dir }} + - name: Pytest + run: uv run pytest tests -q + - name: Build + run: uv build + - name: Check distribution + run: uvx twine check dist/* + - name: Upload artifacts + uses: actions/upload-artifact@v4 + with: + name: python-package-distributions + path: dist/ + + publish-to-pypi: + name: Publish distribution to PyPI + needs: gate + runs-on: ubuntu-latest + environment: + name: ${{ inputs.environment-name }} + url: ${{ inputs.pypi-project-url }} + permissions: + id-token: write + steps: + - name: Download all the dists + uses: actions/download-artifact@v4 + with: + name: python-package-distributions + path: dist/ + - name: Publish distribution to PyPI + uses: pypa/gh-action-pypi-publish@release/v1 + + github-release: + name: Upload distribution to GitHub Release + needs: publish-to-pypi + runs-on: ubuntu-latest + permissions: + contents: write # mandatory for making GitHub Releases + steps: + - name: Download all the dists + uses: actions/download-artifact@v4 + with: + name: python-package-distributions + path: dist/ + - name: Create GitHub Release + env: + GITHUB_TOKEN: ${{ github.token }} + run: >- + gh release create + '${{ github.ref_name }}' + --repo '${{ github.repository }}' + --generate-notes + - name: Upload artifacts to GitHub Release + env: + GITHUB_TOKEN: ${{ github.token }} + run: >- + gh release upload + '${{ github.ref_name }}' dist/** + --repo '${{ github.repository }}' diff --git a/AGENTS.md b/AGENTS.md index bc4f1fd..54aa978 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -93,7 +93,7 @@ Scope flags on `TeslaFleetApi.__init__` control which submodules are instantiate ### Release Process -No release-please or version-bump automation. To ship: bump `version` in `pyproject.toml` and `__version__` in `tesla_fleet_api/__init__.py` in a `Bump version to X.Y.Z` commit on `main`, then push a matching `vX.Y.Z` tag. `.github/workflows/python-publish.yml` runs on every push but only builds+publishes to PyPI (and creates a GitHub Release) when `github.ref` starts with `refs/tags/` — pushing the tag is what actually ships the release; merging to `main` alone does not. +No release-please or version-bump automation. To ship: bump `version` in `pyproject.toml` and `__version__` in `tesla_fleet_api/__init__.py` in a `Bump version to X.Y.Z` commit on `main`, then push a matching `vX.Y.Z` tag. `.github/workflows/python-publish.yml` only triggers on that tag push and calls the reusable `.github/workflows/release.yml` workflow, which reruns the full CI gate (ruff, pyright, pytest, `uv build` + `twine check`) on the exact tagged commit, then requires approval on the `pypi` GitHub environment (required reviewers configured via the Environments API - there's no repo Settings UI for it) before publishing via the existing PyPA OIDC trusted-publishing action and cutting the GitHub Release. `release.yml` is written as a `workflow_call` reusable workflow (parameterized by `package-dir`/`pypi-project-url`) specifically so sibling repos can call it with `uses: Teslemetry/python-tesla-fleet-api/.github/workflows/release.yml@main` instead of copying the job. ### Error Handling