ComfyUI-Manager/docs/internal/cli_migration/CLI_MIGRATION_GUIDE.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

12 KiB

CLI Migration Guide: Legacy to Glob Module

Status: Completed (Historical Reference) Last Updated: 2025-08-30 Purpose: Complete guide for migrating ComfyUI Manager CLI from legacy to glob module


📋 Table of Contents

  1. Overview
  2. Legacy vs Glob Comparison
  3. Migration Strategy
  4. Implementation Details
  5. Key Constraints
  6. API Reference
  7. Rollback Plan

Overview

Objective

Migrate ComfyUI Manager CLI from legacy module to glob module using only existing glob APIs without modifying the glob module itself.

Scope

  • Target File: comfyui_manager/cm_cli/__main__.py (1305 lines)
  • Timeline: 3.5 days
  • Approach: Minimal CLI changes, maximum compatibility
  • Constraint: NO glob module modifications

Current State

# Current imports (Lines 39-41)
from ..legacy import manager_core as core
from ..legacy.manager_core import unified_manager

# Target imports
from ..glob import manager_core as core
from ..glob.manager_core import unified_manager

Legacy vs Glob Comparison

Core Architecture Differences

Legacy Module (Current)

Data Structure: Dictionary-based global state

unified_manager.active_nodes          # Active nodes dict
unified_manager.unknown_active_nodes  # Unknown active nodes
unified_manager.cnr_inactive_nodes    # Inactive CNR nodes
unified_manager.nightly_inactive_nodes # Inactive nightly nodes
unified_manager.unknown_inactive_nodes # Unknown inactive nodes
unified_manager.cnr_map               # CNR info mapping

Glob Module (Target)

Data Structure: Object-oriented with InstalledNodePackage

unified_manager.installed_node_packages  # dict[str, list[InstalledNodePackage]]
unified_manager.repo_nodepack_map        # dict[str, InstalledNodePackage]

Method Compatibility Matrix

Method Legacy Glob Status Action
unified_enable() Compatible Direct mapping
unified_disable() Compatible Direct mapping
unified_uninstall() Compatible Direct mapping
unified_update() Compatible Direct mapping
install_by_id() Sync Async Modified Use asyncio.run()
gitclone_install() Replaced Use repo_install()
get_custom_nodes() Removed Use cnr_utils
load_nightly() Removed Not needed
extract_nodes_from_workflow() Removed Feature removed

InstalledNodePackage Class

@dataclass
class InstalledNodePackage:
    id: str                 # Package identifier
    fullpath: str           # Full filesystem path
    disabled: bool          # Disabled status
    version: str            # Version (nightly/unknown/x.y.z)
    repo_url: str = None    # Repository URL
    
    # Properties
    @property
    def is_unknown(self) -> bool: return self.version == "unknown"
    
    @property  
    def is_nightly(self) -> bool: return self.version == "nightly"
    
    @property
    def is_from_cnr(self) -> bool: return not (self.is_unknown or self.is_nightly)
    
    @property
    def is_enabled(self) -> bool: return not self.disabled
    
    @property
    def is_disabled(self) -> bool: return self.disabled

Migration Strategy

Phase 1: Setup (0.5 day)

Goal: Basic migration with error identification

  1. Import Path Changes

    # Change 2 lines
    from ..glob import manager_core as core
    from ..glob.manager_core import unified_manager
    
  2. Initial Testing

    • Run basic commands
    • Identify breaking changes
    • Document errors
  3. Error Analysis

    • List all affected functions
    • Categorize by priority
    • Plan fixes

Phase 2: Core Implementation (2 days)

Goal: Adapt CLI to glob architecture

  1. install_node() Updates

    # Replace gitclone_install with repo_install
    if unified_manager.is_url_like(node_spec_str):
        res = asyncio.run(unified_manager.repo_install(
            node_spec_str,
            os.path.basename(node_spec_str),
            instant_execution=True,
            no_deps=cmd_ctx.no_deps
        ))
    
  2. show_list() Rewrite (Most complex change)

    • Migrate from dictionary-based to InstalledNodePackage-based
    • Implement installed-only approach with optional CNR lookup
    • See show_list() Implementation section
  3. Context Management

    • Update get_all_installed_node_specs()
    • Adapt to new data structures
  4. Data Structure Migration

    • Replace all active_nodes references
    • Use installed_node_packages instead

Phase 3: Final Testing (1 day)

Goal: Comprehensive validation

  1. Feature Removal

    • Remove deps-in-workflow (not supported)
    • Stub unsupported features
  2. Testing

    • Test all CLI commands
    • Verify output format
    • Check edge cases
  3. Polish

    • Fix bugs
    • Improve error messages
    • Update help text

Implementation Details

show_list() Implementation

Challenge: Legacy uses multiple dictionaries, glob uses single InstalledNodePackage collection

Solution: Installed-only approach with on-demand CNR lookup

