Contributing#

Setup#

You need uv and a JDK, because the functional tests compile and run a small processor jar.

uv sync
uv run pre-commit install

Checks#

Run these before you open a pull request:

uv run ruff format .
uv run ruff check .
uv run pyright
uv run pytest --cov

Testing#

Layer

Contains

Runs

tests/unit

Pure logic and real local files

By default

tests/functional

A real local HTTP server and a real JVM; the internet is blocked

By default

tests/live

Real platforms and complete loader installs

On request

Run the live tests with:

uv run pytest tests/live

CurseForge tests need CURSEFORGE_API_KEY and are skipped without it.

Tests do not use mocks. Do not use unittest.mock, patch internal functions or fake a process or an HTTP client. Setting environment variables with monkeypatch is allowed, because that configures the environment. If code is hard to test this way, give it a parameter, such as a URL, instead.

Platform behaviour is tested only in tests/live, against the real services.

Known gaps#

  • replace_file falls back to copying when a rename fails with EXDEV. The cross-filesystem test only runs when the working directory and the temp directory are on different drives. The MSIX behaviour that needed the fallback cannot be reproduced in a normal environment.

  • is_running() is checked against the real process table, but no test starts the official launcher, and the Microsoft Store launcher’s process name is unconfirmed.

  • No test starts the game.

Docstrings#

Write Google-style docstrings, in English, for every module, class and function. Ruff checks that they exist, so also check that they match what the code does.

  • Describe behaviour. Type annotations already give the types.

  • Add Args, Returns and Raises when a caller needs them.

  • Use single backticks for modsmith objects, which become links in the documentation, and double backticks for parameters and literal values.

  • Use a code comment only to explain why something is done.

Documentation#

The site is built with Sphinx and MyST, and the API reference comes from the docstrings. Warnings and broken references fail the build:

uv run sphinx-build -W -n -b dirhtml docs docs/_build/html

Examples in docstrings run as tests:

uv run sphinx-build -W -b doctest docs docs/_build/doctest

Continuous integration#

Workflow

Runs

Does

ci.yml

Pull requests and pushes to main

Lint, type checks, tests on every Python version on Linux and one each on Windows and macOS, lowest dependencies, documentation, package build

live.yml

Every Monday, or by hand

Runs tests/live on every OS

release.yml

v* tags

Runs CI, publishes to PyPI and creates a GitHub release

live.yml reads CURSEFORGE_API_KEY from the repository secrets.

Releasing#

  1. Set version in pyproject.toml and move the Unreleased notes in CHANGELOG.md under the new version.

  2. Merge that change into main.

  3. Push a tag such as v0.1.0. The tag must match the project version, and the release notes come from that version’s section of CHANGELOG.md.

Commits#

Keep the change focused, write the subject in the imperative mood, and add a line to CHANGELOG.md under Unreleased when users will notice the change.