diff --git a/nodes.py b/nodes.py index faf908538..c6e829eac 100644 --- a/nodes.py +++ b/nodes.py @@ -2233,8 +2233,12 @@ def load_node_replacements_json(module_dir: str, module_name: str): for entry in replacements: if not isinstance(entry, dict): continue + new_node_id = entry.get("new_node_id", "") + if not new_node_id: + logging.warning(f"node_replacements.json in {module_name}: entry for '{old_node_id}' missing 'new_node_id', skipping.") + continue manager.register(NodeReplace( - new_node_id=entry.get("new_node_id", ""), + new_node_id=new_node_id, old_node_id=entry.get("old_node_id", old_node_id), old_widget_ids=entry.get("old_widget_ids"), input_mapping=entry.get("input_mapping"), @@ -2274,7 +2278,10 @@ async def load_custom_node(module_path: str, ignore=set(), module_parent="custom LOADED_MODULE_DIRS[module_name] = os.path.abspath(module_dir) - load_node_replacements_json(module_dir, module_name) + # Only load node_replacements.json from directory-based custom nodes (proper packs). + # Single-file .py nodes share a parent dir, so checking there would be incorrect. + if os.path.isdir(module_path): + load_node_replacements_json(module_dir, module_name) try: from comfy_config import config_parser diff --git a/tests/test_node_replacements_json.py b/tests/test_node_replacements_json.py index ec601f0b3..101c4caa4 100644 --- a/tests/test_node_replacements_json.py +++ b/tests/test_node_replacements_json.py @@ -44,8 +44,12 @@ def load_node_replacements_json(module_dir, module_name, manager, NodeReplace=Mo for entry in replacements: if not isinstance(entry, dict): continue + new_node_id = entry.get("new_node_id", "") + if not new_node_id: + logging.warning(f"node_replacements.json in {module_name}: entry for '{old_node_id}' missing 'new_node_id', skipping.") + continue manager.register(NodeReplace( - new_node_id=entry.get("new_node_id", ""), + new_node_id=new_node_id, old_node_id=entry.get("old_node_id", old_node_id), old_widget_ids=entry.get("old_widget_ids"), input_mapping=entry.get("input_mapping"), @@ -203,6 +207,20 @@ class TestLoadNodeReplacementsJson(unittest.TestCase): self.assertEqual(registered.input_mapping[0]["set_value"], "lanczos") self.assertEqual(registered.input_mapping[1]["old_id"], "dimension") + def test_missing_new_node_id_skipped(self): + """Entry without new_node_id is skipped.""" + self._write_json({ + "OldNode": [ + {"old_node_id": "OldNode"}, + {"new_node_id": "", "old_node_id": "OldNode"}, + {"new_node_id": "ValidNew", "old_node_id": "OldNode"}, + ] + }) + self._load() + self.assertEqual(self.mock_manager.register.call_count, 1) + registered = self.mock_manager.register.call_args[0][0] + self.assertEqual(registered.new_node_id, "ValidNew") + def test_non_dict_entry_skipped(self): """Non-dict entries in the list are silently skipped.""" self._write_json({