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
89 changes: 89 additions & 0 deletions .github/workflows/publish.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
name: Publish to PyPI

# Publishes to PyPI via OIDC trusted publishing whenever a v* tag is pushed.
# No API tokens required — PyPI verifies the GitHub OIDC identity instead.
#
# git tag v0.7.16 # must match the version in pyproject.toml
# git push origin v0.7.16

on:
push:
tags:
- "v*"

jobs:
build:
name: Build distribution
runs-on: ubuntu-latest
permissions:
contents: read # for checkout; upload-artifact uses the Actions runtime token, not GITHUB_TOKEN
steps:
- uses: actions/checkout@v4
with:
persist-credentials: false # don't leave GITHUB_TOKEN in git config; build runs third-party code
# Submodule (spec/) is not needed — cloudglue/sdk is already committed.
Comment thread
coderabbitai[bot] marked this conversation as resolved.

- uses: actions/setup-python@v5
with:
python-version: "3.12"

- name: Install build tooling
run: python -m pip install --upgrade build

- name: Verify tag matches pyproject version
run: |
PKG_VERSION="$(python -c 'import tomllib; print(tomllib.load(open("pyproject.toml","rb"))["project"]["version"])')"
TAG_VERSION="${GITHUB_REF_NAME#v}"
if [ "$PKG_VERSION" != "$TAG_VERSION" ]; then
echo "::error::Tag $GITHUB_REF_NAME (version $TAG_VERSION) does not match pyproject version $PKG_VERSION"
exit 1
fi
echo "Building and publishing version $PKG_VERSION"

- name: Build sdist and wheel
run: python -m build

- name: Check artifacts
run: |
python -m pip install --upgrade twine
python -m twine check dist/*

- uses: actions/upload-artifact@v4
with:
name: dist
path: dist/
Comment thread
cursor[bot] marked this conversation as resolved.

publish:
name: Publish to PyPI
needs: build
runs-on: ubuntu-latest
environment:
name: pypi
url: https://pypi.org/p/cloudglue
permissions:
id-token: write # required for OIDC trusted publishing
steps:
- uses: actions/download-artifact@v4
with:
name: dist
path: dist/
Comment thread
cursor[bot] marked this conversation as resolved.

- uses: pypa/gh-action-pypi-publish@release/v1

github-release:
name: Create GitHub Release
needs: publish # only release after a successful PyPI publish
Comment thread
cursor[bot] marked this conversation as resolved.
runs-on: ubuntu-latest
permissions:
contents: write # required to create the release and upload assets
steps:
- uses: actions/download-artifact@v4
with:
name: dist
path: dist/

- name: Create release with sdist + wheel attached
uses: softprops/action-gh-release@v2
with:
files: dist/*
generate_release_notes: true
46 changes: 46 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,52 @@ make generate # Generate SDK from OpenAPI spec
make build # Build the package
```

### Releasing

Releases are published to PyPI automatically by the
[`Publish to PyPI`](.github/workflows/publish.yml) GitHub Actions workflow
whenever a `v*` tag is pushed. It uses
[OIDC trusted publishing](https://docs.pypi.org/trusted-publishers/) — no API
tokens are stored anywhere.

To cut a release:

```bash
# 1. Bump the version in pyproject.toml (e.g. 0.7.15 -> 0.7.16) and commit it.
# 2. Tag the commit. The tag MUST match the pyproject.toml version, prefixed
# with "v" — the workflow fails fast if they differ.
git tag v0.7.16
git push origin v0.7.16
```

Pushing the tag triggers, in order:

1. **build** — verifies the tag matches `pyproject.toml`, builds the sdist +
wheel, runs `twine check`.
2. **publish** — uploads the artifacts to PyPI via OIDC.
3. **github-release** — creates a [GitHub Release](https://github.com/cloudglue/cloudglue-python/releases)
for the tag with auto-generated notes and the `.tar.gz` + `.whl` attached.

#### One-time setup

This is already configured for the `cloudglue` project, but for reference the
workflow depends on:

- A [PyPI trusted publisher](https://pypi.org/manage/project/cloudglue/settings/publishing/)
for repo `cloudglue/cloudglue-python`, workflow `publish.yml`, environment `pypi`.
- A GitHub Environment named `pypi` (repo **Settings → Environments**).

#### Manual publishing (fallback)

The legacy token-based path still works if you need to publish outside CI. It
requires a [PyPI API token](https://pypi.org/manage/account/token/) scoped to the
`cloudglue` project, in `~/.pypirc` or via env vars:

```bash
make build
TWINE_USERNAME=__token__ TWINE_PASSWORD='pypi-...' make publish
```

### Project Structure

Project directory structure described below:
Expand Down