refactor(cli): extract _finalize_resolve helper, add CNR nightly fallback and pydantic guard
CI / Validate OpenAPI Specification (push) Has been cancelled
CI / Code Quality Checks (push) Has been cancelled
Python Linting / Run Ruff (push) Has been cancelled

- Extract `_finalize_resolve()` to eliminate 7x duplicated uv-compile
  error handling blocks in cm_cli (~-85 lines)
- Move conflict attribution regex to `attribute_conflicts()` in
  unified_dep_resolver.py for direct testability
- Update 4 attribution tests to call production function instead of
  re-implementing regex
- Add CNR nightly fallback: when node is absent from nightly manifest,
  fall back to cnr_map repository URL (glob + legacy)
- Add pydantic Union guard: use getattr for is_unknown in uninstall
  and disable handlers to prevent Union type mismatch
- Add E2E test suites for endpoint install/uninstall and uv-compile
  CLI commands (conflict + success cases)
- Add nightly CNR fallback regression tests
This commit is contained in:
Dr.Lt.Data
2026-03-14 07:57:16 +09:00
parent e0f8e653c7
commit 57eb67f615
9 changed files with 969 additions and 148 deletions
@@ -702,3 +702,23 @@ class UnifiedDepResolver:
)
except OSError:
pass
def attribute_conflicts(
sources: dict[str, list[tuple[str, str]]],
conflicts: list[str],
) -> dict[str, list[tuple[str, str]]]:
"""Cross-reference conflict packages with their requesting node packs.
Uses word-boundary regex to prevent false-positive prefix matches
(e.g. ``torch`` does NOT match ``torchvision`` or ``torch_audio``).
"""
conflict_text = "\n".join(conflicts).lower().replace("-", "_")
return {
pkg: reqs
for pkg, reqs in sources.items()
if re.search(
r'(?<![a-z0-9_])' + re.escape(pkg.lower().replace("-", "_")) + r'(?![a-z0-9_])',
conflict_text,
)
}
+12 -2
View File
@@ -1417,8 +1417,18 @@ class UnifiedManager:
else: # nightly
repo_url = the_node['repository']
else:
result = ManagedResult('install')
return result.fail(f"Node '{node_id}@{version_spec}' not found in [{channel}, {mode}]")
# Fallback for nightly only: use repository URL from CNR map
# when node is registered in CNR but absent from nightly manifest
if version_spec == 'nightly':
cnr_fallback = self.cnr_map.get(node_id)
if cnr_fallback is not None and cnr_fallback.get('repository'):
repo_url = cnr_fallback['repository']
else:
result = ManagedResult('install')
return result.fail(f"Node '{node_id}@{version_spec}' not found in [{channel}, {mode}]")
else:
result = ManagedResult('install')
return result.fail(f"Node '{node_id}@{version_spec}' not found in [{channel}, {mode}]")
if self.is_enabled(node_id, version_spec):
return ManagedResult('skip').with_target(f"{node_id}@{version_spec}")
+4 -3
View File
@@ -995,7 +995,7 @@ async def task_worker():
return OperationResult.failed.value
node_name = params.node_name
is_unknown = params.is_unknown
is_unknown = getattr(params, 'is_unknown', False) # guard: pydantic Union may match UpdatePackParams
logging.debug(
"[ComfyUI-Manager] Uninstalling node: name=%s, is_unknown=%s",
@@ -1019,15 +1019,16 @@ async def task_worker():
async def do_disable(params: DisablePackParams) -> str:
node_name = params.node_name
is_unknown = getattr(params, 'is_unknown', False) # guard: pydantic Union may match UpdatePackParams
logging.debug(
"[ComfyUI-Manager] Disabling node: name=%s, is_unknown=%s",
node_name,
params.is_unknown,
is_unknown,
)
try:
res = core.unified_manager.unified_disable(node_name, params.is_unknown)
res = core.unified_manager.unified_disable(node_name, is_unknown)
if res:
return OperationResult.success.value
+12 -2
View File
@@ -1411,8 +1411,18 @@ class UnifiedManager:
else: # nightly
repo_url = the_node['repository']
else:
result = ManagedResult('install')
return result.fail(f"Node '{node_id}@{version_spec}' not found in [{channel}, {mode}]")
# Fallback for nightly only: use repository URL from CNR map
# when node is registered in CNR but absent from nightly manifest
if version_spec == 'nightly':
cnr_fallback = self.cnr_map.get(node_id)
if cnr_fallback is not None and cnr_fallback.get('repository'):
repo_url = cnr_fallback['repository']
else:
result = ManagedResult('install')
return result.fail(f"Node '{node_id}@{version_spec}' not found in [{channel}, {mode}]")
else:
result = ManagedResult('install')
return result.fail(f"Node '{node_id}@{version_spec}' not found in [{channel}, {mode}]")
if self.is_enabled(node_id, version_spec):
return ManagedResult('skip').with_target(f"{node_id}@{version_spec}")