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
18 changes: 5 additions & 13 deletions .claude/commands/ready-release.md
Original file line number Diff line number Diff line change
@@ -1,21 +1,13 @@
Walk me through readying a gxformat2 release. Run each step, check for problems, and pause if anything looks wrong.
Walk me through readying a gxformat2 release. The full process is documented in `docs/developing.rst` — read that first and defer to it for details. This command drives the interactive parts.

## Steps

1. **Check git status** - ensure working tree is clean, no missing files.
1. **Confirm target version** — read `__version__` in `gxformat2/__init__.py`, show me the current `.devN`, and ask me to confirm the target release version.

2. **Verify version** - read `gxformat2/__init__.py` and confirm `__version__` is a `.devN` variant of the intended release. Show me the current version and ask me to confirm the target release version.
2. **Populate changelog** — run `make add-history`, show me the new `HISTORY.rst` entries for review, pause so I can edit if needed, then commit the result (`make check-release` requires a clean tree).

3. **Setup venv** - check `.venv` exists. If not, run `make setup-venv`. Confirm dev-requirements are installed.
3. **Pre-release checks** — run `make check-release`. If it fails, stop and report. Do not try to work around failures; fix the underlying issue.

4. **Check UPSTREAM remote** - the Makefile defaults `UPSTREAM` to `galaxyproject`. Check if `$UPSTREAM` is set in the environment; if not, check if a git remote named `galaxyproject` exists. If it doesn't, check for `origin` or `upstream` remotes pointing to `galaxyproject/gxformat2` and offer to create a `galaxyproject` alias via `git remote add galaxyproject <url>`. This must be resolved before `make release` can push.

5. **Add history** - run `make add-history` to pull contributions into HISTORY.rst under the .dev0 entry. Show me the new HISTORY.rst additions for review.

6. **Lint** - run `make clean && make lint`. Report any failures.

7. **Review** - show me a summary of outstanding uncommitted changes (if any) and ask if I want to commit them before proceeding.

8. **Release** - after I confirm, run `make release` which does: commit-version, new-version, release-artifacts, push-release. This tags, bumps to next dev version, and pushes upstream.
4. **Release** — after I confirm, run `make release`.

Stop after each step and report status before moving to the next.
36 changes: 29 additions & 7 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,9 @@ BUILD_SCRIPTS_DIR=scripts
DEV_RELEASE?=0
VERSION?=$(shell DEV_RELEASE=$(DEV_RELEASE) python $(BUILD_SCRIPTS_DIR)/print_version_for_release.py $(SOURCE_DIR) $(DEV_RELEASE))
DOC_URL?=https://gxformat2.readthedocs.org
PROJECT_URL?=https://github.com/jmchilton/gxformat2
PROJECT_NAME?=gxformat2
UPSTREAM_REPO?=$(UPSTREAM)/$(PROJECT_NAME)
PROJECT_URL?=https://github.com/$(UPSTREAM_REPO)
TEST_DIR?=tests
DOCS_DIR?=docs

Expand Down Expand Up @@ -45,19 +46,20 @@ clean-test: ## remove test and coverage artifacts

setup-venv: ## setup a development virutalenv in current directory
if command -v uv > /dev/null 2>&1; then \
uv sync --group test --group lint --group mypy; \
uv sync; \
else \
if [ ! -d $(VENV) ]; then \
python3 -m venv $(VENV); \
fi; \
$(IN_VENV) pip install -r requirements.txt && pip install -r dev-requirements.txt; \
fi

setup-git-hook-lint: ## setup precommit hook for linting project
cp $(BUILD_SCRIPTS_DIR)/pre-commit-lint .git/hooks/pre-commit

