Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
83 changes: 83 additions & 0 deletions .github/workflows/cd.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
name: CD

on:
push:
tags:
- '*'

permissions:
contents: read

jobs:
validate-tag:
name: Validate release tag
runs-on: ubuntu-latest
outputs:
release_tag: ${{ steps.validate.outputs.release_tag }}
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: '3.12'
- name: Validate tag and package version
id: validate
run: |
python - <<'PY'
import os
import re
import tomllib
from pathlib import Path

tag = os.environ["GITHUB_REF_NAME"]
version = tomllib.loads(Path("pyproject.toml").read_text())["project"]["version"]
is_release = re.fullmatch(r"[0-9]+\.[0-9]+\.[0-9]+", tag) is not None
with open(os.environ["GITHUB_OUTPUT"], "a", encoding="utf-8") as output:
print(f"release_tag={str(is_release).lower()}", file=output)
if not is_release:
print(f"Tag {tag!r} is not a PyPI release tag; skipping publish.")
raise SystemExit(0)
if version != tag:
raise SystemExit(f"pyproject version {version!r} does not match tag {tag!r}")
print(f"Release version verified: {version}")
PY

build:
name: Build distribution
needs: validate-tag
if: needs.validate-tag.outputs.release_tag == 'true'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: '3.12'
cache: pip
- name: Install build tools
run: python -m pip install build twine
- name: Build package
run: python -m build
- name: Check distribution metadata
run: python -m twine check dist/*
- name: Upload distribution artifact
uses: actions/upload-artifact@v4
with:
name: python-distribution
path: dist/*
if-no-files-found: error

publish-pypi:
name: Publish to PyPI
needs: build
runs-on: ubuntu-latest
environment: pypi
permissions:
contents: read
id-token: write
steps:
- name: Download distribution artifact
uses: actions/download-artifact@v4
with:
name: python-distribution
path: dist
- name: Publish package distributions to PyPI
uses: pypa/gh-action-pypi-publish@release/v1
26 changes: 26 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,32 @@ To keep agents from falling back to local human credentials, update existing Git
- Avoid `@me` assumptions because the GitHub App bot is not the human operator.
- Require write summaries to include the returned `auth_mode`, `app_slug`, `installation_id`, repository, operation, and URL/path.

## Releasing to PyPI

The package is built with Hatchling and publishes through the `CD` GitHub Actions workflow using PyPI Trusted Publishing / OIDC. The workflow listens to all pushed tags but only builds and publishes when the tag matches:

```text
^[0-9]+\.[0-9]+\.[0-9]+$
```

The tag must also match `project.version` in `pyproject.toml`.

Before the first release, configure PyPI Trusted Publishing for this repository and workflow:

- PyPI project name: `hermes-github-app-plugin`
- Owner/repository: this GitHub repository
- Workflow name: `cd.yaml`
- Environment name: `pypi`

Release example:

```bash
git tag 0.1.0
git push origin 0.1.0
```

Tags like `v0.1.0`, `0.1`, or `0.1.0rc1` will not publish.

## Hermes tools

The plugin registers these tools:
Expand Down
Loading