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
26 changes: 26 additions & 0 deletions .github/scripts/tag_release.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
# tag_release.py
import sys
import subprocess
import tomli # Or `tomli` for Python < 3.11

# 1. Read the version from the single source of truth
with open("pyproject.toml", "rb") as f:
pyproject_data = tomli.load(f)
version = pyproject_data["project"]["version"]

# 2. Construct the tag and the git command
tag = f"v{version}"
print(f"Found version {version}. Creating tag: {tag}")

# 3. Run the git command
try:
subprocess.run(["git", "tag", tag], check=True)
except subprocess.CalledProcessError:
print(f"Error: Could not create tag. Does the tag '{tag}' already exist?")
sys.exit(1)
except FileNotFoundError:
print("Error: 'git' command not found. Is Git installed and in your PATH?")
sys.exit(1)
else:
print(f"Successfully created tag '{tag}'.")
sys.exit(0)
30 changes: 30 additions & 0 deletions .github/scripts/validate_main.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
#!/usr/bin/env bash
set -euo pipefail

# Check if on main branch
CURRENT_BRANCH=$(git branch --show-current)
if [ "$CURRENT_BRANCH" != "main" ]; then
echo "Error: You are not on the main branch. Please switch to main."
exit 1
fi

# Check for uncommitted changes
if [ -n "$(git status --porcelain)" ]; then
echo "Error: There are uncommitted changes. Please commit or stash them."
exit 1
fi

# Fetch latest changes from remote
git fetch

# Check if local main is up to date with origin/main
if ! git rev-parse origin/main > /dev/null 2>&1; then
echo "Error: Remote branch origin/main does not exist. Please set up a remote tracking branch."
exit 1
fi
LOCAL_HASH=$(git rev-parse main)
REMOTE_HASH=$(git rev-parse origin/main)
if [ "$LOCAL_HASH" != "$REMOTE_HASH" ]; then
echo "Error: Your local main branch is not up to date with origin/main. Please pull the latest changes."
exit 1
fi
31 changes: 31 additions & 0 deletions .github/workflows/ci-checks.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
name: CI Checks and Reports Update

on:
pull_request:
branches:
- main
push:
branches:
- main
# Manual triggering is allowed as well
workflow_dispatch:

jobs:
run-tests:
name: Run Tests and Generate Reports
runs-on: ubuntu-latest

steps:
- name: Checkout code
uses: actions/checkout@v4

- name: Setup uv
uses: astral-sh/setup-uv@v6
with:
enable-cache: true

- name: Setup Nox
uses: wntrblm/[email protected]

- name: Run Nox sessions
run: nox
99 changes: 99 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
# HOW THIS WORKFLOW WORKS
# This workflow automates creating a GitHub Release and publishing the Python package to PyPI.

# --- Manual Steps ---
# 1. Edit `pyproject.toml` to set the new version (e.g., `version = "1.2.3"` or `version = "1.2.4-rc1"` for pre-releases).
# 2. Commit the change and merge it to the `main` branch (From the feature branch or however you work).
# 3. On your local machine, fetch and pull the latest changes from `main` to ensure you're up to date.
# 4. Use the justfile: `just release` to trigger the process.

# --- Automation ---
# - runs .github/scripts/tag_release.py to create a new tag based on the version in `pyproject.toml`.
# - Pushes the new tag to github which triggers this workflow file.
# - Checks that the tag matches the version in `pyproject.toml`.
# - Builds the sdist and wheel.
# - Publishes the package to PyPI using trusted publishing.
# - Reads the release notes from your CHANGELOG.md.
# - Creates a new GitHub Release, marking it as a pre-release if necessary.


name: Create Release and Publish to PyPI

on:
push:
tags:
- "v*" # Runs on any tag starting with "v", e.g., v1.2.3 or v1.2.3-rc1
workflow_dispatch:
inputs:
tag_name:
description: 'Tag to create release for (e.g., v1.2.3). Must start with "v".'
required: true
type: string

jobs:
build-and-publish:
name: Build and Publish
runs-on: ubuntu-latest
permissions:
id-token: write # Required for Trusted Publishing with PyPI (OIDC).
contents: write # Required to create the GitHub Release.

steps:
# If manually triggered, checkout the specific tag. Otherwise, it checks out the tag that triggered the workflow.
# Fetch all history for all tags so the changelog-reader can find previous tags.
- name: Check out code
uses: actions/checkout@v4
with:
ref: ${{ github.event.inputs.tag_name || github.ref }}
fetch-depth: 0

- name: Set up Python
uses: actions/setup-python@v5
with:
python-version-file: '.python-version'

- name: Install required python packages
run: python -m pip install --upgrade build tomli

