diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index e12fad4..36bd57a 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -4,16 +4,67 @@ on: types: - published +permissions: + contents: read + +concurrency: + group: release-${{ github.event.release.tag_name }} + cancel-in-progress: false + jobs: publish: + if: ${{ !github.event.release.draft && !github.event.release.prerelease }} runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 with: submodules: recursive + fetch-depth: 0 - uses: actions/setup-python@v5 with: python-version: "3.13" + - name: Verify release target + env: + RELEASE_TAG: ${{ github.event.release.tag_name }} + run: | + python - <<'PY' + import os + import subprocess + import sys + import tomllib + + with open("pyproject.toml", "rb") as pyproject: + version = tomllib.load(pyproject)["project"]["version"] + + release_tag = os.environ["RELEASE_TAG"] + expected_tag = f"v{version}" + if release_tag != expected_tag: + print( + f"::error::Release tag {release_tag!r} does not match " + f"pyproject.toml version {version!r}; expected {expected_tag!r}." + ) + sys.exit(1) + + def git(*args: str) -> str: + return subprocess.check_output(("git", *args), text=True).strip() + + release_commit = git("rev-list", "-n", "1", release_tag) + master_commit = git("rev-parse", "origin/master") + if release_commit != master_commit: + print( + f"::error::Release tag {release_tag!r} points at " + f"{release_commit}, but origin/master is {master_commit}." + ) + sys.exit(1) + PY + - name: Check PyPI token + env: + POETRY_PYPI_TOKEN_PYPI: ${{ secrets.PYPI_TOKEN }} + run: | + if [ -z "$POETRY_PYPI_TOKEN_PYPI" ]; then + echo "::error::PYPI_TOKEN secret is not configured or is empty." + exit 1 + fi - name: Install Poetry run: python -m pip install "poetry>=2.0,<3.0" - name: Install dependencies diff --git a/AGENTS.md b/AGENTS.md index b36a55f..f2b53b4 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -11,7 +11,7 @@ run-to-completion execution core. - License: MIT - Python: 3.13+ - Runtime dependencies: none -- Current status: alpha, version `0.7.0` (release-ready; PyPI publish pending) +- Current status: alpha, version `0.7.0` The central bet is simple: charts designed in Stately or shared with a JavaScript frontend should load directly as Python `dict` / JSON data, with @@ -87,8 +87,6 @@ Working: Known gaps: - Full SCXML conformance still has known `more-parallel` failures. -- Publishing `0.7.0` to PyPI is still a release task after the merged - release-readiness work. - Graphviz export and graph/test helper APIs beyond Mermaid are future work. - SCXML condition support is intentionally not a general JavaScript evaluator. diff --git a/CHANGELOG.md b/CHANGELOG.md index e7f6bfc..14bf45a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,7 +2,7 @@ All notable changes to this project will be documented here. -## 0.7.0 - 2026-07-01 +## 0.7.0 - 2026-07-02 ### Added @@ -16,6 +16,14 @@ All notable changes to this project will be documented here. - Added dependency-free Mermaid `stateDiagram-v2` export with `to_mermaid(machine)`. +### Changed + +- Hardened the GitHub Release publish workflow so the release tag must match the + package version, point at `origin/master`, and have a configured PyPI token + before upload. +- Updated PyPI-facing installation docs to use `pip install xstate` for the + released package. + ## 0.6.0 - 2026-06-28 ### Added diff --git a/CLAUDE.md b/CLAUDE.md index 4edc8c5..c4d1f80 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -16,30 +16,27 @@ the niche of native XState JSON compatibility. ``` src/xstate/ __init__.py Public API: `from xstate import Machine` - machine.py Machine class — entry point and pure transition API + machine.py Core Machine class and transition entry point config_parser.py Two-pass XState config parser and normalizer - schema.py TypedDict boundary for raw XState config data + schema.py TypedDict schemas for Machine, StateNode, Transition, and Invoke configs state_node.py Resolved state hierarchy model + transition.py Resolved transition model state.py Public State / MachineSnapshot snapshots algorithm.py SCXML execution engine (microstep/macrostep, entry/exit sets) action.py Action constructors and higher-order action helpers - transition.py Resolved transition model - context.py ContextAdapter policies, including dataclass contexts event.py Event wrapper and conversion helpers + exceptions.py Custom exception classes handlers.py HandlerArgs and HandlerAdapter callable adaptation guards.py Composable guards and state_in/stateIn helpers - snapshot.py Snapshot serialization and restoration helpers - mermaid.py Dependency-free Mermaid diagram export + context.py ContextAdapter policies, including dataclass contexts + interpreter.py Synchronous runtime, queue, subscriptions, timers + async_interpreter.py Asyncio runtime and awaitable action execution actor.py Actor model, create_actor, ActorSystem, actor logic helpers + snapshot.py Snapshot serialization and restoration helpers scheduler.py Clock abstractions + mermaid.py Dependency-free Mermaid diagram export scxml.py SCXML XML → Machine config converter with safe Boolean conds setup_api.py setup(...).create_machine(...) strict API - machine.py Core Machine class and transition entry point - interpreter.py Synchronous runtime, queue, subscriptions, timers - async_interpreter.py Asyncio runtime and awaitable action execution - config_parser.py Parser for StateNode configurations - schema.py TypedDict schemas for Machine, StateNode, Transition, and Invoke configs - exceptions.py Custom exception classes ``` `algorithm.py` is the heart of the library. It implements the W3C SCXML algorithm: @@ -49,7 +46,7 @@ The critical execution order is: `main_event_loop` → `microstep` → `main_eve --- -## Current state (0.7.0 release-ready on master) +## Current state (0.7.0 on master) **Working:** - Hierarchical (compound) states @@ -228,7 +225,7 @@ poetry run python -m pytest tests/test_scxml.py -k cond-js poetry run mypy src/xstate/ # Format + lint -poetry run ruff format src/ tests/ +poetry run ruff format --check src/ tests/ poetry run ruff check src/ tests/ ``` diff --git a/README.md b/README.md index 360c9df..81647f5 100644 --- a/README.md +++ b/README.md @@ -50,8 +50,13 @@ functions, delay values, and actor logic. ## Installation -The `0.7.0` package metadata is ready for release. Until the GitHub release -publishes to PyPI, install from source: +Install the released package from PyPI: + +```bash +pip install xstate +``` + +For an unreleased checkout, install from source: ```bash git clone https://github.com/JovaniPink/xstate-python.git