6d433ba371
Publish to Comfy registry / Publish Custom Node to registry (push) Waiting to run
Disable/enable no longer require ComfyUI Manager: - New pack_fs.py moves packs in/out of custom_nodes/.disabled/ (no import, delete, or re-clone). Fallback for hand-cloned packs, loose single-file nodes, or when Manager is absent. enable strips the @version suffix so packs restore as clean, importable dir names. - Routes: native-disable, native-enable, disabled-packs. - Frontend routes each disable per-pack (Manager queue vs native move), and shows an Enable button on recoverable packs in the Uninstalled tier. The restart banner degrades to a manual-restart notice when no Manager exists. Whitelist (packages-only): a star toggle protects a pack — pulled into its own pinned group, no Disable button, skipped by the 7-day trial auto-disable. - New whitelist_packages table; whitelisted flag on package stats. - Routes: whitelist, whitelist/add, whitelist/remove. Tests: test_pack_fs.py, test_whitelist.py (60 passing). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
151 lines
5.0 KiB
Python
151 lines
5.0 KiB
Python
import os
|
|
|
|
import pytest
|
|
|
|
import pack_fs
|
|
|
|
|
|
@pytest.fixture
|
|
def custom_nodes(tmp_path):
|
|
"""A custom_nodes root wired into the mocked folder_paths."""
|
|
import folder_paths
|
|
|
|
root = tmp_path / "custom_nodes"
|
|
root.mkdir()
|
|
folder_paths.get_folder_paths.return_value = [str(root)]
|
|
return root
|
|
|
|
|
|
def _make_pack(root, name):
|
|
pack = root / name
|
|
pack.mkdir()
|
|
(pack / "__init__.py").write_text("NODE_CLASS_MAPPINGS = {}\n")
|
|
return pack
|
|
|
|
|
|
# --- find_active_pack_path ------------------------------------------------
|
|
|
|
def test_find_active_dir(custom_nodes):
|
|
_make_pack(custom_nodes, "MyPack")
|
|
found = pack_fs.find_active_pack_path("MyPack")
|
|
assert found == os.path.normpath(str(custom_nodes / "MyPack"))
|
|
|
|
|
|
def test_find_active_case_insensitive(custom_nodes):
|
|
_make_pack(custom_nodes, "MyPack")
|
|
assert pack_fs.find_active_pack_path("mypack") is not None
|
|
|
|
|
|
def test_find_active_single_py_file(custom_nodes):
|
|
(custom_nodes / "loose_node.py").write_text("NODE_CLASS_MAPPINGS = {}\n")
|
|
found = pack_fs.find_active_pack_path("loose_node")
|
|
assert found == os.path.normpath(str(custom_nodes / "loose_node.py"))
|
|
|
|
|
|
def test_find_active_ignores_disabled_dir(custom_nodes):
|
|
(custom_nodes / ".disabled").mkdir()
|
|
_make_pack(custom_nodes / ".disabled", "MyPack")
|
|
assert pack_fs.find_active_pack_path("MyPack") is None
|
|
|
|
|
|
def test_find_active_rejects_path_traversal(custom_nodes):
|
|
assert pack_fs.find_active_pack_path("../evil") is None
|
|
assert pack_fs.find_active_pack_path("a/b") is None
|
|
assert pack_fs.find_active_pack_path("") is None
|
|
|
|
|
|
# --- disable_pack_native --------------------------------------------------
|
|
|
|
def test_disable_moves_dir_into_disabled(custom_nodes):
|
|
_make_pack(custom_nodes, "MyPack")
|
|
ok, msg = pack_fs.disable_pack_native("MyPack")
|
|
assert ok, msg
|
|
assert not (custom_nodes / "MyPack").exists()
|
|
assert (custom_nodes / ".disabled" / "MyPack" / "__init__.py").exists()
|
|
|
|
|
|
def test_disable_moves_single_py_file(custom_nodes):
|
|
(custom_nodes / "loose_node.py").write_text("x = 1\n")
|
|
ok, msg = pack_fs.disable_pack_native("loose_node")
|
|
assert ok, msg
|
|
assert not (custom_nodes / "loose_node.py").exists()
|
|
assert (custom_nodes / ".disabled" / "loose_node.py").exists()
|
|
|
|
|
|
def test_disable_missing_pack_fails(custom_nodes):
|
|
ok, msg = pack_fs.disable_pack_native("Ghost")
|
|
assert not ok
|
|
assert "not found" in msg
|
|
|
|
|
|
def test_disable_collision_fails(custom_nodes):
|
|
_make_pack(custom_nodes, "MyPack")
|
|
(custom_nodes / ".disabled").mkdir()
|
|
(custom_nodes / ".disabled" / "MyPack").mkdir() # pre-existing disabled copy
|
|
ok, msg = pack_fs.disable_pack_native("MyPack")
|
|
assert not ok
|
|
assert "already exists" in msg
|
|
# original left untouched
|
|
assert (custom_nodes / "MyPack").exists()
|
|
|
|
|
|
# --- enable_pack_native ---------------------------------------------------
|
|
|
|
def test_enable_moves_back(custom_nodes):
|
|
_make_pack(custom_nodes, "MyPack")
|
|
assert pack_fs.disable_pack_native("MyPack")[0]
|
|
ok, msg = pack_fs.enable_pack_native("MyPack")
|
|
assert ok, msg
|
|
assert (custom_nodes / "MyPack" / "__init__.py").exists()
|
|
assert not (custom_nodes / ".disabled" / "MyPack").exists()
|
|
|
|
|
|
def test_enable_missing_fails(custom_nodes):
|
|
ok, msg = pack_fs.enable_pack_native("Ghost")
|
|
assert not ok
|
|
assert "not found" in msg
|
|
|
|
|
|
def test_enable_strips_version_suffix(custom_nodes):
|
|
# A Manager-disabled pack on disk carries an @version suffix; enabling should
|
|
# restore it as a clean, importable directory name.
|
|
ddir = custom_nodes / ".disabled"
|
|
ddir.mkdir()
|
|
pack = ddir / "ComfyMath@nightly"
|
|
pack.mkdir()
|
|
(pack / "__init__.py").write_text("NODE_CLASS_MAPPINGS = {}\n")
|
|
|
|
ok, msg = pack_fs.enable_pack_native("ComfyMath")
|
|
assert ok, msg
|
|
assert (custom_nodes / "ComfyMath" / "__init__.py").exists()
|
|
assert not (custom_nodes / "ComfyMath@nightly").exists()
|
|
assert not (ddir / "ComfyMath@nightly").exists()
|
|
|
|
|
|
def test_disable_then_enable_roundtrip_py_file(custom_nodes):
|
|
(custom_nodes / "loose_node.py").write_text("x = 1\n")
|
|
assert pack_fs.disable_pack_native("loose_node")[0]
|
|
assert pack_fs.enable_pack_native("loose_node")[0]
|
|
assert (custom_nodes / "loose_node.py").exists()
|
|
|
|
|
|
# --- list_disabled_packs --------------------------------------------------
|
|
|
|
def test_list_disabled_empty(custom_nodes):
|
|
assert pack_fs.list_disabled_packs() == set()
|
|
|
|
|
|
def test_list_disabled_strips_version_and_ext(custom_nodes):
|
|
ddir = custom_nodes / ".disabled"
|
|
ddir.mkdir()
|
|
(ddir / "ComfyMath@nightly").mkdir() # Manager-style @version suffix
|
|
(ddir / "PlainPack").mkdir()
|
|
(ddir / "loose_node.py").write_text("x = 1\n") # single-file pack
|
|
assert pack_fs.list_disabled_packs() == {"ComfyMath", "PlainPack", "loose_node"}
|
|
|
|
|
|
def test_list_disabled_reflects_a_native_disable(custom_nodes):
|
|
_make_pack(custom_nodes, "MyPack")
|
|
assert pack_fs.disable_pack_native("MyPack")[0]
|
|
assert "MyPack" in pack_fs.list_disabled_packs()
|