Add NodePackage

This commit is contained in:
huchenlei 2024-12-20 14:09:07 -08:00
parent f8e5521b50
commit 9d1ef85af8
7 changed files with 108 additions and 91 deletions

1
.gitignore vendored
View File

@ -17,3 +17,4 @@ github-stats-cache.json
pip_overrides.json
*.json
check2.sh
/venv/

View File

@ -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]

View File

@ -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()

View File

@ -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
"""

70
glob/node_package.py Normal file
View File

@ -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
)

View File

@ -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])) {

View File

@ -6,3 +6,4 @@ huggingface-hub>0.20
typer
rich
typing-extensions
toml