From 6797e20bcf94c2d53fa7183d34a23b304f0965a4 Mon Sep 17 00:00:00 2001 From: bymyself Date: Thu, 5 Feb 2026 18:17:28 -0800 Subject: [PATCH 1/2] feat: switch SubgraphManager to index.json-driven discovery with distribution filtering Amp-Thread-ID: https://ampcode.com/threads/T-019c30d2-a605-708d-824f-35e8f3a0c2f5 --- app/subgraph_manager.py | 48 +++++++++++++++++++++++++++++++++++++++-- 1 file changed, 46 insertions(+), 2 deletions(-) diff --git a/app/subgraph_manager.py b/app/subgraph_manager.py index 6a8f586a4..ceabf8a0f 100644 --- a/app/subgraph_manager.py +++ b/app/subgraph_manager.py @@ -1,6 +1,8 @@ from __future__ import annotations from typing import TypedDict +import json +import logging import os import folder_paths import glob @@ -40,6 +42,7 @@ class SubgraphManager: def __init__(self): self.cached_custom_node_subgraphs: dict[SubgraphEntry] | None = None self.cached_blueprint_subgraphs: dict[SubgraphEntry] | None = None + self.distribution = os.environ.get("DISTRIBUTION", "localhost") def _create_entry(self, file: str, source: str, node_pack: str) -> tuple[str, SubgraphEntry]: """Create a subgraph entry from a file path. Expects normalized path (forward slashes).""" @@ -90,15 +93,56 @@ class SubgraphManager: return subgraphs_dict async def get_blueprint_subgraphs(self, force_reload=False): - """Load subgraphs from the blueprints directory.""" + """Load subgraphs from the blueprints directory using index.json for discovery.""" if not force_reload and self.cached_blueprint_subgraphs is not None: return self.cached_blueprint_subgraphs subgraphs_dict: dict[SubgraphEntry] = {} blueprints_dir = os.path.join(os.path.dirname(os.path.dirname(__file__)), 'blueprints') - if os.path.exists(blueprints_dir): + index_path = os.path.join(blueprints_dir, "index.json") + if os.path.isfile(index_path): + try: + with open(index_path, "r", encoding="utf-8") as f: + categories = json.load(f) + except (json.JSONDecodeError, OSError) as e: + logging.error("Failed to load blueprint index %s: %s", index_path, e) + categories = [] + + if not isinstance(categories, list): + logging.error("Blueprint index.json is not a list: %s", index_path) + categories = [] + + for category in categories: + module_name = category.get("moduleName", "default") + for blueprint in category.get("blueprints", []): + name = blueprint.get("name") + if not name: + logging.warning("Blueprint entry missing 'name' in category '%s', skipping", module_name) + continue + + include_on = blueprint.get("includeOnDistributions") + if include_on is not None and self.distribution not in include_on: + continue + + file_by_dist = blueprint.get("fileByDistribution", {}) + filename = file_by_dist.get(self.distribution, f"{name}.json") + filepath = os.path.realpath(os.path.join(blueprints_dir, filename)) + if not filepath.startswith(os.path.realpath(blueprints_dir) + os.sep): + logging.warning("Blueprint path escapes blueprints directory: %s", filepath) + continue + + if not os.path.isfile(filepath): + logging.warning("Blueprint file not found: %s", filepath) + continue + + entry_id, entry = self._create_entry(filepath, Source.templates, module_name) + subgraphs_dict[entry_id] = entry + elif os.path.exists(blueprints_dir): + logging.warning("No blueprint index.json found at %s, falling back to glob", index_path) for file in glob.glob(os.path.join(blueprints_dir, "*.json")): + if os.path.basename(file) == "index.json": + continue file = file.replace('\\', '/') entry_id, entry = self._create_entry(file, Source.templates, "comfyui") subgraphs_dict[entry_id] = entry From 44dbb093c305f9dae377447b740f16e722556a1b Mon Sep 17 00:00:00 2001 From: bymyself Date: Fri, 20 Feb 2026 02:21:55 -0800 Subject: [PATCH 2/2] feat: pass filtering fields to frontend instead of server-side filtering Remove server-side includeOnDistributions filtering and fileByDistribution resolution. Instead, forward requiresCustomNodes and includeOnDistributions in the info object so the frontend handles filtering client-side. Amp-Thread-ID: https://ampcode.com/threads/T-019c6f43-6212-7308-bea6-bfc35a486cbf --- app/subgraph_manager.py | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/app/subgraph_manager.py b/app/subgraph_manager.py index ceabf8a0f..d32dee7a9 100644 --- a/app/subgraph_manager.py +++ b/app/subgraph_manager.py @@ -42,7 +42,6 @@ class SubgraphManager: def __init__(self): self.cached_custom_node_subgraphs: dict[SubgraphEntry] | None = None self.cached_blueprint_subgraphs: dict[SubgraphEntry] | None = None - self.distribution = os.environ.get("DISTRIBUTION", "localhost") def _create_entry(self, file: str, source: str, node_pack: str) -> tuple[str, SubgraphEntry]: """Create a subgraph entry from a file path. Expects normalized path (forward slashes).""" @@ -121,12 +120,7 @@ class SubgraphManager: logging.warning("Blueprint entry missing 'name' in category '%s', skipping", module_name) continue - include_on = blueprint.get("includeOnDistributions") - if include_on is not None and self.distribution not in include_on: - continue - - file_by_dist = blueprint.get("fileByDistribution", {}) - filename = file_by_dist.get(self.distribution, f"{name}.json") + filename = f"{name}.json" filepath = os.path.realpath(os.path.join(blueprints_dir, filename)) if not filepath.startswith(os.path.realpath(blueprints_dir) + os.sep): logging.warning("Blueprint path escapes blueprints directory: %s", filepath) @@ -137,6 +131,13 @@ class SubgraphManager: continue entry_id, entry = self._create_entry(filepath, Source.templates, module_name) + info = entry["info"] + include_on = blueprint.get("includeOnDistributions") + if include_on is not None: + info["includeOnDistributions"] = include_on + requires = blueprint.get("requiresCustomNodes") + if requires is not None: + info["requiresCustomNodes"] = requires subgraphs_dict[entry_id] = entry elif os.path.exists(blueprints_dir): logging.warning("No blueprint index.json found at %s, falling back to glob", index_path)