def show_list(kind: str, simple: bool = False):
    """
    Display node package list
    
    Args:
        kind: 'installed', 'enabled', 'disabled', 'all'
        simple: If True, show simple format
    """
    
    # Get all installed packages
    all_packages = []
    for packages in unified_manager.installed_node_packages.values():
        all_packages.extend(packages)
    
    # Filter by kind
    if kind == "enabled":
        packages = [p for p in all_packages if p.is_enabled]
    elif kind == "disabled":
        packages = [p for p in all_packages if p.is_disabled]
    elif kind == "installed" or kind == "all":
        packages = all_packages
    else:
        print(f"Unknown kind: {kind}")
        return
    
    # Display
    if simple:
        for pkg in packages:
            print(pkg.id)
    else:
        # Detailed display with CNR info on-demand
        for pkg in packages:
            status = "disabled" if pkg.disabled else "enabled"
            version_info = f"v{pkg.version}" if pkg.version != "unknown" else "unknown"
            
            print(f"[{status}] {pkg.id} ({version_info})")
            
            # Optionally fetch CNR info for non-nightly packages
            if pkg.is_from_cnr and not simple:
                cnr_info = cnr_utils.get_nodepackage(pkg.id)
                if cnr_info:
                    print(f"  Description: {cnr_info.get('description', 'N/A')}")

Key Changes:

  1. Single source of truth: installed_node_packages
  2. No separate active/inactive dictionaries
  3. On-demand CNR lookup instead of pre-cached cnr_map
  4. Filter by InstalledNodePackage properties

Git Installation Migration

Before (Legacy):

if core.is_valid_url(node_spec_str):
    res = asyncio.run(core.gitclone_install(
        node_spec_str, 
        no_deps=cmd_ctx.no_deps
    ))

After (Glob):

if unified_manager.is_url_like(node_spec_str):
    res = asyncio.run(unified_manager.repo_install(
        node_spec_str,
        os.path.basename(node_spec_str),  # repo_path derived from URL
        instant_execution=True,            # Execute immediately
        no_deps=cmd_ctx.no_deps           # Respect --no-deps flag
    ))

Async Function Handling

Pattern: Wrap async glob methods with asyncio.run()

# install_by_id is async in glob
res = asyncio.run(unified_manager.install_by_id(
    packname=node_name,
    version_spec=version,
    instant_execution=True,
    no_deps=cmd_ctx.no_deps
))

Key Constraints

Hard Constraints (Cannot Change)

  1. No glob module modifications

    • Cannot add new methods to UnifiedManager
    • Cannot add compatibility properties
    • Must use existing APIs only
  2. No legacy dependencies

    • CLI must work without legacy module
    • Clean break from old architecture
  3. Maintain CLI interface

    • Command syntax unchanged
    • Output format similar (minor differences acceptable)

Soft Constraints (Acceptable Trade-offs)

  1. Feature removal acceptable

    • deps-in-workflow feature can be removed
    • Channel/mode support can be simplified
  2. Performance trade-offs acceptable

    • On-demand CNR lookup vs pre-cached
    • Slight performance degradation acceptable
  3. Output format flexibility

    • Minor formatting differences acceptable
    • Must remain readable and useful

API Reference (Quick)

UnifiedManager Core Methods

# Installation
async def install_by_id(packname, version_spec, instant_execution, no_deps) -> ManagedResult

# Git/URL installation  
async def repo_install(url, repo_path, instant_execution, no_deps) -> ManagedResult

# Enable/Disable
def unified_enable(packname, version_spec=None) -> ManagedResult
def unified_disable(packname) -> ManagedResult

# Update/Uninstall
def unified_update(packname, instant_execution, no_deps) -> ManagedResult
def unified_uninstall(packname) -> ManagedResult

# Query
def get_active_pack(packname) -> InstalledNodePackage | None
def get_inactive_pack(packname, version_spec) -> InstalledNodePackage | None
def resolve_node_spec(packname, guess_mode) -> NodeSpec

# Utility
def is_url_like(text) -> bool

Data Access

# Installed packages
unified_manager.installed_node_packages: dict[str, list[InstalledNodePackage]]

# Repository mapping
unified_manager.repo_nodepack_map: dict[str, InstalledNodePackage]

External Utilities

# CNR utilities
from ..common import cnr_utils
cnr_utils.get_nodepackage(id) -> dict
cnr_utils.get_all_nodepackages() -> list[dict]

For complete API reference, see CLI_API_REFERENCE.md


Rollback Plan

If Migration Fails

  1. Immediate Rollback (< 5 minutes)

    # Revert imports in __main__.py
    from ..legacy import manager_core as core
    from ..legacy.manager_core import unified_manager
    
  2. Verify Rollback

    # Test basic commands
    cm-cli show installed
    cm-cli install <package>
    
  3. Document Issues

    • Note what failed
    • Gather error logs
    • Plan fixes

Risk Mitigation

  1. Backup: Keep legacy module available
  2. Testing: Comprehensive test suite before deployment
  3. Staging: Test in non-production environment first
  4. Monitoring: Watch for errors after deployment

Success Criteria

Must Pass (Blockers)

  • All core commands functional (install, update, enable, disable, uninstall)
  • Package information displays correctly
  • No glob module modifications
  • No critical regressions

Should Pass (Important)

  • Output format similar to legacy
  • Performance comparable to legacy
  • User-friendly error messages
  • Help text updated

Nice to Have

  • Improved code structure
  • Better error handling
  • Type hints added

Reference Documents


Conclusion

The CLI migration from legacy to glob module is achievable through systematic adaptation of CLI code to glob's object-oriented architecture. The key is respecting the constraint of no glob modifications while leveraging existing glob APIs effectively.

Status: This migration has been completed successfully. The CLI now uses glob module exclusively.