setup-git-hook-lint-and-test: ## setup precommit hook for linting and testing project
cp $(BUILD_SCRIPTS_DIR)/pre-commit-lint-and-test .git/hooks/pre-commit
setup-pre-commit: ## install pre-commit hook (uses .pre-commit-config.yaml if present, else the .sample)
@if [ -f .pre-commit-config.yaml ]; then \
$(IN_VENV) pre-commit install; \
else \
$(IN_VENV) pre-commit install --config .pre-commit-config.yaml.sample; \
fi

lint: ## check style with ruff, flake8, black, and mypy
uv run --group lint isort --check --diff .
Expand Down Expand Up @@ -114,6 +116,26 @@ commit-version: ## Update version and history, commit.
new-version: ## Mint a new version
$(IN_VENV) DEV_RELEASE=$(DEV_RELEASE) python $(BUILD_SCRIPTS_DIR)/new_version.py $(SOURCE_DIR) $(VERSION)

check-release: ## pre-release checklist: venv, clean tree, history entries, lint, lint-docs
@echo "==> checking $(VENV) exists"
@test -d $(VENV) || \
(echo "ERROR: $(VENV) does not exist; run 'make setup-venv'"; exit 1)
@echo "==> checking working tree is clean"
@test -z "$$(git status --porcelain)" || \
(echo "ERROR: working tree has uncommitted changes or untracked files"; git status --short; exit 1)
@echo "==> checking UPSTREAM remote '$(UPSTREAM)' points to $(UPSTREAM_REPO)"
@git remote get-url $(UPSTREAM) 2>/dev/null | grep -q "$(UPSTREAM_REPO)" || \
(echo "ERROR: remote '$(UPSTREAM)' missing or not pointing to $(UPSTREAM_REPO)."; \
echo " Fix: git remote add $(UPSTREAM) git@github.com:$(UPSTREAM_REPO).git"; \
echo " Or fork: make check-release UPSTREAM=<name> UPSTREAM_REPO=<owner>/<repo>"; exit 1)
@echo "==> checking HISTORY.rst has entries under current .devN header"
@$(IN_VENV) python $(BUILD_SCRIPTS_DIR)/check_history_entries.py
@echo "==> make clean && make lint && make lint-docs"
@$(MAKE) clean
@$(MAKE) lint
@$(MAKE) lint-docs
@echo "==> check-release OK"

release-local: commit-version new-version

push-release: ## Push a tagged release to github
Expand Down
120 changes: 85 additions & 35 deletions docs/developing.rst
Original file line number Diff line number Diff line change
@@ -1,41 +1,91 @@
==================
Release Checklist
==================
==========
Developing
==========

This page describes the process of releasing new versions of gxformat2.
This page describes how to set up a development environment for gxformat2
and how to cut a release.

This release checklist is based on the `Pocoo Release Management Workflow
<http://www.pocoo.org/internal/release-management/>`_.
Development Setup
=================

This assumes ``~/.pypirc`` file exists with the following fields (variations)
are fine.
Create a virtualenv with dev dependencies installed::

make setup-venv

This runs ``uv sync``. The ``test``, ``lint``, ``mypy``, and ``docs``
dependency groups are marked as ``default-groups`` in ``pyproject.toml``
so they install automatically.

Optional pre-commit hooks are configured via `pre-commit
<https://pre-commit.com/>`_. A sample configuration ships as
``.pre-commit-config.yaml.sample``. Install the hook with::

make setup-pre-commit

This uses ``.pre-commit-config.yaml`` if you have one (copy the sample and
customize), and otherwise falls back to the committed
``.pre-commit-config.yaml.sample``.

Running Tests and Linters
=========================

::

[pypi]
username:<username>
password:<password>

[test]
repository:https://testpypi.python.org/pypi
username:<username>
password:<password>


* Review ``git status`` for missing files.
* Verify the latest Travis CI builds pass.
* ``make open-docs`` and review changelog.
* Ensure the target release is set correctly in ``galaxy/__init__.py`` (
``version`` will be a ``devN`` variant of target release).
* ``make clean && make lint && make test``
* ``make release``

