Skip to content

Release process

chatwire uses semantic versioning and publishes to PyPI. The Homebrew tap and mobile app have separate release cadences.

Versioning

The version source of truth is _version.py:

__version__ = "1.14.0"

All other version references derive from this: - pyproject.toml uses dynamic = ["version"] with version = { attr = "_version.__version__" } - The --version flag reads _version.__version__ - The update-check banner compares against this

Versioning rules (semver): - Patch (1.14.01.14.1) — bug fixes, no new features, no breaking changes - Minor (1.14.01.15.0) — new features, backward-compatible - Major (1.x.x2.0.0) — breaking changes (config schema, API, plugin SDK)

PyPI release

1. Bump the version

Edit _version.py:

__version__ = "1.15.0"

2. Update the changelog

Add a section to CHANGELOG.md (if it exists) or document the changes in the GitHub release notes.

3. Run the tests

python3 -m pytest tests/ --tb=short -q
cd web/frontend && npm run test -- --run && npm run typecheck

4. Build the wheel and sdist

python3 -m build
# Creates dist/chatwire-1.15.0-py3-none-any.whl and dist/chatwire-1.15.0.tar.gz

5. Verify the build

# Check the wheel contents
python3 -m zipfile -l dist/chatwire-1.15.0-py3-none-any.whl | head -30

# Install in a fresh venv and run doctor
python3 -m venv /tmp/test-chatwire
/tmp/test-chatwire/bin/pip install dist/chatwire-1.15.0-py3-none-any.whl
/tmp/test-chatwire/bin/chatwire --version

6. Publish to PyPI

twine upload dist/chatwire-1.15.0*

Requires PyPI credentials. Use twine upload --repository testpypi to test on TestPyPI first.

7. Create the GitHub release

git tag v1.15.0
git push origin v1.15.0

Then create a release on GitHub with: - Tag: v1.15.0 - Title: chatwire v1.15.0 - Notes: what changed, upgrade instructions if needed

Homebrew tap update

The Homebrew tap is at https://github.com/allenbina/homebrew-tap.

After publishing to PyPI, update the formula:

# Formula/chatwire.rb
class Chatwire < Formula
  include Language::Python::Virtualenv

  desc "macOS iMessage bridge: web UI, Telegram relay, plugins"
  homepage "https://github.com/allenbina/chatwire"
  url "https://files.pythonhosted.org/packages/.../chatwire-1.15.0.tar.gz"
  sha256 "abc123..."  # from PyPI

  # ...
end

Get the SHA256 from the PyPI release page or via:

curl -sL https://pypi.org/pypi/chatwire/1.15.0/json | python3 -c "
import json, sys
data = json.load(sys.stdin)
for f in data['urls']:
    if f['packagetype'] == 'sdist':
        print(f['digests']['sha256'])
"

BUILD_ID vs RELEASE_VERSION

Two version-shaped values exist at runtime:

Name Value Purpose
RELEASE_VERSION Semver from _version.py Update-check banner, /version endpoint
BUILD_ID Git commit hash (or mtime) Static asset cache-busting (?v=abc1234)

BUILD_ID changes on every commit so cached CSS/JS files are invalidated without changing the semver. This matters for users who access chatwire via a reverse proxy that aggressively caches.

Frontend builds

The React frontend must be built before release — the wheel includes the compiled output:

cd web/frontend
npm run build
# Output: web/frontend/dist/ (copied to web/static/ by the build script)

The python -m build step packages whatever is in web/frontend/dist/. Always build the frontend before building the wheel.

Plugin releases

Built-in plugins (chatwire-ntfy, chatwire-telegram, etc.) have their own version numbers and PyPI release cadences. They don't need to release in sync with chatwire core, but:

  • They should declare chatwire>=<min-version> in their dependencies to avoid compatibility issues
  • Breaking changes to BridgeContext or BaseIntegration require a chatwire major version bump with a migration guide for plugin authors

Deprecation policy

  • Features deprecated in minor version 1.N.0 are removed in 1.(N+2).0 — giving users at least one minor release to migrate
  • The legacy htmx UI (/?legacy=1) follows this policy
  • Plugin API changes follow this policy

Mobile app releases

The React Native app (packages/mobile/) is released separately:

  • Android APK — built with EAS Build (eas build --platform android --profile production) and attached to the GitHub release
  • iOS — TestFlight distribution (requires Apple Developer account); production App Store submission is future work

Mobile builds don't need to match the web/bridge version exactly. The mobile app's minimum server version is noted in packages/mobile/app.json.