diff --git a/README.md b/README.md index 58a7e27..660dd18 100644 --- a/README.md +++ b/README.md @@ -21,8 +21,18 @@ replacement or rewiring. ## Install -Clone the public repository into ComfyUI's `custom_nodes` directory and restart -ComfyUI: +Install the immutable Registry release with Comfy CLI: + +```bash +comfy node install etk-from-list-index@1.0.0 +``` + +New Registry releases have a short pending security-scan window. During that +window the exact version remains installable, but the `@latest` alias is not +added to ComfyUI-Manager's active CNR index yet. + +Alternatively, clone the public repository into ComfyUI's `custom_nodes` +directory and restart ComfyUI: ```bash git clone https://git.hoppingmadgames.com/hmg-comfy/ComfyUI_ETK_From_List_Index.git diff --git a/tests/test_publish_release.py b/tests/test_publish_release.py index 2b2a52b..b0cc8c7 100644 --- a/tests/test_publish_release.py +++ b/tests/test_publish_release.py @@ -1,3 +1,4 @@ +from io import BytesIO from pathlib import Path import pytest @@ -56,3 +57,47 @@ def test_publish_keeps_token_out_of_arguments_and_environment(monkeypatch, tmp_p 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" + + +def test_registry_release_status_finds_exact_published_version(monkeypatch, tmp_path): + (tmp_path / "pyproject.toml").write_text( + '[project]\nname = "etk-from-list-index"\nversion = "1.0.0"\n', + encoding="utf-8", + ) + + class Response(BytesIO): + def __enter__(self): + return self + + def __exit__(self, *args): + self.close() + + payload = b'{"versions":[{"version":"1.0.0","status":"NodeVersionStatusPending"}]}' + monkeypatch.setattr( + publish_registry_release.urllib.request, + "urlopen", + lambda *args, **kwargs: Response(payload), + ) + + assert publish_registry_release.registry_release_status(tmp_path) == ( + "etk-from-list-index", + "1.0.0", + "NodeVersionStatusPending", + ) + + +def test_pending_registry_status_does_not_claim_latest_is_ready( + monkeypatch, capsys, tmp_path +): + monkeypatch.setattr( + publish_registry_release, + "registry_release_status", + lambda source: ("etk-from-list-index", "1.0.0", "NodeVersionStatusPending"), + ) + + publish_registry_release.print_registry_install_status(tmp_path) + + output = capsys.readouterr().out + assert "comfy node install etk-from-list-index@1.0.0" in output + assert "unavailable until" in output + assert "etk-from-list-index@latest" not in output diff --git a/tools/publish_registry_release.py b/tools/publish_registry_release.py index bd240e0..d50a33c 100755 --- a/tools/publish_registry_release.py +++ b/tools/publish_registry_release.py @@ -5,15 +5,22 @@ from __future__ import annotations import argparse import getpass +import json import os import shutil import subprocess import sys +import tomllib +import urllib.parse +import urllib.request from pathlib import Path from tools.verify_manager_install import verify_manager_install +REGISTRY_VERSIONS_URL = "https://api.comfy.org/versions" + + def publish_release( source: Path, *, @@ -35,6 +42,41 @@ def publish_release( ) +def registry_release_status(source: Path) -> tuple[str, str, str]: + with (source / "pyproject.toml").open("rb") as stream: + project = tomllib.load(stream)["project"] + node_id = str(project["name"]) + version = str(project["version"]) + query = urllib.parse.urlencode( + { + "nodeId": node_id, + "include_status_reason": "true", + "pageSize": "100", + } + ) + with urllib.request.urlopen( + f"{REGISTRY_VERSIONS_URL}?{query}", timeout=30 + ) as response: + versions = json.load(response)["versions"] + release = next((item for item in versions if item.get("version") == version), None) + if release is None: + raise RuntimeError(f"Registry upload is not visible: {node_id}@{version}") + return node_id, version, str(release.get("status", "unknown")) + + +def print_registry_install_status(source: Path) -> None: + node_id, version, status = registry_release_status(source) + print(f"REGISTRY VERSION STATUS: {node_id}@{version} {status}") + print(f"EXACT CNR INSTALL: comfy node install {node_id}@{version}") + if status != "NodeVersionStatusActive": + print( + "LATEST CNR ALIAS: unavailable until the Registry security scan " + "activates this version" + ) + else: + print(f"LATEST CNR INSTALL: comfy node install {node_id}@latest") + + def main() -> int: parser = argparse.ArgumentParser() parser.add_argument( @@ -57,6 +99,7 @@ def main() -> int: token=token, changelog=args.changelog, ) + print_registry_install_status(args.source.resolve()) except (OSError, subprocess.CalledProcessError, RuntimeError) as exc: print(f"REGISTRY RELEASE FAILED: {exc}", file=sys.stderr) return 1