mirror of
https://git.datalinker.icu/ltdrdata/ComfyUI-Manager
synced 2025-12-18 02:24:24 +08:00
Remove package-level caching in cnr_utils and node_package modules to enable proper dynamic custom node installation and version switching without ComfyUI server restarts. Key Changes: - Remove @lru_cache decorators from version-sensitive functions - Remove cached_property from NodePackage for dynamic state updates - Add comprehensive test suite with parallel execution support - Implement version switching tests (CNR ↔ Nightly) - Add case sensitivity integration tests - Improve error handling and logging API Priority Rules (manager_core.py:1801): - Enabled-Priority: Show only enabled version when both exist - CNR-Priority: Show only CNR when both CNR and Nightly are disabled - Prevents duplicate package entries in /v2/customnode/installed API - Cross-match using cnr_id and aux_id for CNR ↔ Nightly detection Test Infrastructure: - 8 test files with 59 comprehensive test cases - Parallel test execution across 5 isolated environments - Automated test scripts with environment setup - Configurable timeout (60 minutes default) - Support for both master and dr-support-pip-cm branches Bug Fixes: - Fix COMFYUI_CUSTOM_NODES_PATH environment variable export - Resolve test fixture regression with module-level variables - Fix import timing issues in test configuration - Register pytest integration marker to eliminate warnings - Fix POSIX compliance in shell scripts (((var++)) → $((var + 1))) Documentation: - CNR_VERSION_MANAGEMENT_DESIGN.md v1.0 → v1.1 with API priority rules - Add test guides and execution documentation (TESTING_PROMPT.md) - Add security-enhanced installation guide - Create CLI migration guides and references - Document package version management 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
292 lines
9.6 KiB
Markdown
292 lines
9.6 KiB
Markdown
# Glob Module API Reference for CLI Migration
|
|
|
|
## 🎯 Quick Reference
|
|
This document provides essential glob module APIs available for CLI implementation. **READ ONLY** - do not modify glob module.
|
|
|
|
---
|
|
|
|
## 📦 Core Classes
|
|
|
|
### UnifiedManager
|
|
**Location**: `comfyui_manager/glob/manager_core.py:436`
|
|
**Instance**: Available as `unified_manager` (global instance)
|
|
|
|
#### Data Structures
|
|
```python
|
|
class UnifiedManager:
|
|
def __init__(self):
|
|
# PRIMARY DATA - Use these instead of legacy dicts
|
|
self.installed_node_packages: dict[str, list[InstalledNodePackage]]
|
|
self.repo_nodepack_map: dict[str, InstalledNodePackage] # compact_url -> package
|
|
self.processed_install: set
|
|
```
|
|
|
|
#### Core Methods (Direct CLI Equivalents)
|
|
```python
|
|
# Installation & Management
|
|
async def install_by_id(packname: str, version_spec=None, channel=None,
|
|
mode=None, instant_execution=False, no_deps=False,
|
|
return_postinstall=False) -> ManagedResult
|
|
def unified_enable(packname: str, version_spec=None) -> ManagedResult
|
|
def unified_disable(packname: str) -> ManagedResult
|
|
def unified_uninstall(packname: str) -> ManagedResult
|
|
def unified_update(packname: str, instant_execution=False, no_deps=False,
|
|
return_postinstall=False) -> ManagedResult
|
|
def unified_fix(packname: str, version_spec, instant_execution=False,
|
|
no_deps=False) -> ManagedResult
|
|
|
|
# Package Resolution & Info
|
|
def resolve_node_spec(packname: str, guess_mode=None) -> tuple[str, str, bool] | None
|
|
def get_active_pack(packname: str) -> InstalledNodePackage | None
|
|
def get_inactive_pack(packname: str, version_spec=None) -> InstalledNodePackage | None
|
|
|
|
# Git Repository Operations
|
|
async def repo_install(url: str, repo_path: str, instant_execution=False,
|
|
no_deps=False, return_postinstall=False) -> ManagedResult
|
|
def repo_update(repo_path: str, instant_execution=False, no_deps=False,
|
|
return_postinstall=False) -> ManagedResult
|
|
|
|
# Utilities
|
|
def is_url_like(url: str) -> bool
|
|
def reload() -> None
|
|
```
|
|
|
|
---
|
|
|
|
### InstalledNodePackage
|
|
**Location**: `comfyui_manager/common/node_package.py:10`
|
|
|
|
```python
|
|
@dataclass
|
|
class InstalledNodePackage:
|
|
# Core Data
|
|
id: str # Package identifier
|
|
fullpath: str # Installation path
|
|
disabled: bool # Disabled state
|
|
version: str # Version (cnr version, "nightly", or "unknown")
|
|
repo_url: str = None # Git repository URL (for nightly/unknown)
|
|
|
|
# Computed Properties
|
|
@property
|
|
def is_unknown(self) -> bool: # version == "unknown"
|
|
@property
|
|
def is_nightly(self) -> bool: # version == "nightly"
|
|
@property
|
|
def is_from_cnr(self) -> bool: # not unknown and not nightly
|
|
@property
|
|
def is_enabled(self) -> bool: # not disabled
|
|
@property
|
|
def is_disabled(self) -> bool: # disabled
|
|
|
|
# Methods
|
|
def get_commit_hash(self) -> str
|
|
def isValid(self) -> bool
|
|
|
|
@staticmethod
|
|
def from_fullpath(fullpath: str, resolve_from_path) -> InstalledNodePackage
|
|
```
|
|
|
|
---
|
|
|
|
### ManagedResult
|
|
**Location**: `comfyui_manager/glob/manager_core.py:285`
|
|
|
|
```python
|
|
class ManagedResult:
|
|
def __init__(self, action: str):
|
|
self.action: str = action # 'install-cnr', 'install-git', 'enable', 'skip', etc.
|
|
self.result: bool = True # Success/failure
|
|
self.msg: str = "" # Human readable message
|
|
self.target: str = None # Target identifier
|
|
self.postinstall = None # Post-install callback
|
|
|
|
# Methods
|
|
def fail(self, msg: str = "") -> ManagedResult
|
|
def with_msg(self, msg: str) -> ManagedResult
|
|
def with_target(self, target: str) -> ManagedResult
|
|
def with_postinstall(self, postinstall) -> ManagedResult
|
|
```
|
|
|
|
---
|
|
|
|
## 🛠️ Standalone Functions
|
|
|
|
### Core Manager Functions
|
|
```python
|
|
# Snapshot Operations
|
|
async def save_snapshot_with_postfix(postfix: str, path: str = None,
|
|
custom_nodes_only: bool = False) -> str
|
|
|
|
async def restore_snapshot(snapshot_path: str, git_helper_extras=None) -> None
|
|
|
|
# Node Utilities
|
|
def simple_check_custom_node(url: str) -> str # Returns: 'installed', 'not-installed', 'disabled'
|
|
|
|
# Path Utilities
|
|
def get_custom_nodes_paths() -> list[str]
|
|
```
|
|
|
|
---
|
|
|
|
## 🔗 CNR Utilities
|
|
**Location**: `comfyui_manager/common/cnr_utils.py`
|
|
|
|
```python
|
|
# Essential CNR functions for CLI
|
|
def get_nodepack(packname: str) -> dict | None
|
|
# Returns CNR package info or None
|
|
|
|
def get_all_nodepackages() -> dict[str, dict]
|
|
# Returns all CNR packages {package_id: package_info}
|
|
|
|
def all_versions_of_node(node_name: str) -> list[dict] | None
|
|
# Returns version history for a package
|
|
```
|
|
|
|
---
|
|
|
|
## 📋 Usage Patterns for CLI Migration
|
|
|
|
### 1. Replace Legacy Dict Access
|
|
```python
|
|
# ❌ OLD (Legacy way)
|
|
for k, v in unified_manager.active_nodes.items():
|
|
version, fullpath = v
|
|
print(f"Active: {k} @ {version}")
|
|
|
|
# ✅ NEW (Glob way)
|
|
for packages in unified_manager.installed_node_packages.values():
|
|
for pack in packages:
|
|
if pack.is_enabled:
|
|
print(f"Active: {pack.id} @ {pack.version}")
|
|
```
|
|
|
|
### 2. Package Installation
|
|
```python
|
|
# CNR Package Installation
|
|
res = await unified_manager.install_by_id("package-name", "1.0.0",
|
|
instant_execution=True, no_deps=False)
|
|
|
|
# Git URL Installation
|
|
if unified_manager.is_url_like(url):
|
|
repo_name = os.path.basename(url).replace('.git', '')
|
|
res = await unified_manager.repo_install(url, repo_name,
|
|
instant_execution=True, no_deps=False)
|
|
```
|
|
|
|
### 3. Package State Queries
|
|
```python
|
|
# Check if package is active
|
|
active_pack = unified_manager.get_active_pack("package-name")
|
|
if active_pack:
|
|
print(f"Package is enabled: {active_pack.version}")
|
|
|
|
# Check if package is inactive
|
|
inactive_pack = unified_manager.get_inactive_pack("package-name")
|
|
if inactive_pack:
|
|
print(f"Package is disabled: {inactive_pack.version}")
|
|
```
|
|
|
|
### 4. CNR Data Access
|
|
```python
|
|
# Get CNR package information
|
|
from ..common import cnr_utils
|
|
|
|
cnr_info = cnr_utils.get_nodepack("package-name")
|
|
if cnr_info:
|
|
publisher = cnr_info.get('publisher', {}).get('name', 'Unknown')
|
|
print(f"Publisher: {publisher}")
|
|
|
|
# Get all CNR packages (for show not-installed)
|
|
all_cnr = cnr_utils.get_all_nodepackages()
|
|
```
|
|
|
|
### 5. Result Handling
|
|
```python
|
|
res = await unified_manager.install_by_id("package-name")
|
|
|
|
if res.action == 'skip':
|
|
print(f"SKIP: {res.msg}")
|
|
elif res.action == 'install-cnr' and res.result:
|
|
print(f"INSTALLED: {res.target}")
|
|
elif res.action == 'enable' and res.result:
|
|
print(f"ENABLED: package was already installed")
|
|
else:
|
|
print(f"ERROR: {res.msg}")
|
|
```
|
|
|
|
---
|
|
|
|
## 🚫 NOT Available in Glob (Handle These)
|
|
|
|
### Legacy Functions That Don't Exist:
|
|
- `get_custom_nodes()` → Use `cnr_utils.get_all_nodepackages()`
|
|
- `load_nightly()` → Remove or stub
|
|
- `extract_nodes_from_workflow()` → Remove feature
|
|
- `gitclone_install()` → Use `repo_install()`
|
|
|
|
### Legacy Properties That Don't Exist:
|
|
- `active_nodes` → Use `installed_node_packages` + filter by `is_enabled`
|
|
- `cnr_map` → Use `cnr_utils.get_all_nodepackages()`
|
|
- `cnr_inactive_nodes` → Use `installed_node_packages` + filter by `is_disabled` and `is_from_cnr`
|
|
- `nightly_inactive_nodes` → Use `installed_node_packages` + filter by `is_disabled` and `is_nightly`
|
|
- `unknown_active_nodes` → Use `installed_node_packages` + filter by `is_enabled` and `is_unknown`
|
|
- `unknown_inactive_nodes` → Use `installed_node_packages` + filter by `is_disabled` and `is_unknown`
|
|
|
|
---
|
|
|
|
## 🔄 Data Migration Examples
|
|
|
|
### Show Enabled Packages
|
|
```python
|
|
def show_enabled_packages():
|
|
enabled_packages = []
|
|
|
|
# Collect enabled packages
|
|
for packages in unified_manager.installed_node_packages.values():
|
|
for pack in packages:
|
|
if pack.is_enabled:
|
|
enabled_packages.append(pack)
|
|
|
|
# Display with CNR info
|
|
for pack in enabled_packages:
|
|
if pack.is_from_cnr:
|
|
cnr_info = cnr_utils.get_nodepack(pack.id)
|
|
publisher = cnr_info.get('publisher', {}).get('name', 'Unknown') if cnr_info else 'Unknown'
|
|
print(f"[ ENABLED ] {pack.id:50} (author: {publisher}) [{pack.version}]")
|
|
elif pack.is_nightly:
|
|
print(f"[ ENABLED ] {pack.id:50} (nightly) [NIGHTLY]")
|
|
else:
|
|
print(f"[ ENABLED ] {pack.id:50} (unknown) [UNKNOWN]")
|
|
```
|
|
|
|
### Show Not-Installed Packages
|
|
```python
|
|
def show_not_installed_packages():
|
|
# Get installed package IDs
|
|
installed_ids = set()
|
|
for packages in unified_manager.installed_node_packages.values():
|
|
for pack in packages:
|
|
installed_ids.add(pack.id)
|
|
|
|
# Get all CNR packages
|
|
all_cnr = cnr_utils.get_all_nodepackages()
|
|
|
|
# Show not-installed
|
|
for pack_id, pack_info in all_cnr.items():
|
|
if pack_id not in installed_ids:
|
|
publisher = pack_info.get('publisher', {}).get('name', 'Unknown')
|
|
latest_version = pack_info.get('latest_version', {}).get('version', '0.0.0')
|
|
print(f"[ NOT INSTALLED ] {pack_info['name']:50} {pack_id:30} (author: {publisher}) [{latest_version}]")
|
|
```
|
|
|
|
---
|
|
|
|
## ⚠️ Key Constraints
|
|
|
|
1. **NO MODIFICATIONS**: Do not add any functions or properties to glob module
|
|
2. **USE EXISTING APIs**: Only use the functions and classes documented above
|
|
3. **ADAPT CLI**: CLI must adapt to glob's data structures and patterns
|
|
4. **REMOVE IF NEEDED**: Remove features that can't be implemented with available APIs
|
|
|
|
This reference should provide everything needed to implement the CLI migration using only existing glob APIs. |