From 9d1ef85af82758d238ae118fb6f210aba3ded6b8 Mon Sep 17 00:00:00 2001 From: huchenlei Date: Fri, 20 Dec 2024 14:09:07 -0800 Subject: [PATCH] Add NodePackage --- .gitignore | 1 + cm-cli.py | 2 +- git_helper.py | 14 +++++- glob/manager_core.py | 107 ++++++++-------------------------------- glob/node_package.py | 70 ++++++++++++++++++++++++++ js/workflow-metadata.js | 2 +- requirements.txt | 3 +- 7 files changed, 108 insertions(+), 91 deletions(-) create mode 100644 glob/node_package.py diff --git a/.gitignore b/.gitignore index 004ddb2a..33ee743b 100644 --- a/.gitignore +++ b/.gitignore @@ -17,3 +17,4 @@ github-stats-cache.json pip_overrides.json *.json check2.sh +/venv/ \ No newline at end of file diff --git a/cm-cli.py b/cm-cli.py index d7111716..9e86ddf6 100644 --- a/cm-cli.py +++ b/cm-cli.py @@ -224,7 +224,7 @@ def fix_node(node_spec_str, is_all=False, cnt_msg=''): print(f"ERROR: f{res.msg}") -def uninstall_node(node_spec_str, is_all=False, cnt_msg=''): +def uninstall_node(node_spec_str: str, is_all: bool = False, cnt_msg: str = ''): spec = node_spec_str.split('@') if len(spec) == 2 and spec[1] == 'unknown': node_name = spec[0] diff --git a/git_helper.py b/git_helper.py index a709811a..cb5b5b43 100644 --- a/git_helper.py +++ b/git_helper.py @@ -437,6 +437,16 @@ def setup_environment(): git.Git().update_environment(GIT_PYTHON_GIT_EXECUTABLE=config['default']['git_exe']) +def is_git_repo(path: str) -> bool: + """ Check if the path is a git repository. """ + try: + # Try to create a Repo object from the path + _ = git.Repo(path).git_dir + return True + except git.exc.InvalidGitRepositoryError: + return False + + setup_environment() @@ -467,5 +477,5 @@ try: except Exception as e: print(e) sys.exit(-1) - - + + diff --git a/glob/manager_core.py b/glob/manager_core.py index fe22c5e6..1abe276f 100644 --- a/glob/manager_core.py +++ b/glob/manager_core.py @@ -32,6 +32,7 @@ import cm_global import cnr_utils import manager_util import manager_downloader +from node_package import InstalledNodePackage version_code = [3, 1] @@ -329,6 +330,8 @@ def get_commit_hash(fullpath): class UnifiedManager: def __init__(self): + self.installed_node_packages: dict[str, InstalledNodePackage] = {} + self.cnr_inactive_nodes = {} # node_id -> node_version -> fullpath self.nightly_inactive_nodes = {} # node_id -> fullpath self.unknown_inactive_nodes = {} # node_id -> repo url * fullpath @@ -462,94 +465,26 @@ class UnifiedManager: else: return "unknown" - def resolve_id_from_repo(self, fullpath): - git_config_path = os.path.join(fullpath, '.git', 'config') + def update_cache_at_path(self, fullpath): + node_package = InstalledNodePackage.from_fullpath(fullpath) + self.installed_node_packages[node_package.id] = node_package - if not os.path.exists(git_config_path): - return None + if node_package.is_disabled and node_package.is_unknown: + # TODO: figure out where url is used. + self.unknown_inactive_nodes[node_package.id] = ('', node_package.fullpath) - config = configparser.ConfigParser() - config.read(git_config_path) + if node_package.is_disabled and node_package.is_nightly: + self.nightly_inactive_nodes[node_package.id] = node_package.fullpath - for k, v in config.items(): - if k.startswith('remote ') and 'url' in v: - cnr = self.get_cnr_by_repo(v['url']) - if cnr: - return "nightly", cnr['id'], v['url'] - else: - return "unknown", v['url'].split('/')[-1], v['url'] + if node_package.is_enabled: + self.active_nodes[node_package.id] = node_package.version, node_package.fullpath - def resolve_unknown(self, node_id, fullpath): - res = self.resolve_id_from_repo(fullpath) + if node_package.is_enabled and node_package.is_unknown: + # TODO: figure out where url is used. + self.unknown_active_nodes[node_package.id] = ('', node_package.fullpath) - if res is None: - self.unknown_inactive_nodes[node_id] = '', fullpath - return - - ver_spec, node_id, url = res - - if ver_spec == 'nightly': - self.nightly_inactive_nodes[node_id] = fullpath - else: - self.unknown_inactive_nodes[node_id] = url, fullpath - - def update_cache_at_path(self, fullpath, is_disabled): - name = os.path.basename(fullpath) - - if name.endswith(".disabled"): - node_spec = name[:-9] - is_disabled = True - else: - node_spec = name - - if '@' in node_spec: - node_spec = node_spec.split('@') - node_id = node_spec[0] - if node_id is None: - node_version = 'unknown' - else: - node_version = node_spec[1].replace("_", ".") - - if node_version != 'unknown': - if node_id not in self.cnr_map: - # fallback - v = node_version - - self.cnr_map[node_id] = { - 'id': node_id, - 'name': node_id, - 'latest_version': {'version': v}, - 'publisher': {'id': 'N/A', 'name': 'N/A'} - } - - elif node_version == 'unknown': - res = self.resolve_id_from_repo(fullpath) - if res is None: - print(f"Custom node unresolved: {fullpath}") - return - - node_version, node_id, _ = res - else: - res = self.resolve_id_from_repo(fullpath) - if res is None: - print(f"Custom node unresolved: {fullpath}") - return - - node_version, node_id, _ = res - - if not is_disabled: - # active nodes - if node_version == 'unknown': - self.unknown_active_nodes[node_id] = node_version, fullpath - else: - self.active_nodes[node_id] = node_version, fullpath - else: - if node_version == 'unknown': - self.resolve_unknown(node_id, fullpath) - elif node_version == 'nightly': - self.nightly_inactive_nodes[node_id] = fullpath - else: - self.add_to_cnr_inactive_nodes(node_id, node_version, fullpath) + if node_package.is_from_cnr and node_package.is_disabled: + self.add_to_cnr_inactive_nodes(node_package.id, node_package.version, node_package.fullpath) def is_updatable(self, node_id): cur_ver = self.get_cnr_active_version(node_id) @@ -722,7 +657,7 @@ class UnifiedManager: fullpath = os.path.join(custom_nodes_path, x) if os.path.isdir(fullpath): if x not in ['__pycache__', '.disabled']: - self.update_cache_at_path(fullpath, is_disabled=False) + self.update_cache_at_path(fullpath) # reload node status info from custom_nodes/.disabled/* for custom_nodes_path in folder_paths.get_folder_paths('custom_nodes'): @@ -731,7 +666,7 @@ class UnifiedManager: for x in os.listdir(disabled_dir): fullpath = os.path.join(disabled_dir, x) if os.path.isdir(fullpath): - self.update_cache_at_path(fullpath, is_disabled=True) + self.update_cache_at_path(fullpath) @staticmethod async def load_nightly(channel, mode): @@ -1112,7 +1047,7 @@ class UnifiedManager: return result - def unified_uninstall(self, node_id, is_unknown): + def unified_uninstall(self, node_id: str, is_unknown: bool): """ Remove whole installed custom nodes including inactive nodes """ diff --git a/glob/node_package.py b/glob/node_package.py new file mode 100644 index 00000000..583631f4 --- /dev/null +++ b/glob/node_package.py @@ -0,0 +1,70 @@ +from __future__ import annotations + +from dataclasses import dataclass +import os + +import toml + +from git_helper import is_git_repo + + +@dataclass +class InstalledNodePackage: + """Information about an installed node package.""" + + id: str + fullpath: str + disabled: bool + version: str + + @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 and not self.is_nightly + + @property + def is_enabled(self) -> bool: + return not self.disabled + + @property + def is_disabled(self) -> bool: + return self.disabled + + @staticmethod + def from_fullpath(fullpath: str) -> InstalledNodePackage: + parent_folder_name = os.path.split(fullpath)[-2] + module_name = os.path.basename(fullpath) + pyproject_toml_path = os.path.join(fullpath, "pyproject.toml") + + if module_name.endswith(".disabled"): + node_id = module_name[:-9] + disabled = True + elif parent_folder_name == ".disabled": + # Nodes under custom_nodes/.disabled/* are disabled + node_id = module_name + disabled = True + else: + node_id = module_name + disabled = False + + if is_git_repo(fullpath): + version = "nightly" + elif os.path.exists(pyproject_toml_path): + # Read project.toml to get the version + with open(pyproject_toml_path, "r", encoding="utf-8") as f: + pyproject_toml = toml.load(f) + # Fallback to 'unknown' if project.version doesn't exist + version = pyproject_toml.get("project", {}).get("version", "unknown") + else: + version = "unknown" + + return InstalledNodePackage( + id=node_id, fullpath=fullpath, disabled=disabled, version=version + ) diff --git a/js/workflow-metadata.js b/js/workflow-metadata.js index 20d03df6..7ebd2e53 100644 --- a/js/workflow-metadata.js +++ b/js/workflow-metadata.js @@ -47,7 +47,7 @@ class WorkflowMetadataExtension { const modules = nodeData.python_module.split("."); if (modules[0] === "custom_nodes") { - const nodePackageName = modules[1].split("@")[0]; + const nodePackageName = modules[1]; const nodeVersion = this.installedNodeVersions[nodePackageName]; nodeVersions[nodePackageName] = nodeVersion; } else if (["nodes", "comfy_extras"].includes(modules[0])) { diff --git a/requirements.txt b/requirements.txt index 89f93b41..ee4b90e4 100644 --- a/requirements.txt +++ b/requirements.txt @@ -5,4 +5,5 @@ transformers huggingface-hub>0.20 typer rich -typing-extensions \ No newline at end of file +typing-extensions +toml \ No newline at end of file