Skip to content

Commit 8158bd2

Browse files
committed
ci: rework release workflow
1 parent 114ea1c commit 8158bd2

File tree

1 file changed

+141
-37
lines changed

1 file changed

+141
-37
lines changed
Lines changed: 141 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -1,44 +1,148 @@
1-
name: Publish to PyPI
1+
name: Release
22

33
on:
44
release:
5-
types: [created]
5+
types: [published]
6+
workflow_dispatch:
7+
inputs:
8+
tag:
9+
description: 'Release tag (vX.Y.Z) to re-run publish flow'
10+
required: true
11+
type: string
12+
13+
concurrency:
14+
group: release-${{ github.event_name == 'workflow_dispatch' && format('refs/tags/{0}', github.event.inputs.tag) || github.ref }}
15+
cancel-in-progress: false
616

717
jobs:
8-
deploy:
18+
verify-and-build:
919
runs-on: ubuntu-latest
20+
env:
21+
RELEASE_REF: ${{ github.event_name == 'workflow_dispatch' && format('refs/tags/{0}', github.event.inputs.tag) || github.ref }}
22+
RELEASE_TAG: ${{ github.event_name == 'workflow_dispatch' && github.event.inputs.tag || github.ref_name }}
1023
steps:
11-
- uses: actions/checkout@v3
12-
13-
- name: Set up Python
14-
uses: actions/setup-python@v4
15-
with:
16-
python-version: '3.10'
17-
18-
- name: Install dependencies
19-
run: |
20-
python -m pip install --upgrade pip
21-
pip install build twine
22-
23-
- name: Extract version from tag
24-
id: get_version
25-
run: echo "VERSION=${GITHUB_REF#refs/tags/v}" >> $GITHUB_ENV
26-
27-
- name: Update version in files
28-
run: |
29-
# Update pyproject.toml version
30-
sed -i "s/^version = .*/version = \"${{ env.VERSION }}\"/" pyproject.toml
31-
32-
# Update __init__.py version
33-
sed -i "s/__version__ = .*/__version__ = \"${{ env.VERSION }}\"/" src/code_index_mcp/__init__.py
34-
35-
- name: Build package
36-
run: python -m build
37-
38-
- name: Check package
39-
run: twine check dist/*
40-
41-
- name: Publish to PyPI
42-
uses: pypa/gh-action-pypi-publish@release/v1
43-
with:
44-
password: ${{ secrets.PYPI_API_TOKEN }}
24+
- uses: actions/checkout@v4
25+
with:
26+
fetch-depth: 0
27+
ref: ${{ env.RELEASE_REF }}
28+
29+
- name: Ensure tag is published from main
30+
run: |
31+
git fetch origin main
32+
if ! git merge-base --is-ancestor "$(git rev-parse HEAD)" origin/main; then
33+
echo "::error::Release tag must point to a commit reachable from main"
34+
exit 1
35+
fi
36+
37+
- name: Set up Python
38+
uses: actions/setup-python@v5
39+
with:
40+
python-version: '3.10'
41+
42+
- name: Install uv
43+
run: python -m pip install --upgrade pip uv
44+
45+
- name: Cache uv environments
46+
uses: actions/cache@v4
47+
with:
48+
path: |
49+
.venv
50+
.uv-cache
51+
key: uv-${{ runner.os }}-${{ hashFiles('uv.lock') }}
52+
restore-keys: |
53+
uv-${{ runner.os }}-
54+
55+
- name: Install dependencies
56+
run: uv sync --frozen
57+
58+
- name: Verify version matches tag
59+
run: |
60+
TAG_VERSION="${RELEASE_TAG#v}"
61+
if [ "$TAG_VERSION" = "$RELEASE_TAG" ]; then
62+
echo "::error::Release tag must start with the letter 'v' (e.g. v1.2.3)"
63+
exit 1
64+
fi
65+
66+
PYPROJECT_VERSION=$(python - <<'PY'
67+
import pathlib, re
68+
text = pathlib.Path("pyproject.toml").read_text()
69+
match = re.search(r'^version\s*=\s*"([^"]+)"', text, re.MULTILINE)
70+
if not match:
71+
raise SystemExit("Could not read version from pyproject.toml")
72+
print(match.group(1))
73+
PY
74+
)
75+
76+
INIT_VERSION=$(python - <<'PY'
77+
import pathlib, re
78+
text = pathlib.Path("src/code_index_mcp/__init__.py").read_text()
79+
match = re.search(r'__version__\s*=\s*"([^"]+)"', text)
80+
if not match:
81+
raise SystemExit("Could not read __version__ from src/code_index_mcp/__init__.py")
82+
print(match.group(1))
83+
PY
84+
)
85+
86+
LOCK_VERSION=$(python - <<'PY'
87+
version = None
88+
with open("uv.lock", "r", encoding="utf-8") as fh:
89+
lines = fh.readlines()
90+
for idx, line in enumerate(lines):
91+
if line.strip() == 'name = "code-index-mcp"':
92+
for follower in lines[idx:idx+6]:
93+
if follower.strip().startswith("version ="):
94+
version = follower.split("=", 1)[1].strip().strip('"')
95+
break
96+
break
97+
if version is None:
98+
raise SystemExit("Could not find code-index-mcp in uv.lock")
99+
print(version)
100+
PY
101+
)
102+
103+
for entry in "pyproject.toml:$PYPROJECT_VERSION" "src/code_index_mcp/__init__.py:$INIT_VERSION" "uv.lock:$LOCK_VERSION"; do
104+
FILE=${entry%%:*}
105+
VERSION=${entry#*:}
106+
if [ "$VERSION" != "$TAG_VERSION" ]; then
107+
echo "::error file=$FILE::Expected version $TAG_VERSION but found $VERSION"
108+
exit 1
109+
fi
110+
done
111+
112+
- name: Run tests
113+
run: |
114+
uv run pytest
115+
uv run code-index-mcp --help
116+
117+
- name: Build distributions
118+
run: uv run python -m build
119+
120+
- name: Twine check
121+
run: uv run twine check dist/*
122+
123+
- name: Upload dist artifacts
124+
uses: actions/upload-artifact@v4
125+
with:
126+
name: dist-${{ env.RELEASE_TAG }}
127+
path: dist/*
128+
retention-days: 7
129+
130+
publish:
131+
needs: verify-and-build
132+
runs-on: ubuntu-latest
133+
environment:
134+
name: production
135+
env:
136+
RELEASE_TAG: ${{ github.event_name == 'workflow_dispatch' && github.event.inputs.tag || github.ref_name }}
137+
steps:
138+
- name: Download build artifacts
139+
uses: actions/download-artifact@v4
140+
with:
141+
name: dist-${{ env.RELEASE_TAG }}
142+
path: dist
143+
144+
- name: Publish to PyPI
145+
uses: pypa/gh-action-pypi-publish@release/v1
146+
with:
147+
packages-dir: dist
148+
password: ${{ secrets.PYPI_API_TOKEN }}

0 commit comments

Comments
 (0)