Require Manager clone gate before Registry publish

This commit is contained in:
Hopping Mad Games
2026-08-26 10:52:07 -06:00
parent 1c74fcc81f
commit 2e7c1f14e5
7 changed files with 152 additions and 31 deletions
-25
View File
@@ -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 .
+8 -3
View File
@@ -2,11 +2,16 @@
All notable changes to this project are documented here. 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 ## 1.0.2 - 2026-08-26
- Add a credential-free ComfyUI Manager clone gate and tag-triggered workflow - Add a credential-free ComfyUI Manager clone gate so Registry releases fail
so Registry releases fail when their declared public repository is missing, when their declared public repository is missing, inaccessible, stale, or
inaccessible, stale, or inconsistent with the release commit and version. inconsistent with the release commit and version.
## 1.0.1 - 2026-08-26 ## 1.0.1 - 2026-08-26
+13 -2
View File
@@ -57,8 +57,19 @@ python3 tools/verify_manager_install.py --source .
The gate reads the public repository from `pyproject.toml`, disables ambient The gate reads the public repository from `pyproject.toml`, disables ambient
Git credentials, performs Manager's recursive clone into a clean Git credentials, performs Manager's recursive clone into a clean
`custom_nodes` directory, verifies the release commit and version tag, and `custom_nodes` directory, verifies the release commit and version tag, and
compiles every shipped Python and JavaScript source from the clone. The Gitea compiles every shipped Python and JavaScript source from the clone.
tag workflow runs the same command before a release is considered installable.
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 ## License
+4 -1
View File
@@ -1,6 +1,6 @@
[project] [project]
name = "etk-ltxv-timeline-editor" 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." description = "A visual timeline editor for LTXV image guides, prompts, strengths, and frame positions in ComfyUI."
license = { file = "LICENSE" } license = { file = "LICENSE" }
requires-python = ">=3.10" requires-python = ">=3.10"
@@ -20,3 +20,6 @@ Documentation = "https://git.hoppingmadgames.com/hmg-comfy/ComfyUI_ETK_LTXV_Time
PublisherId = "hmg" PublisherId = "hmg"
DisplayName = "ETK LTXV Timeline Image Editor" DisplayName = "ETK LTXV Timeline Image Editor"
requires-comfyui = ">=0.33.0" requires-comfyui = ">=0.33.0"
[tool.ruff.lint.per-file-ignores]
"tools/__init__.py" = ["N999"]
+58
View File
@@ -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"
+1
View File
@@ -0,0 +1 @@
"""Release tooling for the standalone timeline node pack."""
+68
View File
@@ -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())