From 7be6cabef565256d693674ee8cc70f57ba287ef7 Mon Sep 17 00:00:00 2001 From: Vecipher Date: Thu, 9 Oct 2025 19:47:43 +0800 Subject: [PATCH 1/2] packaging: ship prebuilt viewer in wheels/sdists; docs; extra [frontend] --- MANIFEST.in | 8 +++++++- docs/m8/packaging_cli.md | 31 +++++++++++++++++++++++++++++++ pyproject.toml | 5 ++++- 3 files changed, 42 insertions(+), 2 deletions(-) diff --git a/MANIFEST.in b/MANIFEST.in index 0d2c433..f4e36c5 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -21,6 +21,12 @@ recursive-include man *.1 # Docs (ship minimal markdown in sdist; wheels omit by default) recursive-include docs *.md +# Frontend viewer: ship prebuilt static assets in sdists +recursive-include clematis/frontend/dist * + +# Do not ship repo-level frontend sources (TS/Node dev tree) +prune frontend + # Do not ship repo-level examples/fixtures in sdist prune examples prune fixtures @@ -38,4 +44,4 @@ prune dist prune build # Global ignores -global-exclude __pycache__ *.py[cod] *.pyo *.so .DS_Store +global-exclude __pycache__ *.py[cod] *.pyo *.so .DS_Store *.map diff --git a/docs/m8/packaging_cli.md b/docs/m8/packaging_cli.md index 87702a9..1c0593b 100644 --- a/docs/m8/packaging_cli.md +++ b/docs/m8/packaging_cli.md @@ -49,6 +49,35 @@ Wrappers locate these via `importlib.resources`. See implementation helpers: - `clematis/cli/_resources.py` - `clematis/cli/_wrapper_common.py` → `inject_default_from_packaged_or_cwd(...)` +### Frontend assets (viewer) +The **offline viewer** is prebuilt and shipped in both sdists and wheels under: + +``` +/clematis/frontend/dist/ +``` + +You do **not** need Node/TypeScript to use it. Maintainers who want to rebuild can opt into the optional extra: + +```bash +python -m pip install "clematis[frontend]" +# then in a repo checkout: +npm ci --prefix frontend && npm run --prefix frontend build && make frontend-build +``` + +**Post-install smoke (offline viewer)** + +```bash +# Produce a small bundle +python -m clematis export-logs -- --out run_bundle.json + +# Print the viewer entrypoint path +python - <<'PY' +from importlib.resources import files +print(files("clematis").joinpath("frontend/dist/index.html")) +PY +# Open the printed file:// path in your browser and drag-drop run_bundle.json +``` + --- ## CLI invariants (guardrails) @@ -284,10 +313,12 @@ Some features are optional and installed via extras: | `zstd` | zstandard | .zst helpers/tests | | `lancedb` | lancedb | LanceDB import smoke | | `dev` | test+linters | Local dev setup (`.[dev]`) | +| `frontend`| nodeenv | Maintainer-only viewer rebuild tools | Install examples: ```bash python -m pip install 'clematis[zstd]' python -m pip install 'clematis[lancedb]' python -m pip install 'clematis[dev]' +python -m pip install 'clematis[frontend]' ``` diff --git a/pyproject.toml b/pyproject.toml index 1a97d62..fa24d76 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -74,6 +74,9 @@ cli-demo = [ 'numpy>=2.1; python_version >= "3.13"', "pyyaml>=6.0" ] +frontend = [ + "nodeenv>=1.8.0" +] [build-system] requires = ["setuptools>=77", "wheel"] @@ -100,7 +103,7 @@ markers = [ ] [tool.setuptools.package-data] -clematis = ["fixtures/**/*", "examples/**/*", "VERSION"] +clematis = ["fixtures/**/*", "examples/**/*", "VERSION", "frontend/dist/**"] [tool.setuptools.data-files] "share/man/man1" = ["man/*.1"] From d6bd1a55b303365cee958d7ff9e6e590e78bc31a Mon Sep 17 00:00:00 2001 From: Vecipher Date: Thu, 9 Oct 2025 20:20:50 +0800 Subject: [PATCH 2/2] wtf :sob: --- .github/workflows/ci.yml | 84 ++++++++++++++++++++++++++++++++++ clematis/VERSION | 2 +- pyproject.toml | 2 +- tests/cli/goldens/help/top.txt | 2 +- 4 files changed, 87 insertions(+), 3 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index af34a44..3064e30 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -321,6 +321,40 @@ jobs: run: | git clean -xfd + - name: Prepare frontend assets for packaging (build #1) + run: | + set -euo pipefail + python - <<'PY' + import os, shutil, pathlib + sde = int(os.environ.get("SOURCE_DATE_EPOCH","315532800")) + src_dist = pathlib.Path("frontend/dist") + src_tsdist = pathlib.Path("frontend/tsdist/assets") + src_index = pathlib.Path("frontend/index.html") + dst = pathlib.Path("clematis/frontend/dist") + + def reset_dst(): + if dst.exists(): + shutil.rmtree(dst) + dst.mkdir(parents=True, exist_ok=True) + + if src_dist.exists() and (src_dist/"index.html").exists(): + reset_dst() + shutil.copytree(src_dist, dst, dirs_exist_ok=True) + mode = "copied prebuilt frontend/dist" + elif src_index.exists() and src_tsdist.exists(): + reset_dst() + shutil.copy2(src_index, dst/"index.html") + shutil.copytree(src_tsdist, dst/"assets", dirs_exist_ok=True) + mode = "assembled from tsdist/assets" + else: + raise SystemExit("No frontend assets found (frontend/dist or tsdist). Ensure compiled assets are committed.") + + for p in dst.rglob("*"): + if p.is_file(): + os.utime(p, (sde, sde)) + print(f"Prepared {dst} ({mode})") + PY + - name: Build #1 and record hashes run: | set -euo pipefail @@ -386,10 +420,60 @@ jobs: # wheel paths use the data-files target path unzip -l dist/*.whl | grep -E 'share/examples/clematis/gel/(enabled|disabled)\.yaml' + - name: Assert viewer included in wheel + run: | + set -euo pipefail + echo "Verify viewer assets are present in wheel" + unzip -l dist/*.whl | grep -F 'clematis/frontend/dist/index.html' + # Print a few entries for easier debugging + unzip -l dist/*.whl | grep -F 'clematis/frontend/dist/' | head -n 20 + + - name: Assert viewer included in sdist + run: | + set -euo pipefail + echo "Verify viewer assets are present in sdist" + tar -tzf dist/*.tar.gz | grep -E '(^|/)clematis/frontend/dist/index.html$' + # Print a few entries for easier debugging + tar -tzf dist/*.tar.gz | grep -E '(^|/)clematis/frontend/dist/' | head -n 20 + - name: Clean tree (before build #2) run: | git clean -xfd + - name: Prepare frontend assets for packaging (build #2) + run: | + set -euo pipefail + python - <<'PY' + import os, shutil, pathlib + sde = int(os.environ.get("SOURCE_DATE_EPOCH","315532800")) + src_dist = pathlib.Path("frontend/dist") + src_tsdist = pathlib.Path("frontend/tsdist/assets") + src_index = pathlib.Path("frontend/index.html") + dst = pathlib.Path("clematis/frontend/dist") + + def reset_dst(): + if dst.exists(): + shutil.rmtree(dst) + dst.mkdir(parents=True, exist_ok=True) + + if src_dist.exists() and (src_dist/"index.html").exists(): + reset_dst() + shutil.copytree(src_dist, dst, dirs_exist_ok=True) + mode = "copied prebuilt frontend/dist" + elif src_index.exists() and src_tsdist.exists(): + reset_dst() + shutil.copy2(src_index, dst/"index.html") + shutil.copytree(src_tsdist, dst/"assets", dirs_exist_ok=True) + mode = "assembled from tsdist/assets" + else: + raise SystemExit("No frontend assets found (frontend/dist or tsdist). Ensure compiled assets are committed.") + + for p in dst.rglob("*"): + if p.is_file(): + os.utime(p, (sde, sde)) + print(f"Prepared {dst} ({mode})") + PY + - name: Build #2 and record hashes (same SDE) run: | set -euo pipefail diff --git a/clematis/VERSION b/clematis/VERSION index 78bc1ab..5eef0f1 100644 --- a/clematis/VERSION +++ b/clematis/VERSION @@ -1 +1 @@ -0.10.0 +0.10.2 diff --git a/pyproject.toml b/pyproject.toml index fa24d76..56f23d7 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "clematis" -version = "0.10.0" +version = "0.10.2" description = "Clematis — deterministic mind-like simulation engine (M1–M8 baseline)" readme = "README.md" requires-python = ">=3.11,<3.14" diff --git a/tests/cli/goldens/help/top.txt b/tests/cli/goldens/help/top.txt index 1500b85..0555296 100644 --- a/tests/cli/goldens/help/top.txt +++ b/tests/cli/goldens/help/top.txt @@ -1,6 +1,6 @@ usage: clematis [-h] [--version] -Clematis umbrella CLI (v0.10.0) +Clematis umbrella CLI (v0.10.2) options: -h, --help show this help message and exit