@@ -2,20 +2,18 @@ name: Release
22
33on :
44 push :
5- tags : [ 'v*.*.*' ]
6- workflow_dispatch : {}
5+ tags : [ 'v*.*.*' ] # only runs on semver-like tags
6+ workflow_dispatch : {} # manual runs won't publish unless also with a tag
77
88permissions :
99 contents : read
1010
1111jobs :
1212 build-publish :
13- # uncomment the next line ONLY if you set an Environment name on PyPI's Trusted Publisher
14- # environment: pypi
1513 runs-on : ubuntu-latest
1614 permissions :
17- contents : write # attach assets to GH Release
18- id-token : write # OIDC for PyPI Trusted Publisher
15+ contents : write # attach assets to GH Release
16+ id-token : write # OIDC for PyPI Trusted Publisher
1917 steps :
2018 - name : Checkout
2119 uses : actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5
2523 with :
2624 python-version : " 3.12"
2725
28- # Install build + twine via pipx (no raw pip)
26+ # Tools via pipx (pinned; no raw pip)
2927 - name : Install pipx + tools
3028 run : |
3129 set -euxo pipefail
3634 pipx install build==1.2.2
3735 pipx install twine==5.1.1
3836
37+ # tag (vX.Y.Z) must match pyproject.toml version (X.Y.Z)
38+ - name : Check version matches tag
39+ id : vercheck
40+ run : |
41+ TAG="${GITHUB_REF_NAME#v}"
42+ PYVER=$(python - <<'PY'
43+ import sys, tomllib, pathlib
44+ data = tomllib.loads(pathlib.Path("pyproject.toml").read_text(encoding="utf-8"))
45+ print(data["project"]["version"])
46+ PY
47+ )
48+ echo "tag=${TAG}"
49+ echo "pyproject=${PYVER}"
50+ test "$PYVER" = "$TAG" || { echo "ERROR: pyproject.toml version != tag"; exit 1; }
51+
3952 - name : Build distributions
40- run : python -m build -o dist
53+ run : pyproject- build -o dist
4154
4255 - name : Twine check
4356 run : twine check dist/*
5164 env :
5265 GITHUB_TOKEN : ${{ secrets.GITHUB_TOKEN }}
5366
67+ # skip if version already on PyPI (prevents "File already exists")
68+ - name : Skip if version exists on PyPI
69+ id : pypi-exists
70+ run : |
71+ PKG="decision-security"
72+ VER="${GITHUB_REF_NAME#v}"
73+ python - <<PY
74+ import json, sys, urllib.request
75+ pkg = sys.argv[1]; ver = sys.argv[2]
76+ try:
77+ with urllib.request.urlopen(f"https://pypi.org/pypi/{pkg}/json", timeout=10) as r:
78+ data = json.load(r)
79+ if ver in data.get("releases", {}):
80+ print(f"Version {ver} already on PyPI; skipping publish.")
81+ sys.exit(78) # neutral
82+ except Exception:
83+ pass
84+ PY
85+ continue-on-error : true
86+
5487 - name : Publish to PyPI (OIDC Trusted Publisher)
55- if : startsWith(github.ref, 'refs/tags/')
88+ if : startsWith(github.ref, 'refs/tags/') && steps.pypi-exists.outcome != 'success'
5689 uses : pypa/gh-action-pypi-publish@ed0c53931b1dc9bd32cbe73a98c7f6766f8a527e # v1.13.0
5790 with :
5891 print-hash : true
0 commit comments