From 3b72e5c93d39b9d5998ed852852c76844280a615 Mon Sep 17 00:00:00 2001 From: zhanghui Date: Fri, 24 Jul 2026 15:29:37 +0800 Subject: [PATCH] ci(release): add PyPI Trusted Publishing workflow + /release skill MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit GitHub had no publish path — the package was previously released from the (now-archived) GitLab mirror. Bring publishing to the public repo: - .github/workflows/release.yml: on a vX.Y.Z tag, build + smoke-test (make package) and upload to PyPI via Trusted Publishing (OIDC, no stored token), gated behind the `release` environment for manual approval. A guard step refuses to publish when the tag != pyproject version. - .claude/skills/release: /release documents the cut (bump version → CHANGELOG → tag → approve → verify) and the one-time PyPI trusted-publisher + GitHub environment setup. Requires two owner-only one-time steps before the first release (documented in the workflow header and the skill): register the GitHub trusted publisher on PyPI, and create the `release` environment with required reviewers. Co-Authored-By: Claude Opus 4.8 (1M context) --- .claude/skills/release/SKILL.md | 52 ++++++++++++++++++++++++++++++ .github/workflows/release.yml | 56 +++++++++++++++++++++++++++++++++ CLAUDE.md | 2 +- 3 files changed, 109 insertions(+), 1 deletion(-) create mode 100644 .claude/skills/release/SKILL.md create mode 100644 .github/workflows/release.yml diff --git a/.claude/skills/release/SKILL.md b/.claude/skills/release/SKILL.md new file mode 100644 index 00000000..2c39733f --- /dev/null +++ b/.claude/skills/release/SKILL.md @@ -0,0 +1,52 @@ +--- +name: release +description: Cut a versioned release and publish everos to PyPI via the tag-triggered workflow +--- + +# /release + +Publish a new version of `everos` to PyPI. Publishing is automated: pushing a +`vX.Y.Z` tag triggers [.github/workflows/release.yml](../../../.github/workflows/release.yml), +which builds, smoke-tests, and uploads via PyPI **Trusted Publishing** (OIDC — +no stored token) behind the `release` environment's manual-approval gate. + +## Preconditions + +- On `main`, up to date, with green CI (the tag builds from `main`'s tree). +- Decide the version per SemVer: patch = fixes, minor = back-compatible + features, major = breaking changes. + +## Steps + +``` +1. Bump the version → pyproject.toml [project] version = "X.Y.Z" + (single source; everos.__version__ reads installed package metadata) +2. Update CHANGELOG.md → move the Unreleased entries under a new + ## [X.Y.Z] - heading +3. Commit → git commit -m "chore(release): vX.Y.Z" +4. Open a PR, merge to main after green CI +5. Tag main + push → git tag -a vX.Y.Z -m "vX.Y.Z" && git push origin vX.Y.Z +6. Approve → the release.yml run pauses on the `release` + environment; a reviewer approves in the Actions run +7. Verify → https://pypi.org/project/everos/X.Y.Z/ +``` + +The tag must equal the `pyproject.toml` version — the workflow refuses to +publish on a mismatch. + +## Pre-releases + +PEP 440 pre-release tags publish too (PyPI accepts them; `pip install everos` +ignores them unless `--pre`): `vX.Y.ZrcN`, `vX.Y.ZaN`, `vX.Y.ZbN`. Set the same +suffix in `pyproject.toml` version before tagging. + +## One-time setup (project owner, not doable from CI) + +1. **PyPI trusted publisher** — PyPI → project `everos` → Settings → + Publishing → add: owner `EverMind-AI`, repo `EverOS`, workflow + `release.yml`, environment `release`. +2. **GitHub environment** — repo Settings → Environments → create `release` + with required reviewers, so every publish needs a manual approval. + +No PyPI API token is ever stored; the workflow mints a short-lived OIDC token +at publish time. diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 00000000..798595e2 --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,56 @@ +# Publishes to PyPI via Trusted Publishing (OIDC) — no stored token. +# Fires on a version tag (vX.Y.Z, plus PEP 440 pre-releases vX.Y.ZrcN / aN / bN). +# The `release` environment gates the upload behind manual approval; configure +# required reviewers under Settings → Environments → release. +# +# One-time setup on PyPI (project owner, cannot be done from CI): +# PyPI → project `everos` → Settings → Publishing → add a GitHub trusted +# publisher: owner=EverMind-AI, repo=EverOS, workflow=release.yml, +# environment=release. +name: Release + +on: + push: + tags: + # vX.Y.Z plus PEP 440 pre-release/dev suffixes (rc1 / a1 / b1 / .dev1). + - "v[0-9]+.[0-9]+.[0-9]+*" + +permissions: + contents: read + +jobs: + publish: + name: build + publish to PyPI + runs-on: ubuntu-latest + environment: release + permissions: + id-token: write # OIDC token for PyPI Trusted Publishing + contents: read + steps: + - uses: actions/checkout@v6 + + - name: Install uv + uses: astral-sh/setup-uv@v8.2.0 + with: + enable-cache: true + cache-dependency-glob: uv.lock + + - name: Set up Python + run: uv python install 3.12 + + # Guard: the tag must match the package version, so a mistyped tag can't + # publish the wrong release. + - name: Verify tag matches pyproject version + run: | + tag="${GITHUB_REF_NAME#v}" + pkg="$(grep -m1 -E '^version = ' pyproject.toml | sed -E 's/^version = "(.+)"/\1/')" + if [ "$tag" != "$pkg" ]; then + echo "::error::tag v$tag != pyproject version $pkg" + exit 1 + fi + + - name: Build + smoke-test the package + run: make package # builds sdist+wheel into dist/ and import-smokes it + + - name: Publish to PyPI (Trusted Publishing) + uses: pypa/gh-action-pypi-publish@release/v1 diff --git a/CLAUDE.md b/CLAUDE.md index 91d48755..e3cdc655 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -38,7 +38,7 @@ Detailed: [docs/architecture.md](docs/architecture.md). ## Engineering practices - **Coding rules** auto-loaded from [.claude/rules/](.claude/rules/) (10 rules; the three always-loaded ones cover architecture / code-style / language-policy, the rest are path-scoped and load when Claude Code opens a matching file) -- **Workflows** as slash commands in [.claude/skills/](.claude/skills/) — `/commit`, `/new-branch`, `/pr` +- **Workflows** as slash commands in [.claude/skills/](.claude/skills/) — `/commit`, `/new-branch`, `/pr`, `/release` - **Project-level decisions** in [docs/](docs/) (low-frequency, human-judgment-required) - **Language policy**: the project targets a global audience — docs and code are English; CJK appears only in test fixtures and locale-suffixed mirrors. Scanned by `make check-cjk`. - **Datetime discipline**: never call `datetime.now()` / `time.time()` directly — use `everos.component.utils.datetime`. Enforced by `make check-datetime`.