This process will push packages to test PyPI, allow review, publish
to production PyPI, tag the git repository, push the tag upstream,
and modify the Homebrew recipe. If changes are needed, such as manual
changes to the homebrew recipe, this can be broken down into steps
such as:

* ``make release-local``
* ``make push-release``
* ``make release-brew``
make test # pytest
make lint # ruff, flake8, black, mypy, schema build dry-run
make lint-docs # rebuild Sphinx HTML and check for warnings

Set ``GXFORMAT2_TEST_IWC_DIRECTORY`` to a local clone of the IWC repository
to enable integration tests that exercise real-world workflows.

Releases
========

gxformat2 publishes to PyPI via GitHub Actions trusted publishing — no
``~/.pypirc`` or PyPI credentials are needed on the release machine. Pushing
a version tag to ``galaxyproject/gxformat2`` triggers the publish workflow.

Pre-release checklist
---------------------

Run::

make check-release

This verifies:

* ``.venv`` exists (or ``uv`` is available).
* Working tree is clean (no uncommitted or untracked files).
* The ``UPSTREAM`` git remote exists and points to ``UPSTREAM_REPO``
(defaults: ``galaxyproject`` / ``galaxyproject/gxformat2``).
* ``HISTORY.rst`` has entries under the current ``.devN`` section.
* ``make clean``, ``make lint``, and ``make lint-docs`` all pass.

Fork maintainers can override the defaults::

make check-release UPSTREAM=myfork UPSTREAM_REPO=me/gxformat2

Cutting the release
-------------------

1. Populate the changelog from merged PRs::

make add-history

Review the diff to ``HISTORY.rst``, adjust as needed, and commit it
(``make check-release`` in the next step requires a clean working tree).

2. Confirm the target version in ``gxformat2/__init__.py`` (``__version__``
is the ``.devN`` variant of the release you intend to cut).

3. ``make check-release``.

4. ``make release`` — this runs ``commit-version``, ``new-version``, and
``push-release``, which commits the version bump, tags the release,
bumps ``__version__`` to the next ``.dev0``, and pushes ``main`` plus
the tag to ``UPSTREAM``. The pushed tag triggers the PyPI publish
workflow on GitHub.

If you need finer control the release can be broken into its pieces:
``make release-local`` (commit, tag, next-dev) followed by
``make push-release`` (push main and tags).
3 changes: 3 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,9 @@ docs = [
"sphinxcontrib-mermaid",
]

[tool.uv]
default-groups = ["test", "lint", "mypy", "docs"]

[tool.ruff]
exclude = [
"gxformat2/schema/v19_09.py",
Expand Down
26 changes: 26 additions & 0 deletions scripts/check_history_entries.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
"""Fail if HISTORY.rst's current .devN section has no bullet entries."""

import re
import sys
from pathlib import Path

HISTORY = Path(__file__).resolve().parent.parent / "HISTORY.rst"


def main() -> int:
text = HISTORY.read_text()
m = re.search(r"-{3,}\n(\d+\.\d+\.\d+\.dev\d+)\n-{3,}\n(.*?)(?=\n-{3,}\n|\Z)", text, re.DOTALL)
if not m:
print(f"ERROR: no .devN section found in {HISTORY}", file=sys.stderr)
return 1
version, body = m.group(1), m.group(2)
bullets = [line for line in body.splitlines() if line.lstrip().startswith("*")]
if not bullets:
print(f"ERROR: {version} section in HISTORY.rst has no entries; run 'make add-history'", file=sys.stderr)
return 1
print(f"OK: {version} has {len(bullets)} entr{'y' if len(bullets) == 1 else 'ies'}")
return 0


if __name__ == "__main__":
sys.exit(main())
3 changes: 0 additions & 3 deletions scripts/pre-commit-lint

This file was deleted.

4 changes: 0 additions & 4 deletions scripts/pre-commit-lint-and-test

This file was deleted.