Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
167 changes: 0 additions & 167 deletions .github/workflows/monthly-release.yml

This file was deleted.

187 changes: 187 additions & 0 deletions .github/workflows/release-on-upstream.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,187 @@
name: Release on upstream

# Upstream uxarray does not release on a schedule: it skipped 2026.01 and
# 2026.05, shipped twice in August, and released 2026.09.0 on the 10th. A
# monthly cron on the 5th was therefore either early or late every time. This
# polls daily and reacts to what upstream actually published.
#
# It never publishes. Green opens a release PR; red opens an issue. Everything
# that reaches PyPI does so through a human merge of that PR.

on:
schedule:
# 05:17 UTC daily.
- cron: "17 5 * * *"
workflow_dispatch:
inputs:
version:
description: "Explicit version to release, e.g. 2026.9.0. Leave empty to derive from upstream."
required: false
default: ""
upstream:
description: "Upstream uxarray version to mirror. Leave empty to read PyPI."
required: false
default: ""
force:
description: "Prepare a release even with no commits and no new upstream."
required: false
type: boolean
default: false

permissions:
contents: write
pull-requests: write
issues: write

jobs:
prepare-release:
if: github.repository == 'UXARRAY/uxarray-mcp-server'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
persist-credentials: true

- name: Set up Git identity
run: |
git config user.name "Rajeev Jain"
git config user.email "rajeeja@gmail.com"

- name: Prepare version
id: prep
env:
INPUT_VERSION: ${{ inputs.version }}
INPUT_UPSTREAM: ${{ inputs.upstream }}
INPUT_FORCE: ${{ inputs.force }}
run: |
args=()
if [ -n "$INPUT_VERSION" ]; then
args+=(--version "$INPUT_VERSION")
fi
if [ -n "$INPUT_UPSTREAM" ]; then
args+=(--upstream "$INPUT_UPSTREAM")
fi
if [ "$INPUT_FORCE" = "true" ]; then
args+=(--force)
fi
python scripts/prepare_release.py "${args[@]}"

- name: Stop if no release is needed
if: steps.prep.outputs.release_needed != 'true'
run: |
echo "No commits since the latest tag and no new upstream release."

- name: Install uv
if: steps.prep.outputs.release_needed == 'true'
uses: astral-sh/setup-uv@v5

- name: Set up Python 3.12
if: steps.prep.outputs.release_needed == 'true'
uses: actions/setup-python@v5
with:
python-version: "3.12"

- name: Relock uv.lock at the new version
if: steps.prep.outputs.release_needed == 'true'
run: |
# uv.lock records the project's own version and the resolved uxarray,
# so the bump above left it describing the previous release against
# the previous upstream. Nothing in CI passes --locked, so the
# mismatch is invisible here and surfaces for whoever checks out the
# tag and runs `uv sync --locked`.
# Extras go in here, not on the `uv run` lines below: those pass
# --no-sync, and a `uv run --extra` would have to sync to honour it.
uv lock
uv sync --locked --dev --extra hpc --extra docs

- name: Install the upstream release being mirrored
if: steps.prep.outputs.release_needed == 'true'
env:
UPSTREAM: ${{ steps.prep.outputs.upstream }}
run: |
if [ -n "$UPSTREAM" ]; then
uv pip install --upgrade "uxarray==${UPSTREAM}"
fi
uv pip list | grep -E '^uxarray '

- name: Run release checks
if: steps.prep.outputs.release_needed == 'true'
# --no-sync, or this step silently undoes the install above it. A bare
# `uv run` re-syncs the environment to `uv.lock` first, which would put
# back whatever uxarray the lock resolved and leave this job proving
# nothing about the upstream release it is named for. Measured in
# upstream-compat.yml: a bare `uv run` reported the locked version
# after an explicit `uv pip install` of a different one.
run: |
uv run --no-sync pre-commit run --all-files
uv run --no-sync pytest tests/ -m "not live" -v
uv run --no-sync --extra docs sphinx-build -b html docs docs/_build/html -W --keep-going