# Use github.ref_name which reliably gives the tag name (e.g., "v1.2.3")
# Create a step output named 'version' that contains the tag name without the 'v'
- name: Verify tag matches pyproject.toml version
run: |
TAG_NAME="${{ github.ref_name }}"
PYPROJECT_VERSION=$(python -c "import tomli; print(tomli.load(open('pyproject.toml', 'rb'))['project']['version'])")

if [ "v$PYPROJECT_VERSION" != "$TAG_NAME" ]; then
echo "Error: Tag '$TAG_NAME' does not match pyproject.toml version 'v$PYPROJECT_VERSION'"
exit 1
fi

echo "Tag and pyproject.toml version match: $TAG_NAME"
echo "version=${TAG_NAME#v}" >> $GITHUB_OUTPUT

- name: Build package
run: python -m build

- name: Publish to PyPI
uses: pypa/gh-action-pypi-publish@release/v1

- name: Get Changelog Entry
id: changelog_reader
uses: mindsers/changelog-reader-action@v2
with:
validation_level: warn
version: ${{ steps.version_check.outputs.version }}
path: ./CHANGELOG.md

- name: Create GitHub Release
uses: ncipollo/release-action@v1
with:
# Use the tag name that triggered the workflow
tag: ${{ github.ref_name }}
# The release title will be, e.g., "Release v1.2.3"
name: Release ${{ github.ref_name }}
# The body of the release is the changelog entry from the previous step
body: ${{ steps.changelog_reader.outputs.changes }}
# Automatically mark as pre-release if the tag contains a hyphen (e.g., v1.2.3-rc1)
prerelease: ${{ contains(github.ref_name, '-') }}
# This allows the action to update a release if it already exists
allowUpdates: true
22 changes: 19 additions & 3 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,23 @@ build/
dist/
wheels/
*.egg-info
**/Thumbs.db
**/thumbs.db

# Textual stuff
snapshot_report.html

# Virtual environments
.venv
*.drawio.bkp

# tooling stuff
*_cache/
.nox/

# other
[Tt]humbs.db
*.drawio.bkp

# AI / Agent stuff
.crush
CRUSH.md
.claude
CLAUDE.md
2 changes: 1 addition & 1 deletion .python-version
Original file line number Diff line number Diff line change
@@ -1 +1 @@
3.13
3.12
File renamed without changes.
68 changes: 44 additions & 24 deletions justfile
Original file line number Diff line number Diff line change
Expand Up @@ -14,42 +14,62 @@ run-dev:
console:
uv run textual console -x EVENT -x SYSTEM -x WORKER


# Runs ruff, exits with 0 if no issues are found
lint:
uv run ruff check src || (echo "Ruff found issues. Please address them." && exit 1)
@uv run ruff check src || (echo "Ruff found issues. Please address them." && exit 1)

# Runs mypy, exits with 0 if no issues are found
typecheck:
uv run mypy src || (echo "Mypy found issues. Please address them." && exit 1)
@uv run mypy src || (echo "Mypy found issues. Please address them." && exit 1)
@uv run basedpyright src || (echo "BasedPyright found issues. Please address them." && exit 1)

# Runs black
format:
uv run black src
@uv run black src

test:
@uv run pytest tests -vvv

test-update:
@uv run pytest tests -vvv --snapshot-update

# Runs ruff, mypy, and black
all-checks: lint typecheck format
echo "All pre-commit checks passed. You're good to publish."

# Build the package, run clean first
build: clean
@echo "Building the package..."
uv build

# Publish the package, run build first
publish: build
@echo "Publishing the package..."
uv publish

# Remove the build and dist directories
all-checks: lint typecheck format test
echo "All checks passed."

# Run the Nox testing suite for comprehensive testing
nox:
nox

# Remove build/dist directories and pyc files
clean:
rm -rf build dist
find . -name "*.pyc" -delete
rm -rf build dist
find . -name "*.pyc" -delete

# Remove tool caches
clean-caches:
rm -rf .mypy_cache
rm -rf .ruff_cache
rm -rf .nox

# Remove the virtual environment and lock file
del-env:
rm -rf .venv
rm -rf uv.lock
rm -rf .venv
rm -rf uv.lock

nuke: clean clean-caches del-env
@echo "All build artifacts and caches have been removed."

# Removes all environment and build stuff
reset: nuke install
@echo "Environment reset."

# Release the kraken
release:
bash .github/scripts/validate_main.sh && \
uv run .github/scripts/tag_release.py && \
git push --tags

reset: clean del-env install
@echo "Resetting the environment..."
#-------------------------------------------------------------------------------
sync-tags:
git fetch --prune origin "+refs/tags/*:refs/tags/*"
Loading