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
465 changes: 465 additions & 0 deletions .clickup-docs-updates.md

Large diffs are not rendered by default.

48 changes: 19 additions & 29 deletions .github/CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,34 +32,24 @@
- **Merge PRs into `develop`.** Configure repository settings so that branches are deleted automatically after PRs are merged.
- **Only merge to `main` if [fast-forwarding](https://www.git-scm.com/book/en/v2/Git-Branching-Basic-Branching-and-Merging) from `develop`.**
- **Enable [branch protection](https://docs.github.com/en/free-pro-team@latest/github/administering-a-repository/about-protected-branches) on `develop` and `main`.**
- **Set up a release workflow.** Here's an example release workflow, controlled by Git tags:
- Bump the version number in `pyproject.toml` with `poetry version` and commit the changes to `develop`.
- Push to `develop` and verify all CI checks pass.
- Fast-forward merge to `main`, push, and verify all CI checks pass.
- Create an [annotated and signed Git tag](https://www.git-scm.com/book/en/v2/Git-Basics-Tagging)
- Follow [SemVer](https://semver.org/) guidelines when choosing a version number.
- List PRs and commits in the tag message:
```sh
git log --pretty=format:"- %s (%h)" "$(git describe --abbrev=0 --tags)"..HEAD
```
- Omit the leading `v` (use `1.0.0` instead of `v1.0.0`)
- Example: `git tag -a -s 1.0.0`
- Push the tag. GitHub Actions will build and push the Python package and Docker images.
- **Create a changelog.** Here's an example changelog generation command, controlled by Git tags:

```sh
printf '# Changelog\n\n' >CHANGELOG.md

GIT_LOG_FORMAT='## %(subject) - %(taggerdate:short)

%(contents:body)
Tagger: %(taggername) %(taggeremail)
Date: %(taggerdate:iso)

%(contents:signature)'

git tag -l --sort=-taggerdate:iso --format="$GIT_LOG_FORMAT" >>CHANGELOG.md
```
- **Release workflow is fully automated.** This project uses [python-semantic-release](https://python-semantic-release.readthedocs.io/) for automated versioning:
- Releases happen automatically when commits are pushed to `main`
- Version is determined by analyzing [conventional commits](https://www.conventionalcommits.org/)
- CHANGELOG.md is automatically updated
- Git tags are created and GPG signed
- Package is published to PyPI via Trusted Publishing
- GitHub Release is created with changelog
- Documentation is deployed automatically
- **No manual tagging or version bumping required!**
- For detailed information, see [Release Automation Guide](../docs/releases.md)
- **Use conventional commits.** All commit messages must follow the [Conventional Commits](https://www.conventionalcommits.org/) specification:
- `feat:` for new features (minor version bump)
- `fix:` for bug fixes (patch version bump)
- `docs:`, `chore:`, `ci:`, etc. for non-releasing changes
- `feat!:` or `BREAKING CHANGE:` for breaking changes (major version bump)
- Commit messages are validated before commit via lefthook
- PR titles must also follow this format
- See [Release Automation Guide](../docs/releases.md) for examples and best practices

## Git

Expand All @@ -68,7 +58,7 @@
- [Configure Git to connect to GitHub with SSH](https://docs.github.com/en/free-pro-team@latest/github/authenticating-to-github/connecting-to-github-with-ssh)
- [Fork](https://docs.github.com/en/free-pro-team@latest/github/getting-started-with-github/fork-a-repo) this repo
- Create a [branch](https://www.git-scm.com/book/en/v2/Git-Branching-Branches-in-a-Nutshell) in your fork.
- Commit your changes with a [properly-formatted Git commit message](https://chris.beams.io/posts/git-commit/).
- Commit your changes with a [conventional commit message](https://www.conventionalcommits.org/). See [Release Automation Guide](../docs/releases.md) for format and examples.
- Create a [pull request (PR)](https://docs.github.com/en/free-pro-team@latest/github/collaborating-with-issues-and-pull-requests/about-pull-requests) to incorporate your changes into the upstream project you forked.

## Python
Expand Down
2 changes: 1 addition & 1 deletion .github/actions/setup-python-uv/action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ inputs:
python-version:
description: 'Python version to use'
required: false
default: '3.13'
default: '3.14'
uv-version:
description: 'uv version to use'
required: false
Expand Down
24 changes: 16 additions & 8 deletions .github/workflows/docs.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,7 @@ on:
- main
release:
types:
- released
- prereleased
- published

jobs:
prepare:
Expand All @@ -36,11 +35,15 @@ jobs:

- name: Extract major and minor version
id: version
env:
LATEST_TAG: ${{ steps.latest_tag.outputs.tag }}
run: |
echo "value=`echo ${{ steps.latest_tag.outputs.tag }} | sed -r 's|(v[0-9]+.[0-9]+).*|\1|g'`" >> $GITHUB_OUTPUT
echo "value=$(echo "$LATEST_TAG" | sed -r 's|(v[0-9]+.[0-9]+).*|\1|g')" >> $GITHUB_OUTPUT

- name: Computed Doc Version
run: echo ${{ steps.version.outputs.value }}
env:
DOC_VERSION: ${{ steps.version.outputs.value }}
run: echo "$DOC_VERSION"

publish-docs:
name: Publish Docs
Expand All @@ -65,9 +68,12 @@ jobs:
git_commit_gpgsign: true

- name: Configurating Git
env:
GIT_EMAIL: ${{ steps.import-gpg.outputs.email }}
GIT_NAME: ${{ steps.import-gpg.outputs.name }}
run: |
git config user.email "${{ steps.import-gpg.outputs.email }}"
git config user.name "${{ steps.import-gpg.outputs.name }}"
git config user.email "$GIT_EMAIL"
git config user.name "$GIT_NAME"
git config core.autocrlf false
git config commit.gpgsign true

Expand All @@ -77,6 +83,8 @@ jobs:
dependency-groups: doc

- name: Publish
env:
DOC_VERSION: ${{ needs.prepare.outputs.version }}
run: |
uv run mike deploy --push --update-aliases "${{ needs.prepare.outputs.version }}" latest
uv run mike deploy --push --update-aliases "${{ needs.prepare.outputs.version }}" docs
uv run mike deploy --push --update-aliases "$DOC_VERSION" latest
uv run mike deploy --push --update-aliases "$DOC_VERSION" docs
106 changes: 106 additions & 0 deletions .github/workflows/semantic-release.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
name: Semantic Release

on:
push:
branches:
- main
workflow_dispatch:

permissions:
id-token: write # PyPI Trusted Publishing
contents: write # Create tags and releases
issues: write # Comment on issues
pull-requests: write # Comment on PRs

jobs:
release:
name: Semantic Release
runs-on: ubuntu-latest
outputs:
released: ${{ steps.release.outputs.released }}
version: ${{ steps.release.outputs.version }}
tag: ${{ steps.release.outputs.tag }}

steps:
- name: Checkout code
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5
with:
fetch-depth: 0
token: ${{ secrets.GITHUB_TOKEN }}

- name: Import GPG key
id: import-gpg
uses: crazy-max/ghaction-import-gpg@e89d40939c28e39f97cf32126055eeae86ba74ec # v6
with:
gpg_private_key: ${{ secrets.HOTHER_BOT_GPG_KEY }}
passphrase: ${{ secrets.HOTHER_BOT_GPG_PASSPHRASE }}
git_user_signingkey: true
git_commit_gpgsign: true
git_tag_gpgsign: true

- name: Configure Git
env:
GIT_EMAIL: ${{ steps.import-gpg.outputs.email }}
GIT_NAME: ${{ steps.import-gpg.outputs.name }}
run: |
git config user.email "$GIT_EMAIL"
git config user.name "$GIT_NAME"
git config core.autocrlf false
git config commit.gpgsign true
git config tag.gpgsign true

- name: Setup Python and uv
uses: ./.github/actions/setup-python-uv

- name: Python Semantic Release
id: release
uses: python-semantic-release/python-semantic-release@v9.15.3
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
git_committer_name: ${{ steps.import-gpg.outputs.name }}
git_committer_email: ${{ steps.import-gpg.outputs.email }}

- name: Publish to PyPI
if: steps.release.outputs.released == 'true'
uses: pypa/gh-action-pypi-publish@release/v1

- name: Publish GitHub Release
if: steps.release.outputs.released == 'true'
uses: python-semantic-release/publish-action@v9.15.3
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
tag: ${{ steps.release.outputs.tag }}

dispatch:
name: Dispatch to Package Registry
runs-on: ubuntu-latest
needs: release
if: needs.release.outputs.released == 'true'

steps:
- name: Checkout code
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5
with:
ref: ${{ needs.release.outputs.tag }}

- name: Get package description
id: description
run: |
python -c "import tomllib; print(tomllib.load(open('pyproject.toml', 'rb'))['project']['description'])" > desc.txt
echo "value=$(cat desc.txt)" >> $GITHUB_OUTPUT

- name: Dispatch to package registry
env:
GH_TOKEN: ${{ secrets.GH_TOKEN }}
PKG_NAME: ${{ vars.PKG_NAME }}
PKG_REGISTRY: ${{ vars.PKG_REGISTRY }}
VERSION_TAG: ${{ needs.release.outputs.tag }}
REPO_OWNER: ${{ github.repository_owner }}
REPO_FULL: ${{ github.repository }}
DESCRIPTION: ${{ steps.description.outputs.value }}
run: |
curl -H "Accept: application/vnd.github.everest-preview+json" \
-H "Authorization: token $GH_TOKEN" \
--request POST \
--data "{\"event_type\": \"new_release\", \"client_payload\": { \"package_name\": \"$PKG_NAME\", \"version\": \"$VERSION_TAG\", \"author\": \"$REPO_OWNER\", \"short_desc\": \"$DESCRIPTION\", \"long_desc\": \"$DESCRIPTION\", \"homepage\": \"https://github.com/$REPO_FULL/\", \"link\": \"https://github.com/$REPO_FULL/releases/tag/$VERSION_TAG\" }}" \
"https://api.github.com/repos/$PKG_REGISTRY/dispatches"
71 changes: 54 additions & 17 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -395,14 +395,6 @@ Git hooks for:
- **Plugins**: mkdocstrings, social cards, glightbox
- **Watch paths**: src/, examples/, docs/, docs/.hooks/

### `Makefile`
Convenience commands:
- `make install`: Setup dependencies
- `make test`: Run tests
- `make lint`: Run linting
- `make docs`: Serve documentation


## CI/CD

The project uses GitHub Actions for CI/CD:
Expand All @@ -416,12 +408,57 @@ The project uses GitHub Actions for CI/CD:

## Release Process

1. **Update version** in `pyproject.toml`
2. **Update CHANGELOG.md** with release notes
3. **Run full test suite**: `uv run pytest --cov`
4. **Build package**: `uv build`
5. **Test package locally**: `uv pip install dist/hother_cancelable-*.whl`
6. **Publish to PyPI**: `uv publish`
7. **Tag release**: `git tag v0.1.0 && git push --tags`
8. **Deploy docs**: `uv run mike deploy --update-aliases v0.1 latest`
9. **Create GitHub release** with changelog
This project uses **python-semantic-release** for fully automated versioning and releases.

### How It Works

Releases happen automatically when commits are pushed to `main`:
1. Commits are analyzed using conventional commit format
2. Version is automatically bumped based on commit types
3. CHANGELOG.md is updated
4. Git tag is created and pushed (GPG signed)
5. Package is built and published to PyPI (via Trusted Publishing)
6. GitHub Release is created
7. Documentation is deployed

### Version Bumping Rules

| Commit Type | Bump | Example |
|-------------|------|---------|
| `feat:` | Minor | 0.5.0 → 0.6.0 |
| `fix:`, `perf:`, `refactor:` | Patch | 0.5.0 → 0.5.1 |
| `feat!:`, `BREAKING CHANGE:` | Major | 0.5.0 → 1.0.0 |
| `docs:`, `chore:`, `ci:`, `style:`, `test:` | None | No release |

### Local Version Preview

```bash
# Check current version
grep 'version = ' pyproject.toml | cut -d'"' -f2

# Preview next version (on main branch)
uv run semantic-release --noop version --print
```

### Manual Release Trigger

```bash
# Via GitHub UI: Actions → Semantic Release → Run workflow
# or via gh CLI:
gh workflow run semantic-release.yml
```

### Documentation Deployment

Docs are automatically deployed on releases. Manual deployment:

```bash
# Deploy specific version
uv run mike deploy --push --update-aliases v0.5 latest

# Set default
uv run mike set-default latest

# Serve locally
uv run mkdocs serve
```
12 changes: 5 additions & 7 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -94,11 +94,9 @@ docs-publish: # Publishes the documentation
licenses:
uvx --from pip-licenses==5.0.0 pip-licenses --from=mixed --order count -f $(FORMAT) --output-file licenses.$(FORMAT)

changelog: ### Generate full changelog
git-cliff -o CHANGELOG.md
version-check: ### Show current and next version
@echo "Current: $(shell grep 'version = ' pyproject.toml | cut -d'"' -f2)"
@echo "Next: $(shell uv run semantic-release --noop version --print 2>/dev/null || echo 'No release needed (must be on main branch)')"

changelog-unreleased: ### Preview unreleased changes
@git-cliff --unreleased

changelog-tag: ### Get changelog for latest tag (for releases)
@git-cliff --latest --strip header
changelog-preview: ### Preview changelog for unreleased commits
@uv run semantic-release changelog
Loading
Loading