mirror of
https://github.com/Comfy-Org/ComfyUI-Manager.git
synced 2026-07-26 10:07:38 +08:00
feat(cli): add update-cache command, purge reinstall fallback, and batch failure tracking (#2693)
Add `cm-cli update-cache` command that force-fetches all remote data
and populates local cache in blocking mode. Bypasses pip/offline guards
in get_data_by_mode and get_cnr_data by directly fetching channel JSON
files and calling reload('remote'). Solves permanent cold-start issue
where pip-installed cm-cli could never populate CNR cache without
running the web server first.
Add UnifiedManager.purge_node_state() to both glob and legacy packages
for reinstall categorization mismatch (e.g. unknown→nightly). Includes
path traversal protection via commonpath, root directory guard,
comfyui-manager self-protection, and finally-guarded dictionary cleanup.
Add NodeInstallError exception and batch failure tracking to
for_each_nodes: reinstall propagates install failures via
raise_on_fail, for_each_nodes catches NodeInstallError, tracks
failed nodes, reports aggregate summary, and exits non-zero.
Remove debug print in install_node.
Bump version to 4.1b5.
E2E verified:
- update-cache: empty cache (5 lines) → populated (7815 lines)
- reinstall batch: 2 packs, 1 failure → continues to 2nd → summary + exit 1
This commit is contained in:
@@ -1142,6 +1142,71 @@ class UnifiedManager:
|
||||
|
||||
return result
|
||||
|
||||
def purge_node_state(self, node_id: str):
|
||||
"""
|
||||
Remove a node's directory and clean ALL internal dictionaries regardless of categorization.
|
||||
Used by reinstall to guarantee clean state before re-installation.
|
||||
"""
|
||||
if 'comfyui-manager' in node_id.lower():
|
||||
return
|
||||
|
||||
custom_nodes_dir = os.path.normcase(os.path.realpath(get_default_custom_nodes_path()))
|
||||
paths_to_remove = set()
|
||||
|
||||
def _add_path(raw_path):
|
||||
"""Normalize and validate a path before adding to removal set."""
|
||||
if not raw_path:
|
||||
return
|
||||
resolved = os.path.normcase(os.path.realpath(raw_path))
|
||||
if resolved == custom_nodes_dir:
|
||||
logging.warning(f"[ComfyUI-Manager] purge_node_state: refusing to delete custom_nodes root: {raw_path}")
|
||||
return
|
||||
try:
|
||||
if os.path.commonpath([custom_nodes_dir, resolved]) != custom_nodes_dir:
|
||||
logging.warning(f"[ComfyUI-Manager] purge_node_state: path escapes custom_nodes scope, skipping: {raw_path}")
|
||||
return
|
||||
except ValueError:
|
||||
logging.warning(f"[ComfyUI-Manager] purge_node_state: cannot verify containment, skipping: {raw_path}")
|
||||
return
|
||||
paths_to_remove.add(resolved)
|
||||
|
||||
# Collect paths from all dictionaries
|
||||
entry = self.unknown_active_nodes.get(node_id)
|
||||
if entry is not None:
|
||||
_add_path(entry[1])
|
||||
|
||||
entry = self.active_nodes.get(node_id)
|
||||
if entry is not None:
|
||||
_add_path(entry[1])
|
||||
|
||||
entry = self.unknown_inactive_nodes.get(node_id)
|
||||
if entry is not None:
|
||||
_add_path(entry[1])
|
||||
|
||||
fullpath = self.nightly_inactive_nodes.get(node_id)
|
||||
if fullpath is not None:
|
||||
_add_path(fullpath)
|
||||
|
||||
ver_map = self.cnr_inactive_nodes.get(node_id)
|
||||
if ver_map is not None:
|
||||
for key, fp in ver_map.items():
|
||||
_add_path(fp)
|
||||
|
||||
# Convention-based fallback path
|
||||
_add_path(os.path.join(get_default_custom_nodes_path(), node_id))
|
||||
|
||||
# Remove all validated paths, then always clean dictionaries
|
||||
try:
|
||||
for path in paths_to_remove:
|
||||
if os.path.exists(path):
|
||||
try_rmtree(node_id, path)
|
||||
finally:
|
||||
self.unknown_active_nodes.pop(node_id, None)
|
||||
self.active_nodes.pop(node_id, None)
|
||||
self.unknown_inactive_nodes.pop(node_id, None)
|
||||
self.nightly_inactive_nodes.pop(node_id, None)
|
||||
self.cnr_inactive_nodes.pop(node_id, None)
|
||||
|
||||
def unified_uninstall(self, node_id: str, is_unknown: bool):
|
||||
"""
|
||||
Remove whole installed custom nodes including inactive nodes
|
||||
|
||||
Reference in New Issue
Block a user