- name: Build and check distributions
if: steps.prep.outputs.release_needed == 'true'
run: |
uv build
uvx twine check dist/*
uv venv /tmp/uxarray-mcp-wheel --python 3.12
uv pip install --python /tmp/uxarray-mcp-wheel/bin/python dist/uxarray_mcp-*.whl
/tmp/uxarray-mcp-wheel/bin/uxarray-mcp --help

- name: Update conda recipe source hash
if: steps.prep.outputs.release_needed == 'true'
env:
RELEASE_VERSION: ${{ steps.prep.outputs.version }}
run: |
python scripts/update_conda_recipe.py \
--version "$RELEASE_VERSION" \
--sdist dist/uxarray_mcp-*.tar.gz

- name: Open the release pull request
if: steps.prep.outputs.release_needed == 'true'
env:
GH_TOKEN: ${{ github.token }}
RELEASE_VERSION: ${{ steps.prep.outputs.version }}
RELEASE_TAG: ${{ steps.prep.outputs.tag }}
PREVIOUS_TAG: ${{ steps.prep.outputs.previous_tag }}
CHANGED_COMMITS: ${{ steps.prep.outputs.changed_commits }}
UPSTREAM: ${{ steps.prep.outputs.upstream }}
run: |
# No tag, no GitHub release, no dispatch to release.yml. The tag is
# cut from the merge commit by a human; nothing here can reach PyPI.
branch="release/${RELEASE_VERSION}"
git checkout -b "$branch"
git add pyproject.toml src/uxarray_mcp/__init__.py \
conda/recipe/meta.yaml CHANGELOG.md uv.lock
if git diff --cached --quiet; then
echo "Version files already match ${RELEASE_VERSION}; nothing to open."
exit 0
fi
git commit -m "Release ${RELEASE_VERSION}"
git push origin "$branch"
{
echo "Release ${RELEASE_VERSION} against uxarray ${UPSTREAM:-unchanged}, ${CHANGED_COMMITS} commit(s) since ${PREVIOUS_TAG:-the first commit}."
echo "Checks, build and wheel smoke test passed on this branch before it was opened."
echo "Merging this, then tagging ${RELEASE_TAG} on main, is what publishes."
} > pr-body.md
gh pr create \
--repo "$GITHUB_REPOSITORY" \
--base main \
--head "$branch" \
--title "Release ${RELEASE_VERSION}" \
--body-file pr-body.md

- name: Report a failed release check
if: failure() && steps.prep.outputs.release_needed == 'true'
env:
GH_TOKEN: ${{ github.token }}
RELEASE_VERSION: ${{ steps.prep.outputs.version }}
UPSTREAM: ${{ steps.prep.outputs.upstream }}
run: |
{
echo "Preparing ${RELEASE_VERSION} against uxarray ${UPSTREAM:-unchanged} failed before any pull request was opened."
echo "Run: ${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY}/actions/runs/${GITHUB_RUN_ID}"
} > issue-body.md
gh issue create \
--repo "$GITHUB_REPOSITORY" \
--title "Release ${RELEASE_VERSION} blocked by uxarray ${UPSTREAM:-unchanged}" \
--body-file issue-body.md
2 changes: 1 addition & 1 deletion .github/zizmor.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,6 @@ rules:
ignore:
- ci.yml
- upstream-compat.yml
- monthly-release.yml
- release-on-upstream.yml
- release.yml
- zizmor.yml
20 changes: 18 additions & 2 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
# Changelog

All notable changes are recorded here. Dates are ISO 8601 (UTC). The project
uses Semantic Versioning for public releases.
All notable changes are recorded here. Dates are ISO 8601 (UTC). Releases are
CalVer, `<year>.<month>.<patch>`, mirroring the `uxarray` release they were
built against; see `docs/release.md`. Versions through `0.3.1` were SemVer.

## Unreleased
### Added
Expand All @@ -27,6 +28,21 @@ uses Semantic Versioning for public releases.
an explicit `get_submission_id()`, which keeps the wire shape in one place and
lets all 44 tests run against a fake client with no credentials in CI.

### Changed
- Releases now follow upstream instead of the calendar. The workflow polled on
the 5th of every month, but upstream skipped 2026.01 and 2026.05, shipped
twice in August, and released 2026.09.0 on the 10th, so the poll was either
early or late every time. It now runs daily, reads the newest `uxarray` from
PyPI (not from upstream's tags, which mix `v2026.09.0` and `v2026.4.0`), and
releases `<year>.<month>.<patch>` mirroring upstream's month. The release
commit moves both ends of the uxarray pin: a floor raised without its
ceiling leaves the package uninstallable beside the very release it was
tested against. Zero-padded versions are refused rather than normalized,
because PEP 440 strips the zero and `2026.09.0` would publish as a dist
disagreeing with its own tag. The workflow no longer tags, releases or
publishes on its own — green opens a release pull request, red opens an
issue, and a human merge is what reaches PyPI.

### Fixed
- A directory of SCRIP meshes was classified as nothing and advised nothing.
`_GRID_HINTS` held `grid`, `mesh`, `topo`, `coord` and `geo` but not
Expand Down
Loading
Loading