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>
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
- Overview
- Legacy vs Glob Comparison
- Migration Strategy
- Implementation Details
- Key Constraints
- API Reference
- 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
-
Import Path Changes
# Change 2 lines from ..glob import manager_core as core from ..glob.manager_core import unified_manager -
Initial Testing
- Run basic commands
- Identify breaking changes
- Document errors
-
Error Analysis
- List all affected functions
- Categorize by priority
- Plan fixes
Phase 2: Core Implementation (2 days)
Goal: Adapt CLI to glob architecture
-
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 )) -
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
-
Context Management
- Update get_all_installed_node_specs()
- Adapt to new data structures
-
Data Structure Migration
- Replace all active_nodes references
- Use installed_node_packages instead
Phase 3: Final Testing (1 day)
Goal: Comprehensive validation
-
Feature Removal
- Remove deps-in-workflow (not supported)
- Stub unsupported features
-
Testing
- Test all CLI commands
- Verify output format
- Check edge cases
-
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:
- Single source of truth:
installed_node_packages - No separate active/inactive dictionaries
- On-demand CNR lookup instead of pre-cached cnr_map
- 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)
-
❌ No glob module modifications
- Cannot add new methods to UnifiedManager
- Cannot add compatibility properties
- Must use existing APIs only
-
❌ No legacy dependencies
- CLI must work without legacy module
- Clean break from old architecture
-
❌ Maintain CLI interface
- Command syntax unchanged
- Output format similar (minor differences acceptable)
Soft Constraints (Acceptable Trade-offs)
-
✅ Feature removal acceptable
- deps-in-workflow feature can be removed
- Channel/mode support can be simplified
-
✅ Performance trade-offs acceptable
- On-demand CNR lookup vs pre-cached
- Slight performance degradation acceptable
-
✅ 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
-
Immediate Rollback (< 5 minutes)
# Revert imports in __main__.py from ..legacy import manager_core as core from ..legacy.manager_core import unified_manager -
Verify Rollback
# Test basic commands cm-cli show installed cm-cli install <package> -
Document Issues
- Note what failed
- Gather error logs
- Plan fixes
Risk Mitigation
- Backup: Keep legacy module available
- Testing: Comprehensive test suite before deployment
- Staging: Test in non-production environment first
- 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
- CLI_API_REFERENCE.md - Complete API documentation
- CLI_IMPLEMENTATION_CHECKLIST.md - Step-by-step tasks
- CLI_TESTING_GUIDE.md - Testing strategy
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.