diff --git a/comfy_cli/registry/config_parser.py b/comfy_cli/registry/config_parser.py index 60fc7746..36b443ef 100644 --- a/comfy_cli/registry/config_parser.py +++ b/comfy_cli/registry/config_parser.py @@ -6,6 +6,7 @@ import tomlkit import tomlkit.exceptions +import tomlkit.items import typer from comfy_cli import ui @@ -44,6 +45,55 @@ ) +# Uncommentable hint emitted below "[tool.comfy].includes". +_INCLUDES_HINT = """ +# "requires-comfyui" = ">=1.0.0" # ComfyUI version compatibility +""" + +# Uncommentable hint emitted below "[project].license", for OS/GPU support. +_CLASSIFIERS_HINT = """ +# classifiers = [ +# # For OS-independent nodes (works on all operating systems) +# "Operating System :: OS Independent", +# +# # OR for OS-specific nodes, specify the supported systems: +# "Operating System :: Microsoft :: Windows", # Windows specific +# "Operating System :: POSIX :: Linux", # Linux specific +# "Operating System :: MacOS", # macOS specific +# +# # GPU Accelerator support. Pick the ones that are supported by your extension. +# "Environment :: GPU :: NVIDIA CUDA", # NVIDIA CUDA support +# "Environment :: GPU :: AMD ROCm", # AMD ROCm support +# "Environment :: GPU :: Intel Arc", # Intel Arc support +# "Environment :: NPU :: Huawei Ascend", # Huawei Ascend support +# "Environment :: GPU :: Apple Metal", # Apple Metal support +# ] +""" + + +def _add_commented_hint(table: tomlkit.items.Table, hint: str) -> None: + """Append `hint` to `table` as standalone comment lines. + + tomlkit's `Item.comment()` takes a single line by contract — it raises + `ValueError` on line breaks as of tomlkit 0.15.1 — and renders inline, which + would leave the hint impossible to uncomment in place. So emit one standalone + comment per line instead. `tomlkit.comment()` re-adds the `#` marker, hence + the strip. Hints must be added while `table` is still being built, before any + sub-table: a comment appended after one renders *inside* that sub-table. + """ + # `strip()` drops the blank lines the triple-quoted literal carries before and + # after its `#` body, so `splitlines()` doesn't emit stray leading/trailing `#`. + for line in hint.strip().splitlines(): + content = line.removeprefix("#").removeprefix(" ") + if content: + table.add(tomlkit.comment(content)) + else: + # A bare separator line: `tomlkit.comment("")` renders `"# "` with + # trailing whitespace (which the repo's trailing-whitespace pre-commit + # hook flags), so emit a bare `#` via Trivia instead. + table.add(tomlkit.items.Comment(tomlkit.items.Trivia(indent="", comment_ws="", comment="#", trail="\n"))) + + def create_comfynode_config(): # Create the initial structure of the TOML document document = tomlkit.document() @@ -55,6 +105,11 @@ def create_comfynode_config(): project["dependencies"] = tomlkit.aot() project["license"] = "MIT" + # Attach the classifiers hint below "license", before the "urls" sub-table is + # added — `initialize_project_config` reparses this file and only rewrites + # values, so this is the one place the hint can be positioned correctly. + _add_commented_hint(project, _CLASSIFIERS_HINT) + urls = tomlkit.table() urls["Repository"] = "" @@ -72,9 +127,7 @@ def create_comfynode_config(): comfy["includes"] = tomlkit.array() # Add uncommentable hint for ComfyUI version compatibility, below of "[tool.comfy].includes" field. - comfy["includes"].comment(""" -# "requires-comfyui" = ">=1.0.0" # ComfyUI version compatibility -""") + _add_commented_hint(comfy, _INCLUDES_HINT) tool.add("comfy", comfy) document.add("tool", tool) @@ -236,35 +289,9 @@ def initialize_project_config(): # Use PEP 639 SPDX license identifier project["license"] = "MIT" - # [project].classifiers Classifiers uncommentable hint for OS/GPU support - # Attach classifiers comments to the project, below of "license" field. - # will generate a comment like this: - # - # [project] - # ... - # license = "MIT" - # # classifiers = [ - # # # For OS-independent nodes (works on all operating systems) - # ... - - project["license"].comment(""" -# classifiers = [ -# # For OS-independent nodes (works on all operating systems) -# "Operating System :: OS Independent", -# -# # OR for OS-specific nodes, specify the supported systems: -# "Operating System :: Microsoft :: Windows", # Windows specific -# "Operating System :: POSIX :: Linux", # Linux specific -# "Operating System :: MacOS", # macOS specific -# -# # GPU Accelerator support. Pick the ones that are supported by your extension. -# "Environment :: GPU :: NVIDIA CUDA", # NVIDIA CUDA support -# "Environment :: GPU :: AMD ROCm", # AMD ROCm support -# "Environment :: GPU :: Intel Arc", # Intel Arc support -# "Environment :: NPU :: Huawei Ascend", # Huawei Ascend support -# "Environment :: GPU :: Apple Metal", # Apple Metal support -# ] -""") + # The [project].classifiers hint is emitted by `create_comfynode_config` above, + # which is the only place it can be positioned below "license" (see + # `_add_commented_hint`). Reassigning "license" here preserves it. tool = document.get("tool", tomlkit.table()) comfy = tool.get("comfy", tomlkit.table()) diff --git a/pyproject.toml b/pyproject.toml index 62b427e9..45ea593a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -50,7 +50,7 @@ dependencies = [ "rich", "ruff", "semver~=3.0.2", - "tomlkit", + "tomlkit>=0.13,<0.16", "typer>=0.12.5", "typing-extensions>=4.7", "uv>=0.11.15", diff --git a/tests/comfy_cli/registry/test_config_parser.py b/tests/comfy_cli/registry/test_config_parser.py index 07365d43..f3b84a75 100644 --- a/tests/comfy_cli/registry/test_config_parser.py +++ b/tests/comfy_cli/registry/test_config_parser.py @@ -5,7 +5,9 @@ import tomlkit from comfy_cli.registry.config_parser import ( + _add_commented_hint, _strip_url_credentials, + create_comfynode_config, extract_node_configuration, initialize_project_config, validate_and_extract_accelerator_classifiers, @@ -1066,3 +1068,70 @@ def test_initialize_project_config_vcs_with_inline_comment(tmp_path, monkeypatch data = tomlkit.parse(f.read()) deps = [str(d) for d in data["project"]["dependencies"]] assert deps == ["git+https://github.com/org/mono.git#subdirectory=pkg"] + + +# The `[tool.comfy]` version-compatibility hint is emitted as a standalone comment. +# tomlkit's `Item.comment()` is single-line by contract (it raises on line breaks as +# of 0.15.1) and renders inline, which would leave the hint un-uncommentable. + + +def test_create_comfynode_config_emits_uncommentable_version_hint(tmp_path, monkeypatch): + monkeypatch.chdir(tmp_path) + create_comfynode_config() + rendered = (tmp_path / "pyproject.toml").read_text() + + # The hint sits on its own line beneath `includes`, not trailing it inline. + assert '\nincludes = []\n# "requires-comfyui" = ">=1.0.0" # ComfyUI version compatibility\n' in rendered + + # It is inert until uncommented... + assert "requires-comfyui" not in tomlkit.parse(rendered)["tool"]["comfy"] + + # ...and uncommenting it in place yields a valid key. + uncommented = rendered.replace('# "requires-comfyui"', '"requires-comfyui"', 1) + assert tomlkit.parse(uncommented)["tool"]["comfy"]["requires-comfyui"] == ">=1.0.0" + + +def test_initialize_project_config_emits_uncommentable_classifiers_hint(tmp_path, monkeypatch): + monkeypatch.chdir(tmp_path) + _init_git_repo_with_reqs(tmp_path, "") + initialize_project_config() + rendered = (tmp_path / "pyproject.toml").read_text() + + # The hint sits under `license`, and critically *before* the `[project.urls]` + # sub-table — appending it afterwards would scope `classifiers` to the wrong table. + assert '\nlicense = "MIT"\n# classifiers = [\n' in rendered + assert rendered.index("# classifiers = [") < rendered.index("[project.urls]") + assert '# "Operating System :: OS Independent",\n' in rendered + assert "\n# ]\n" in rendered + + # Blank separator lines render as a bare `#`, not `"# "` — no trailing + # whitespace (which the repo's trailing-whitespace pre-commit hook flags). + assert "\n#\n" in rendered + assert not any(line == "# " for line in rendered.splitlines()) + + # It is inert until uncommented... + assert "classifiers" not in tomlkit.parse(rendered)["project"] + + # ...and uncommenting the block in place yields a valid `[project].classifiers`. + hint_start = rendered.index("# classifiers = [") + hint_end = rendered.index("# ]", hint_start) + len("# ]") + block = rendered[hint_start:hint_end] + uncommented = "\n".join(line[1:].lstrip(" ") if line.strip() != "#" else "" for line in block.splitlines()) + parsed = tomlkit.parse(rendered[:hint_start] + uncommented + rendered[hint_end:]) + assert "Operating System :: OS Independent" in parsed["project"]["classifiers"] + assert "Environment :: GPU :: Apple Metal" in parsed["project"]["classifiers"] + + +def test_add_commented_hint_strips_surrounding_whitespace(): + # The triple-quoted hint literals carry blank lines around their `#` body; a + # leading blank surviving into `splitlines()` would emit a stray `#` comment + # above the hint. `strip()` handles that (and any stray whitespace such as a + # `\r`). Feed a hint padded with blank/CR-terminated lines and assert no bare + # `#` line leaks through. + table = tomlkit.table() + _add_commented_hint(table, '\r\n# "requires-comfyui" = ">=1.0.0" # note\r\n') + rendered = tomlkit.document() + rendered["tool"] = table + lines = tomlkit.dumps(rendered).splitlines() + assert "#" not in [line.strip() for line in lines] + assert '# "requires-comfyui" = ">=1.0.0" # note' in rendered.as_string() diff --git a/uv.lock b/uv.lock index 6153d2f2..2bbf0b04 100644 --- a/uv.lock +++ b/uv.lock @@ -267,7 +267,7 @@ requires-dist = [ { name = "ruff", marker = "extra == 'dev'" }, { name = "semver", specifier = "~=3.0.2" }, { name = "term-image", marker = "extra == 'preview'", specifier = ">=0.7,<0.8" }, - { name = "tomlkit" }, + { name = "tomlkit", specifier = ">=0.13,<0.16" }, { name = "typer", specifier = ">=0.12.5" }, { name = "typing-extensions", specifier = ">=4.7" }, { name = "uv", specifier = ">=0.11.15" },