Files
cimtechniques-service-suite/cim_suite/__init__.py
Andy 63169a7644 feat: add versioning and changelog system
Conventional-Commit-driven version bumps (scripts/bump.py), a commit-msg validation hook, and a /release-notes workflow that produces a human-reviewed CHANGELOG.md. Adds an in-app version badge + 'What's new' dialog on the launcher. The version is single-sourced from pyproject.toml (cim_suite.__version__), and the deterministic bump backbone lives in scripts/release/ with tests in tests/release/. Marks the 1.0.0 baseline.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-06 11:31:22 -04:00

65 lines
2.3 KiB
Python

"""CIMTechniques Service Suite — unified desktop suite of instrument service tools."""
from __future__ import annotations
import sys
from pathlib import Path
def _candidate_pyprojects() -> list[Path]:
"""Places pyproject.toml may live, in priority order (frozen bundle, then source)."""
candidates = []
meipass = getattr(sys, "_MEIPASS", None)
if meipass is not None:
candidates.append(Path(meipass) / "pyproject.toml")
candidates.append(Path(__file__).resolve().parent.parent / "pyproject.toml")
return candidates
def _detect_version() -> str:
"""Resolve the suite version from its single source of truth, ``pyproject.toml``.
A source checkout (incl. editable installs) reads the live file, so a
``scripts/bump.py`` edit shows up immediately with no reinstall; the frozen build
bundles the same file (see packaging/suite.spec). A plain wheel install with no
pyproject alongside the package falls back to the baked-in distribution metadata.
"""
for pyproject in _candidate_pyprojects():
if pyproject.is_file():
try:
import tomllib
return tomllib.loads(pyproject.read_text("utf-8"))["project"]["version"]
except Exception: # pragma: no cover - malformed pyproject falls through
pass
try:
from importlib.metadata import PackageNotFoundError, version
return version("cim-service-suite")
except PackageNotFoundError: # pragma: no cover - not installed and no pyproject
return "0.0.0"
__version__ = _detect_version()
def changelog_path() -> Path | None:
"""Locate the bundled ``CHANGELOG.md`` (repo root in source, bundle dir when frozen)."""
candidates = []
meipass = getattr(sys, "_MEIPASS", None)
if meipass is not None:
candidates.append(Path(meipass) / "CHANGELOG.md")
candidates.append(Path(__file__).resolve().parent.parent / "CHANGELOG.md")
for path in candidates:
if path.is_file():
return path
return None
def changelog_markdown() -> str:
"""The release notes as markdown, or a friendly placeholder if none is bundled."""
path = changelog_path()
if path is None:
return "# Changelog\n\nNo release notes are available in this build."
return path.read_text("utf-8")