From 7ebe39afe9bd6d2c7db2f8a6ecf6b4d1887c6cfd Mon Sep 17 00:00:00 2001 From: Nicholas Romero Date: Thu, 9 Jul 2026 11:32:38 -0500 Subject: [PATCH] Add release-please PyPI publishing --- .github/workflows/release-please.yml | 20 ++++++ .github/workflows/release.yml | 42 ++++++++++++ .release-please-manifest.json | 3 + docs/releasing.md | 97 ++++++++++++++++++++++++++++ pyproject.toml | 2 +- release-please-config.json | 20 ++++++ src/panopticon/__init__.py | 2 +- 7 files changed, 184 insertions(+), 2 deletions(-) create mode 100644 .github/workflows/release-please.yml create mode 100644 .github/workflows/release.yml create mode 100644 .release-please-manifest.json create mode 100644 docs/releasing.md create mode 100644 release-please-config.json diff --git a/.github/workflows/release-please.yml b/.github/workflows/release-please.yml new file mode 100644 index 00000000..b5546d68 --- /dev/null +++ b/.github/workflows/release-please.yml @@ -0,0 +1,20 @@ +name: Release Please + +on: + push: + branches: [main] + +permissions: + contents: write + issues: write + pull-requests: write + +jobs: + release-please: + runs-on: ubuntu-latest + steps: + - uses: googleapis/release-please-action@v4 + with: + token: ${{ secrets.RELEASE_PLEASE_TOKEN }} + config-file: release-please-config.json + manifest-file: .release-please-manifest.json diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 00000000..59720239 --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,42 @@ +name: Release + +on: + release: + types: [published] + +permissions: + contents: read + id-token: write + +jobs: + publish-pypi: + name: Publish PyPI package + runs-on: ubuntu-latest + environment: pypi + + steps: + - name: Checkout + uses: actions/checkout@v4 + with: + persist-credentials: false + + - name: Install uv + uses: astral-sh/setup-uv@v5 + with: + python-version: "3.13" + enable-cache: true + + - name: Sync dependencies + run: uv sync + + - name: Type-check + run: uv run mypy --package panopticon + + - name: Test + run: uv run pytest + + - name: Build package + run: uv build + + - name: Publish package to PyPI + uses: pypa/gh-action-pypi-publish@release/v1 diff --git a/.release-please-manifest.json b/.release-please-manifest.json new file mode 100644 index 00000000..b985ff6e --- /dev/null +++ b/.release-please-manifest.json @@ -0,0 +1,3 @@ +{ + ".": "0.0.1" +} diff --git a/docs/releasing.md b/docs/releasing.md new file mode 100644 index 00000000..55dfd6ad --- /dev/null +++ b/docs/releasing.md @@ -0,0 +1,97 @@ +# Releasing + +Release automation has two legs: + +1. `release-please` runs on pushes to `main`, opens or updates the release PR, and publishes the + GitHub Release when that PR merges. +2. The `Release` workflow runs when a GitHub Release is published, validates the package, builds + the source distribution and wheel, and publishes them to PyPI. + +## GitHub setup + +Create an organization secret named `RELEASE_PLEASE_TOKEN`. Use a fine-grained GitHub PAT or +GitHub App token that can write contents, issues, and pull requests for this repository. + +The secret is intentionally separate from the default `GITHUB_TOKEN`: release-please opens and +updates release PRs, and the follow-on workflows from those PRs need to behave like normal +repository events. + +Also create a GitHub environment named `pypi`. Keep it restricted to trusted maintainers; requiring +environment approval is a good final gate before an already-published GitHub Release can upload to +PyPI. + +## PyPI trusted publisher setup + +Use PyPI trusted publishing for the normal release path. This avoids storing a long-lived PyPI token +in GitHub and lets the `Release` workflow mint a short-lived upload token through GitHub OIDC. + +If the `panopticon` PyPI project already exists, add a trusted publisher from the project's +`Manage` -> `Publishing` page. If it does not exist yet, add a pending publisher from the PyPI +account or organization `Publishing` page; the first successful trusted publish creates the project. + +Use these exact publisher values: + +```text +PyPI project/package: panopticon +Owner: Unsupervisedcom +Repository: panopticon +Workflow filename: release.yml +Environment name: pypi +``` + +The workflow side is already configured in `.github/workflows/release.yml`: + +```yaml +permissions: + contents: read + id-token: write + +jobs: + publish-pypi: + environment: pypi +``` + +and the publish step uses `pypa/gh-action-pypi-publish@release/v1` without a username, password, or +PyPI API token. + +## First release checklist + +1. Confirm the `RELEASE_PLEASE_TOKEN` organization secret is visible to this repository. +2. Confirm the `pypi` GitHub environment exists and has the intended reviewers. +3. Confirm the PyPI trusted publisher, or pending trusted publisher, matches the values above. +4. Merge the release-please PR only after it bumps `pyproject.toml`, + `src/panopticon/__init__.py`, and `.release-please-manifest.json` together. +5. Approve the `pypi` GitHub environment deployment when the `Release` workflow runs from the + published GitHub Release. + +## Local first publish fallback + +Prefer the pending trusted publisher path for the first publish. It proves the same automation that +will run every future release, and it does not require a PyPI token. + +Only publish locally if the trusted-publisher first publish is blocked and an operator deliberately +chooses to create the PyPI project by hand. For a brand-new project, use a short-lived +account-scoped PyPI API token because a project-scoped token cannot exist before the project exists. +Delete or rotate that token immediately after the upload, then configure the normal trusted +publisher. + +Build and inspect the distributions: + +```sh +uv build +ls -lh dist/ +``` + +Upload to PyPI with Twine: + +```sh +uvx twine upload dist/* +``` + +When prompted, use `__token__` as the username and paste the PyPI API token as the password. After +the upload, verify installation from PyPI: + +```sh +python3 -m pip install --upgrade panopticon +panopticon --help +``` diff --git a/pyproject.toml b/pyproject.toml index 58da40a2..52ff4c10 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "panopticon" -version = "0.0.1" +version = "0.0.1" # x-release-please-version description = "Orchestrate multiple coding agents across isolated tasks and configurable workflows." requires-python = ">=3.11" license = "MIT" diff --git a/release-please-config.json b/release-please-config.json new file mode 100644 index 00000000..9285ea8c --- /dev/null +++ b/release-please-config.json @@ -0,0 +1,20 @@ +{ + "$schema": "https://raw.githubusercontent.com/googleapis/release-please/main/schemas/config.json", + "release-type": "python", + "include-component-in-tag": false, + "packages": { + ".": { + "package-name": "panopticon", + "extra-files": [ + { + "type": "generic", + "path": "pyproject.toml" + }, + { + "type": "generic", + "path": "src/panopticon/__init__.py" + } + ] + } + } +} diff --git a/src/panopticon/__init__.py b/src/panopticon/__init__.py index 714665d1..65b49a6f 100644 --- a/src/panopticon/__init__.py +++ b/src/panopticon/__init__.py @@ -1,3 +1,3 @@ """panopticon — keep an eye on your agents.""" -__version__ = "0.0.1" +__version__ = "0.0.1" # x-release-please-version