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
29 changes: 29 additions & 0 deletions .github/workflows/publish-to-pypi.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,36 @@ on:
types: [published]

jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4

- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: "3.11"

- name: Install uv
uses: astral-sh/setup-uv@v5

- name: Verify release tag matches pyproject version
run: |
PYPROJECT_VERSION=$(python -c "import tomllib;print(tomllib.load(open('pyproject.toml','rb'))['project']['version'])")
TAG="${{ github.event.release.tag_name }}"
TAG_VERSION="${TAG#v}"
TAG_VERSION="${TAG_VERSION#V}"
echo "pyproject=$PYPROJECT_VERSION tag=$TAG normalized=$TAG_VERSION"
if [ "$PYPROJECT_VERSION" != "$TAG_VERSION" ]; then
echo "::error::Release tag '$TAG' does not match pyproject version '$PYPROJECT_VERSION'"
exit 1
fi

- name: Run tests
run: uv run pytest

publish:
needs: test
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
Expand Down
31 changes: 28 additions & 3 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co

### Building and Publishing
- Build package: `uv build`
- Publishing is automated via GitHub Actions on release creation
- Publishing is automated via GitHub Actions on GitHub Release creation, gated on the test suite and a tag↔version check. See [Release Process](#release-process) for the full runbook.

## Code Structure

Expand Down Expand Up @@ -111,14 +111,39 @@ dftt_timecode/

### CI/CD Workflows
- **Documentation:** Builds and deploys to GitHub Pages on push to main (`.github/workflows/docs.yml`)
- **Publishing:** Automatically publishes to PyPI on GitHub release creation (`.github/workflows/publish-to-pypi.yml`)
- **Publishing:** Publishes to PyPI on GitHub Release creation, after running the test suite and verifying the release tag matches `pyproject.toml` (`.github/workflows/publish-to-pypi.yml`). See [Release Process](#release-process)

## Version Information
- Current version: 1.0.0
- Current version: single-sourced from `pyproject.toml` (`[project].version`), read at runtime via `importlib.metadata`
- Python requirement: >=3.11
- Main dependencies: All standard library (fractions, logging, math, functools, re, subprocess)
- Dev dependencies: pytest, sphinx, pydata-sphinx-theme, myst-parser, sphinx-intl

## Release Process

Versioning follows [Semantic Versioning](https://semver.org). The version number changes **only at release time**, on a dedicated short-lived branch — never on feature branches.

### Branching and versioning model
- **Feature/bugfix branches** are short-lived and PR into `main`. Each PR adds its entry under the `## [Unreleased]` section of `CHANGELOG.md` and does **not** touch the version number. `main` therefore always accumulates changes under `Unreleased`.
- The **SemVer level is decided when cutting the release**, from what has accumulated under `Unreleased`:
- `### Added` (new, backwards-compatible) → **minor** bump
- `### Fixed` only → **patch** bump
- Any `### Removed` or breaking `### Changed` → **major** bump

### Cutting a release
1. From an up-to-date `main`, create `release/X.Y.Z`.
2. Bump the version in the **single source of truth**: `pyproject.toml` → `[project].version`. Do not hardcode the version anywhere else — runtime `__version__` (`dftt_timecode/__init__.py`, via `importlib.metadata`) and the Sphinx docs (`docs/conf.py`, via `tomllib`) read it automatically.
3. In `CHANGELOG.md`, rename `## [Unreleased]` to `## [X.Y.Z] - YYYY-MM-DD` and add a fresh empty `## [Unreleased]` above it.
4. Commit, push, and open a PR to `main` containing **only** release prep (version + changelog) — no feature code.
5. Merge the PR.
6. Create a **GitHub Release** with an annotated tag `vX.Y.Z` on the merge commit. This — not a bare tag push — is what triggers the PyPI publish (`.github/workflows/publish-to-pypi.yml`, `on: release: published`). The workflow runs the test suite and verifies the tag matches `pyproject.toml` before uploading.

### Conventions and gotchas
- **Tag format:** `vX.Y.Z`, annotated. (Historical tags are inconsistent — standardize going forward.)
- **Order matters:** the version bump must be merged to `main` *before* the GitHub Release is created, or the tag↔version guard fails and nothing publishes.
- **PyPI is immutable:** a version can never be re-uploaded. Run `uv run pytest` and `uv build` locally before releasing.
- **Long-lived `release/*` branches** are only warranted for parallel maintenance lines (e.g. patching 1.x while developing a breaking 2.0). For single-line development, keep release branches short-lived.

## Common Usage Patterns

### Basic Timecode Creation
Expand Down
2 changes: 1 addition & 1 deletion docs/index.rst
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
dftt_timecode
=============

.. image:: https://img.shields.io/badge/pypi-0.0.14-brightgreen
.. image:: https://img.shields.io/pypi/v/dftt-timecode.svg
:target: https://pypi.org/project/dftt-timecode/
:alt: PyPI

Expand Down
Loading