Skip to content

Commit 4c79ee2

Browse files
committed
Automate monthly releases
1 parent 4193272 commit 4c79ee2

5 files changed

Lines changed: 425 additions & 6 deletions

File tree

Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
1+
name: Monthly release
2+
3+
on:
4+
schedule:
5+
# 05:00 UTC on the 5th day of every month.
6+
- cron: "0 5 5 * *"
7+
workflow_dispatch:
8+
inputs:
9+
version:
10+
description: "Explicit version to release, e.g. 0.1.0. Leave empty to auto-bump patch."
11+
required: false
12+
default: ""
13+
force:
14+
description: "Release even if there are no commits since the latest v* tag."
15+
required: false
16+
type: boolean
17+
default: false
18+
19+
permissions:
20+
contents: write
21+
22+
jobs:
23+
prepare-release:
24+
if: github.repository == 'UXARRAY/uxarray-mcp-server'
25+
runs-on: ubuntu-latest
26+
steps:
27+
- uses: actions/checkout@v4
28+
with:
29+
fetch-depth: 0
30+
31+
- name: Set up Git identity
32+
run: |
33+
git config user.name "Rajeev Jain"
34+
git config user.email "rajeeja@gmail.com"
35+
36+
- name: Prepare version
37+
id: prep
38+
run: |
39+
args=()
40+
if [ -n "${{ inputs.version }}" ]; then
41+
args+=(--version "${{ inputs.version }}")
42+
fi
43+
if [ "${{ inputs.force }}" = "true" ]; then
44+
args+=(--force)
45+
fi
46+
python scripts/prepare_release.py "${args[@]}"
47+
48+
- name: Stop if no release is needed
49+
if: steps.prep.outputs.release_needed != 'true'
50+
run: |
51+
echo "No commits since latest release tag; skipping monthly release."
52+
53+
- name: Install uv
54+
if: steps.prep.outputs.release_needed == 'true'
55+
uses: astral-sh/setup-uv@v5
56+
57+
- name: Set up Python 3.13
58+
if: steps.prep.outputs.release_needed == 'true'
59+
uses: actions/setup-python@v5
60+
with:
61+
python-version: "3.13"
62+
63+
- name: Run release checks
64+
if: steps.prep.outputs.release_needed == 'true'
65+
run: |
66+
uv run pre-commit run --all-files
67+
uv run pytest tests/ --ignore=tests/test_remote_agent.py -v
68+
uv run --extra hpc pytest tests/test_remote_agent.py tests/test_hpc_safety.py -v
69+
uv run --extra docs sphinx-build -b html docs docs/_build/html -W --keep-going
70+
71+
- name: Build and check distributions
72+
if: steps.prep.outputs.release_needed == 'true'
73+
run: |
74+
uv build
75+
uvx twine check dist/*
76+
uv venv /tmp/uxarray-mcp-wheel --python 3.13
77+
uv pip install --python /tmp/uxarray-mcp-wheel/bin/python dist/uxarray_mcp-*.whl
78+
/tmp/uxarray-mcp-wheel/bin/uxarray-mcp --help
79+
80+
- name: Update conda recipe source hash
81+
if: steps.prep.outputs.release_needed == 'true'
82+
run: |
83+
python scripts/update_conda_recipe.py \
84+
--version "${{ steps.prep.outputs.version }}" \
85+
--sdist dist/uxarray_mcp-*.tar.gz
86+
87+
- name: Commit version bump
88+
if: steps.prep.outputs.release_needed == 'true'
89+
run: |
90+
git add pyproject.toml src/uxarray_mcp/__init__.py conda/recipe/meta.yaml
91+
git commit -m "Release ${{ steps.prep.outputs.version }}"
92+
93+
- name: Tag release
94+
if: steps.prep.outputs.release_needed == 'true'
95+
run: |
96+
git tag -a "${{ steps.prep.outputs.tag }}" -m "Release ${{ steps.prep.outputs.version }}"
97+
98+
- name: Push release commit and tag
99+
if: steps.prep.outputs.release_needed == 'true'
100+
run: |
101+
git push origin HEAD:main
102+
git push origin "${{ steps.prep.outputs.tag }}"
103+
104+
- name: Create GitHub release
105+
if: steps.prep.outputs.release_needed == 'true'
106+
env:
107+
GH_TOKEN: ${{ github.token }}
108+
run: |
109+
notes="Automated release ${{ steps.prep.outputs.version }}."
110+
if [ -n "${{ steps.prep.outputs.previous_tag }}" ]; then
111+
notes="$notes
112+
113+
Changes since ${{ steps.prep.outputs.previous_tag }}: ${{ steps.prep.outputs.changed_commits }} commit(s)."
114+
else
115+
notes="$notes
116+
117+
Initial release."
118+
fi
119+
gh release create "${{ steps.prep.outputs.tag }}" \
120+
--repo "$GITHUB_REPOSITORY" \
121+
--title "${{ steps.prep.outputs.tag }}" \
122+
--notes "$notes" \
123+
dist/*

.github/workflows/release.yml

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,9 @@ on:
55
types:
66
- published
77

8+
permissions:
9+
contents: read
10+
811
jobs:
912
test-build:
1013
if: github.repository == 'UXARRAY/uxarray-mcp-server'
@@ -57,3 +60,76 @@ jobs:
5760
with:
5861
skip-existing: true
5962
verbose: true
63+
64+
update-conda-feedstock:
65+
runs-on: ubuntu-latest
66+
needs: publish-pypi
67+
env:
68+
CONDA_FEEDSTOCK_REPOSITORY: ${{ vars.CONDA_FEEDSTOCK_REPOSITORY }}
69+
CONDA_FEEDSTOCK_TOKEN: ${{ secrets.CONDA_FEEDSTOCK_TOKEN }}
70+
steps:
71+
- name: Skip feedstock update when not configured
72+
if: env.CONDA_FEEDSTOCK_REPOSITORY == '' || env.CONDA_FEEDSTOCK_TOKEN == ''
73+
run: |
74+
echo "CONDA_FEEDSTOCK_REPOSITORY or CONDA_FEEDSTOCK_TOKEN is not configured; skipping feedstock PR."
75+
76+
- name: Check out source repository
77+
if: env.CONDA_FEEDSTOCK_REPOSITORY != '' && env.CONDA_FEEDSTOCK_TOKEN != ''
78+
uses: actions/checkout@v4
79+
80+
- name: Install uv
81+
if: env.CONDA_FEEDSTOCK_REPOSITORY != '' && env.CONDA_FEEDSTOCK_TOKEN != ''
82+
uses: astral-sh/setup-uv@v5
83+
84+
- name: Set up Python 3.13
85+
if: env.CONDA_FEEDSTOCK_REPOSITORY != '' && env.CONDA_FEEDSTOCK_TOKEN != ''
86+
uses: actions/setup-python@v5
87+
with:
88+
python-version: "3.13"
89+
90+
- name: Build source distribution for hash
91+
if: env.CONDA_FEEDSTOCK_REPOSITORY != '' && env.CONDA_FEEDSTOCK_TOKEN != ''
92+
run: uv build --sdist
93+
94+
- name: Check out feedstock
95+
if: env.CONDA_FEEDSTOCK_REPOSITORY != '' && env.CONDA_FEEDSTOCK_TOKEN != ''
96+
uses: actions/checkout@v4
97+
with:
98+
repository: ${{ env.CONDA_FEEDSTOCK_REPOSITORY }}
99+
token: ${{ env.CONDA_FEEDSTOCK_TOKEN }}
100+
path: feedstock
101+
102+
- name: Update feedstock recipe
103+
if: env.CONDA_FEEDSTOCK_REPOSITORY != '' && env.CONDA_FEEDSTOCK_TOKEN != ''
104+
run: |
105+
version="${GITHUB_REF_NAME#v}"
106+
python scripts/update_conda_recipe.py \
107+
--recipe feedstock/recipe/meta.yaml \
108+
--version "$version" \
109+
--sdist dist/uxarray_mcp-*.tar.gz
110+
111+
- name: Open feedstock pull request
112+
if: env.CONDA_FEEDSTOCK_REPOSITORY != '' && env.CONDA_FEEDSTOCK_TOKEN != ''
113+
env:
114+
GH_TOKEN: ${{ env.CONDA_FEEDSTOCK_TOKEN }}
115+
run: |
116+
version="${GITHUB_REF_NAME#v}"
117+
cd feedstock
118+
branch="uxarray-mcp-${version}"
119+
git config user.name "Rajeev Jain"
120+
git config user.email "rajeeja@gmail.com"
121+
git checkout -b "$branch"
122+
git add recipe/meta.yaml
123+
if git diff --cached --quiet; then
124+
echo "Feedstock recipe already up to date."
125+
exit 0
126+
fi
127+
git commit -m "Update uxarray-mcp to ${version}"
128+
git push origin "$branch"
129+
pr_url=$(gh pr create \
130+
--repo "$CONDA_FEEDSTOCK_REPOSITORY" \
131+
--base main \
132+
--head "$branch" \
133+
--title "Update uxarray-mcp to ${version}" \
134+
--body "Automated conda-forge feedstock update for uxarray-mcp ${version}.")
135+
gh pr merge "$pr_url" --auto --squash --repo "$CONDA_FEEDSTOCK_REPOSITORY"

docs/release.md

Lines changed: 55 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,42 @@
11
# Release Process
22

3-
This project follows the same broad release model as UXarray:
3+
This project follows the same broad release model as UXarray, with an added
4+
scheduled release workflow:
45

56
1. GitHub CI must be green on `main`.
6-
2. A GitHub Release is published from a `v<version>` tag.
7-
3. The release workflow builds and publishes the Python package to PyPI.
8-
4. Conda packages are handled through a conda-forge feedstock.
7+
2. On the 5th of every month, GitHub Actions checks for commits since the
8+
latest `v*` tag.
9+
3. If there are no new commits, the release is skipped.
10+
4. If there are new commits, the workflow bumps the patch version, commits it,
11+
creates a `v<version>` tag, and publishes a GitHub Release.
12+
5. The release workflow builds and publishes the Python package to PyPI.
13+
6. Conda packages are handled through a conda-forge feedstock.
14+
15+
## Monthly Automation
16+
17+
`.github/workflows/monthly-release.yml` runs at 05:00 UTC on the 5th of every
18+
month. It can also be run manually with `workflow_dispatch`.
19+
20+
Default behavior:
21+
22+
- no previous `v*` tag: release the current version from `pyproject.toml`
23+
- previous `vX.Y.Z` tag exists and commits have landed since then: release
24+
`X.Y.(Z+1)`
25+
- no commits since the latest tag: skip
26+
27+
Manual inputs:
28+
29+
- `version`: release an explicit version such as `0.1.0`
30+
- `force`: release even if there are no commits since the latest tag
31+
32+
The workflow updates:
33+
34+
- `pyproject.toml`
35+
- `src/uxarray_mcp/__init__.py`
36+
- `conda/recipe/meta.yaml`
37+
38+
Then it runs the release checks, builds the package, commits the version bump,
39+
tags it, pushes to `main`, and creates the GitHub Release.
940

1041
## PyPI
1142

@@ -19,6 +50,9 @@ Before the first release, configure PyPI trusted publishing:
1950
- Workflow: `release.yml`
2051
- Environment: `pypi`
2152

53+
The GitHub repository must also have an environment named `pypi` and Actions
54+
workflow permissions set to read/write.
55+
2256
Release steps:
2357

2458
```bash
@@ -29,8 +63,8 @@ uv run --extra docs sphinx-build -b html docs docs/_build/html -W --keep-going
2963
uv build
3064
```
3165

32-
Then create and publish a GitHub Release for a tag such as `v0.1.0`. The
33-
workflow will:
66+
The release workflow (`.github/workflows/release.yml`) runs after a GitHub
67+
Release is published. It will:
3468

3569
- build the source distribution and wheel
3670
- run `twine check`
@@ -57,6 +91,21 @@ Conda packages should be published through conda-forge, not from this repository
5791
directly. A seed recipe lives at `conda/recipe/meta.yaml` to bootstrap a future
5892
`uxarray-mcp-feedstock`.
5993

94+
Fully automatic Conda releases require a feedstock repository and two GitHub
95+
settings in this repository:
96+
97+
- variable `CONDA_FEEDSTOCK_REPOSITORY`, for example
98+
`conda-forge/uxarray-mcp-feedstock`
99+
- secret `CONDA_FEEDSTOCK_TOKEN`, a token with permission to push branches and
100+
open pull requests on that feedstock
101+
102+
When those are configured, the PyPI release workflow will update the feedstock
103+
recipe hash/version, open a pull request, and enable auto-merge. Conda-forge CI
104+
then builds and uploads the package after the PR merges.
105+
106+
If those settings are not configured, the workflow still publishes PyPI and
107+
skips the feedstock update with a clear log message.
108+
60109
Feedstock steps:
61110

62111
1. Publish the PyPI release first.

0 commit comments

Comments
 (0)