From d0f54166247815b8aa4466f40af13ae8eca5d73c Mon Sep 17 00:00:00 2001 From: Hopping Mad Games Date: Wed, 26 Aug 2026 10:52:07 -0600 Subject: [PATCH] Require Manager clone gate before Registry publish --- .gitea/workflows/release-install.yml | 25 ---------- CHANGELOG.md | 11 +++-- README.md | 15 +++++- pyproject.toml | 5 +- tests/test_publish_release.py | 58 ++++++++++++++++++++++++ tools/__init__.py | 1 + tools/publish_registry_release.py | 68 ++++++++++++++++++++++++++++ 7 files changed, 152 insertions(+), 31 deletions(-) delete mode 100644 .gitea/workflows/release-install.yml create mode 100644 tests/test_publish_release.py create mode 100644 tools/__init__.py create mode 100755 tools/publish_registry_release.py diff --git a/.gitea/workflows/release-install.yml b/.gitea/workflows/release-install.yml deleted file mode 100644 index 12a2f42..0000000 --- a/.gitea/workflows/release-install.yml +++ /dev/null @@ -1,25 +0,0 @@ -name: Verify Manager release install - -on: - push: - tags: - - "v*" - -jobs: - verify-manager-install: - runs-on: ubuntu-latest - steps: - - name: Check out release tag - uses: actions/checkout@v4 - with: - fetch-depth: 0 - - name: Set up Python - uses: actions/setup-python@v5 - with: - python-version: "3.12" - - name: Set up Node.js - uses: actions/setup-node@v4 - with: - node-version: "22" - - name: Clone and validate the declared public repository - run: python tools/verify_manager_install.py --source . diff --git a/CHANGELOG.md b/CHANGELOG.md index d0aa46d..f8fa799 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,11 +2,16 @@ All notable changes to this project are documented here. +## 1.0.3 - 2026-08-26 + +- Route Registry publication through the Manager installation gate and keep + the API key out of process arguments and environment variables. + ## 1.0.2 - 2026-08-26 -- Add a credential-free ComfyUI Manager clone gate and tag-triggered workflow - so Registry releases fail when their declared public repository is missing, - inaccessible, stale, or inconsistent with the release commit and version. +- Add a credential-free ComfyUI Manager clone gate so Registry releases fail + when their declared public repository is missing, inaccessible, stale, or + inconsistent with the release commit and version. ## 1.0.1 - 2026-08-26 diff --git a/README.md b/README.md index 9a8b845..4f8ddeb 100644 --- a/README.md +++ b/README.md @@ -57,8 +57,19 @@ python3 tools/verify_manager_install.py --source . The gate reads the public repository from `pyproject.toml`, disables ambient Git credentials, performs Manager's recursive clone into a clean `custom_nodes` directory, verifies the release commit and version tag, and -compiles every shipped Python and JavaScript source from the clone. The Gitea -tag workflow runs the same command before a release is considered installable. +compiles every shipped Python and JavaScript source from the clone. + +Registry publishing is supported only through the gated release command: + +```bash +python3 -m tools.publish_registry_release \ + --changelog "Describe this release" +``` + +It runs the Manager installation gate and Registry validation before invoking +publication. The API key is read interactively and sent to `comfy` over +standard input; it is never placed in process arguments or environment +variables. Direct `comfy node publish` calls bypass the required release gate. ## License diff --git a/pyproject.toml b/pyproject.toml index b3733cc..6287f39 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "etk-ltxv-timeline-editor" -version = "1.0.2" +version = "1.0.3" description = "A visual timeline editor for LTXV image guides, prompts, strengths, and frame positions in ComfyUI." license = { file = "LICENSE" } requires-python = ">=3.10" @@ -20,3 +20,6 @@ Documentation = "https://git.hoppingmadgames.com/hmg-comfy/ComfyUI_ETK_LTXV_Time PublisherId = "hmg" DisplayName = "ETK LTXV Timeline Image Editor" requires-comfyui = ">=0.33.0" + +[tool.ruff.lint.per-file-ignores] +"tools/__init__.py" = ["N999"] diff --git a/tests/test_publish_release.py b/tests/test_publish_release.py new file mode 100644 index 0000000..2b2a52b --- /dev/null +++ b/tests/test_publish_release.py @@ -0,0 +1,58 @@ +from pathlib import Path + +import pytest + +from tools import publish_registry_release + + +def test_publish_stops_before_registry_when_manager_gate_fails(monkeypatch, tmp_path): + calls = [] + + def fail_gate(source): + raise RuntimeError("public clone failed") + + monkeypatch.setattr(publish_registry_release, "verify_manager_install", fail_gate) + monkeypatch.setattr( + publish_registry_release.subprocess, + "run", + lambda *args, **kwargs: calls.append((args, kwargs)), + ) + + with pytest.raises(RuntimeError, match="public clone failed"): + publish_registry_release.publish_release( + tmp_path, + comfy="comfy", + token="private-token", + changelog="release", + ) + assert calls == [] + + +def test_publish_keeps_token_out_of_arguments_and_environment(monkeypatch, tmp_path): + calls = [] + monkeypatch.setattr( + publish_registry_release, + "verify_manager_install", + lambda source: ("1.0.3", "https://example.test/owner/repo", "abc123"), + ) + + def record_run(command, **kwargs): + calls.append((command, kwargs)) + + monkeypatch.setattr(publish_registry_release.subprocess, "run", record_run) + publish_registry_release.publish_release( + Path(tmp_path), + comfy="comfy", + token="private-token", + changelog="release notes", + ) + + assert [command for command, _ in calls] == [ + ["comfy", "node", "validate"], + ["comfy", "node", "publish"], + ] + publish_kwargs = calls[1][1] + assert publish_kwargs["input"] == "private-token\n" + assert "private-token" not in repr(calls[1][0]) + assert "private-token" not in repr(publish_kwargs["env"]) + assert publish_kwargs["env"]["COMFY_NODE_CHANGELOG"] == "release notes" diff --git a/tools/__init__.py b/tools/__init__.py new file mode 100644 index 0000000..f6f9fa8 --- /dev/null +++ b/tools/__init__.py @@ -0,0 +1 @@ +"""Release tooling for the standalone timeline node pack.""" diff --git a/tools/publish_registry_release.py b/tools/publish_registry_release.py new file mode 100755 index 0000000..bd240e0 --- /dev/null +++ b/tools/publish_registry_release.py @@ -0,0 +1,68 @@ +#!/usr/bin/env python3 +"""Publish only after the public Manager installation path is verified.""" + +from __future__ import annotations + +import argparse +import getpass +import os +import shutil +import subprocess +import sys +from pathlib import Path + +from tools.verify_manager_install import verify_manager_install + + +def publish_release( + source: Path, + *, + comfy: str, + token: str, + changelog: str, +) -> None: + verify_manager_install(source) + subprocess.run([comfy, "node", "validate"], cwd=source, check=True) + env = dict(os.environ) + env["COMFY_NODE_CHANGELOG"] = changelog + subprocess.run( + [comfy, "node", "publish"], + cwd=source, + env=env, + input=f"{token}\n", + text=True, + check=True, + ) + + +def main() -> int: + parser = argparse.ArgumentParser() + parser.add_argument( + "--source", + type=Path, + default=Path(__file__).resolve().parents[1], + help="tagged release repository", + ) + parser.add_argument("--changelog", required=True) + parser.add_argument("--comfy", default=shutil.which("comfy") or "comfy") + args = parser.parse_args() + token = getpass.getpass("Comfy Registry API key: ").strip() + if not token: + print("REGISTRY RELEASE FAILED: API key is empty", file=sys.stderr) + return 1 + try: + publish_release( + args.source.resolve(), + comfy=args.comfy, + token=token, + changelog=args.changelog, + ) + except (OSError, subprocess.CalledProcessError, RuntimeError) as exc: + print(f"REGISTRY RELEASE FAILED: {exc}", file=sys.stderr) + return 1 + print("REGISTRY RELEASE PASSED THE MANAGER INSTALL GATE AND WAS UPLOADED") + return 0 + + +if __name__ == "__main__": + raise SystemExit(main())