From c959c5cb478ef2e0665cf757ccf1f18e00187d56 Mon Sep 17 00:00:00 2001 From: BREW <81044362+b-re-w@users.noreply.github.com> Date: Sat, 3 Oct 2026 20:12:58 +0900 Subject: [PATCH] Ci: Publish to PyPI when a GitHub Release is published, not on a tag push Standardized across the repositories by the ecosystem lead. PyPI never accepts the same version twice, so publishing has to be a deliberate act: a v* tag trigger would fire on a stray `git push --tags`, while a Release records the tag and its notes together. publish-pypi.yml now runs on `release: published` and checks the Release's tag (github.event.release.tag_name) against pyproject.toml. The `pypi` environment keeps its v* tag policy, since a Release runs on its tag's ref. AGENTS.md section 15, SPEC S1.1 and PROJECT.md describe the step: publish a GitHub Release tagged v (an alpha marked pre-release) after the maintainer approves; a bare tag push publishes nothing. ci: publish trigger. documentation corrected: AGENTS.md 15, SPEC S1.1, PROJECT.md. The YAML parses; tests unchanged (the version check script is the same). Refs #66 --- .github/workflows/publish-pypi.yml | 19 ++++++++++++------- AGENTS.md | 9 +++++---- PROJECT.md | 10 ++++++---- docs/SPEC.md | 5 +++-- 4 files changed, 26 insertions(+), 17 deletions(-) diff --git a/.github/workflows/publish-pypi.yml b/.github/workflows/publish-pypi.yml index 45a71c9..7e97030 100644 --- a/.github/workflows/publish-pypi.yml +++ b/.github/workflows/publish-pypi.yml @@ -1,11 +1,14 @@ name: Publish to PyPI -# A pushed `v*` tag releases that commit. The tag must be `v` + the version in pyproject.toml. The -# upload uses PyPI trusted publishing: no token is stored anywhere; PyPI trusts this workflow and -# the `pypi` environment, whose deployment policy allows `v*` tags only. +# Publishing a GitHub Release releases its tag's commit. A version cannot be uploaded to PyPI +# twice, so publishing is a deliberate step: a Release records the tag and its notes together, +# where a `v*` tag trigger would fire on a stray `git push --tags`. The Release's tag must be `v` + +# the version in pyproject.toml. The upload uses PyPI trusted publishing: no token is stored +# anywhere; PyPI trusts this workflow and the `pypi` environment, whose deployment policy allows +# `v*` tags only (a Release runs on its tag's ref). on: - push: - tags: ['v*'] + release: + types: [published] permissions: contents: read @@ -21,8 +24,10 @@ jobs: with: python-version: '3.13' - run: python -m pip install build twine - - name: Check the tag against pyproject.toml - run: python .github/scripts/check_release_version.py "$GITHUB_REF_NAME" + - name: Check the Release's tag against pyproject.toml + env: + RELEASE_TAG: ${{ github.event.release.tag_name }} + run: python .github/scripts/check_release_version.py "$RELEASE_TAG" - name: Build the sdist and the wheel run: python -m build - run: twine check dist/* diff --git a/AGENTS.md b/AGENTS.md index e8f33f4..8ab2f29 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -222,10 +222,11 @@ worktree so a test can see it. Read it in place. - The distribution name is `pythonx-compose` (`pyproject.toml`); the import package is `pythonx.compose`. -- **Release.** After the maintainer approves, tag the commit to release `v` (the - `pyproject.toml` version, e.g. `v0.1.0a1`) and push the tag; `.github/workflows/publish-pypi.yml` - builds, checks and publishes through PyPI trusted publishing (environment `pypi`). Never upload - by hand, and never store a PyPI token. +- **Release.** After the maintainer approves, publish a GitHub Release whose tag is `v` + (the `pyproject.toml` version, e.g. `v0.1.0a1`; mark an alpha or beta as a pre-release) on the + commit to release; `.github/workflows/publish-pypi.yml` runs on the Release, checks, builds and + publishes through PyPI trusted publishing (environment `pypi`). Pushing a tag alone publishes + nothing. Never upload by hand, and never store a PyPI token. - `.pyi` stubs, `py.typed` and the manifest must end up **inside the wheel**. A `package-data` pattern is not proof — build the wheel and list it before claiming something ships. diff --git a/PROJECT.md b/PROJECT.md index 8abdcb3..deabc5c 100644 --- a/PROJECT.md +++ b/PROJECT.md @@ -125,10 +125,12 @@ python3 .github/scripts/check_guide.py # 가이드: HTML 파싱, 링 - 워크플로 이름: `tests.yml` → `test.yml`, PyPI 배포는 `publish-pypi.yml`. - 첫 버전은 `0.1.0a1`(알파, 프리릴리스). PyPI 메타데이터(설명·README·MIT·저자·URL·분류자)는 채웠고, README 의 링크는 pypi.org 에서 열리도록 절대 URL 이다(`main` 링크는 `main` 이 `develop` 을 따라잡은 뒤에 열린다). -- 배포 방법: 릴리스할 커밋에 `v0.1.0a1` 태그를 푸시하면 `publish-pypi.yml` 이 태그와 `pyproject.toml` - 버전을 대조하고, sdist·wheel 을 빌드하고, `twine check`·새 venv 스모크를 거쳐 트러스티드 퍼블리싱으로 - 올린다. **현황: 업로드 전.** 태그 푸시는 메인테이너 승인을 기다리고, GitHub 환경 `pypi`(`v*` 태그만 허용) - 와 PyPI pending publisher 는 메인테이너가 설정한다. +- 배포 방법: 릴리스할 커밋에 태그 `v0.1.0a1` 로 GitHub Release 를 게시하면(알파는 pre-release 표시) + `publish-pypi.yml` 이 Release 태그와 `pyproject.toml` 버전을 대조하고, sdist·wheel 을 빌드하고, + `twine check`·새 venv 스모크를 거쳐 트러스티드 퍼블리싱으로 올린다. 태그만 푸시해서는 아무것도 + 올라가지 않는다(같은 버전은 PyPI 에 다시 올릴 수 없으므로 의도적인 단계로 둠). **현황: 업로드 전.** + Release 게시는 메인테이너 승인을 기다린다. GitHub 환경 `pypi`(`v*` 태그만 허용)는 만들었고, PyPI + pending publisher 는 메인테이너가 등록한다. ### 결정됨 (2026-10-03, `docs/INTENT.md` §5) diff --git a/docs/SPEC.md b/docs/SPEC.md index 296a7a8..6d37bee 100644 --- a/docs/SPEC.md +++ b/docs/SPEC.md @@ -57,8 +57,9 @@ in §9 and are not counted as features. pypi.org cannot resolve relative ones), `license = "MIT"` with `LICENSE`, the two authors as `LICENSE` names them (no emails), keywords, classifiers (Alpha, Python 3.11-3.13, Typed) and project URLs. `build-system.requires` is `setuptools>=77`, which understands that license form. -- Released by pushing a tag `v` (`.github/workflows/publish-pypi.yml`): the workflow checks - the tag equals `v` + the `pyproject.toml` version (`.github/scripts/check_release_version.py`, +- Released by publishing a GitHub Release tagged `v` (`.github/workflows/publish-pypi.yml`, + trigger `release: published`; a bare tag push publishes nothing): the workflow checks the + Release's tag equals `v` + the `pyproject.toml` version (`.github/scripts/check_release_version.py`, `tests/test_release_version.py`), builds the sdist and wheel, runs `twine check`, smoke-installs the wheel in a fresh venv, then uploads through PyPI trusted publishing (environment `pypi`, no token). Nothing is uploaded by hand.