ci(release): add PyPI Trusted Publishing workflow + /release skill (#358)
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: zhanghui <zhanghui@shanda.com> Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
42629dfd4d
commit
bebb448c89
|
|
@ -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] - <date> 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.
|
||||
|
|
@ -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
|
||||
|
|
@ -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`.
|
||||
|
|
|
|||
Loading…
Reference in New Issue