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:
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.0 → 1.14.1) — bug fixes, no new features, no breaking changes
- Minor (1.14.0 → 1.15.0) — new features, backward-compatible
- Major (1.x.x → 2.0.0) — breaking changes (config schema, API, plugin SDK)
PyPI release¶
1. Bump the version¶
Edit _version.py:
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¶
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¶
Requires PyPI credentials. Use twine upload --repository testpypi to test on TestPyPI first.
7. Create the GitHub release¶
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
BridgeContextorBaseIntegrationrequire a chatwire major version bump with a migration guide for plugin authors
Deprecation policy¶
- Features deprecated in minor version
1.N.0are removed in1.(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.