feat: native disable/enable fallback and package whitelist; bump to 1.8.0
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>
This commit is contained in:
2026-07-04 17:16:29 +02:00
parent 7ca7d95ef3
commit 6d433ba371
8 changed files with 777 additions and 54 deletions
+72
View File
@@ -0,0 +1,72 @@
import pytest
from tracker import UsageTracker
@pytest.fixture
def tracker(tmp_path):
return UsageTracker(db_path=str(tmp_path / "test.db"))
def test_whitelist_starts_empty(tracker):
assert tracker.get_whitelist() == set()
def test_add_and_get(tracker):
tracker.add_to_whitelist("My-Pack")
assert tracker.get_whitelist() == {"My-Pack"}
def test_add_is_idempotent(tracker):
tracker.add_to_whitelist("My-Pack")
tracker.add_to_whitelist("My-Pack")
assert tracker.get_whitelist() == {"My-Pack"}
def test_remove(tracker):
tracker.add_to_whitelist("My-Pack")
tracker.remove_from_whitelist("My-Pack")
assert tracker.get_whitelist() == set()
def test_remove_absent_is_noop(tracker):
tracker.remove_from_whitelist("Nope") # must not raise
assert tracker.get_whitelist() == set()
def test_reset_clears_whitelist(tracker):
tracker.add_to_whitelist("My-Pack")
tracker.reset()
assert tracker.get_whitelist() == set()
class _Mapper:
"""Minimal stand-in for NodePackageMapper with a fixed mapping."""
def __init__(self, mapping):
self.mapping = mapping
def get_package(self, ct):
return self.mapping.get(ct, "__unknown__")
def get_all_packages(self):
return set(self.mapping.values()) - {"__builtin__"}
def test_package_stats_flags_whitelisted(tracker):
mapper = _Mapper({"NodeA": "Pack-A", "NodeB": "Pack-B"})
tracker.record_usage(["NodeA", "NodeB"], mapper)
tracker.add_to_whitelist("Pack-A")
stats = {p["package"]: p for p in tracker.get_package_stats(mapper)}
assert stats["Pack-A"]["whitelisted"] is True
assert stats["Pack-B"]["whitelisted"] is False
def test_package_stats_whitelist_is_case_insensitive(tracker):
mapper = _Mapper({"NodeA": "Pack-A"})
tracker.record_usage(["NodeA"], mapper)
tracker.add_to_whitelist("pack-a") # different case than the package name
stats = {p["package"]: p for p in tracker.get_package_stats(mapper)}
assert stats["Pack-A"]["whitelisted"] is True