Contributing¶
Thanks for your interest in contributing! There are plenty of ways to help out — from reporting bugs to adding new project templates.
Ways to contribute¶
- Bug reports — Open an issue with steps to reproduce, your OS, and Python version
- Feature requests — Open an issue describing the use case
- New templates — Add a UI framework or project type (mostly declarative — see Adding a new template)
- New boilerplate — Drop starter files into the boilerplate directory; no code changes needed
- Bug fixes and features — Fork, branch, fix, test, PR
- Documentation — Improvements to these docs, in-app help, or code comments
Development setup¶
You'll need Python 3.12+, uv, and Git.
git clone https://github.com/oktl/uv-forger.git
cd uv-forger
uv run uv-forger # Run the app
uv run pytest # Run 781+ tests (coverage automatic)
uv run ruff check uv_forger # Lint
uv run ruff format uv_forger # Format
A pre-commit hook runs Ruff automatically on commit, so you'll catch lint issues before pushing.
Code style¶
- Linter/formatter: Ruff — rules
E,F,I,W,UP,B,SIM - Imports: Absolute
uv_forger.*paths only (e.g.,from uv_forger.core.state import AppState) - Type hints: Use
str | NonenotOptional[str];collections.abc.Callablenottyping.Callable - Models: Dataclasses for new data structures
- Async: Wrap coroutines with
wrap_async()for Flet callbacks; useAsyncExecutor.run()for subprocess calls
Testing¶
Run the full suite before opening a PR:
Tests live in tests/, mirroring the uv_forger/ structure. pytest-asyncio is configured in auto mode — async test functions just work.
Add tests for any new functionality in tests/core/, tests/handlers/, or tests/ui/. Handler tests use Handlers(MockPage(), MockControls(), AppState()) — no real Flet runtime needed.
Submitting a pull request¶
- Fork the repo and create a branch from
main - Make your changes
- Run
uv run pytest— all tests must pass - Run
uv run ruff check uv_forger— no lint errors - Open a PR with a clear description of what changed and why
Small, focused PRs are easier to review. If you're planning something significant, open an issue first so we can discuss the approach.
Releasing¶
Two GitHub Actions workflows, run in order. Never create the tag by hand — the version bump
has to land before the tag, or publish.yml builds the old version and PyPI rejects it with
400 File already exists.
- Update the changelog — add a
## [x.y.z]section toCHANGELOG.mdanddocs/changelog.md, commit, push tomain. - Run the Release workflow — Actions → Release, or
gh workflow run release.yml -f bump=patch(patch|minor|major). It runs the test suite as a gate, thenuv version --bump, commitschore: bump version to x.y.z, and tagsvx.y.zon that commit. Add-f dry_run=true(or tick the box in the Actions UI) to rehearse — runs the test gate and prints the target version, then stops without committing, tagging, or pushing. - Cut the release —
gh release create vx.y.z --generate-notes. Therelease: publishedevent triggerspublish.yml, which builds from the tag and uploads to PyPI via trusted publishing. This step is manual because releases created byGITHUB_TOKENdon't trigger other workflows. - Pull locally —
git pullto get the bump commit.
publish.yml guards against a mismatched tag: it fails the run if the tag doesn't equal
uv version --short.
Questions?¶
Open a GitHub issue — happy to help.