ComfyUI-Manager/docs/internal/cli_migration/CLI_API_REFERENCE.md
Dr.Lt.Data 43647249cf refactor: remove package-level caching to support dynamic installation
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>
2025-11-08 09:07:09 +09:00

9.6 KiB

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

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)

# 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

@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

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

# 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

# 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

# ❌ 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

# 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

# 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

# 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

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

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

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.