Compare commits
5 Commits
attach_nod
...
rework_ver
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9503c34d5b | ||
|
|
19be1f85da | ||
|
|
4e44c26beb | ||
|
|
9d1ef85af8 | ||
|
|
f8e5521b50 |
1
.gitignore
vendored
1
.gitignore
vendored
@@ -17,3 +17,4 @@ github-stats-cache.json
|
||||
pip_overrides.json
|
||||
*.json
|
||||
check2.sh
|
||||
/venv/
|
||||
@@ -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]
|
||||
|
||||
@@ -206,7 +206,7 @@ def checkout_custom_node_hash(git_custom_node_infos):
|
||||
repo_name_to_url[repo_name] = url
|
||||
|
||||
for path in os.listdir(working_directory):
|
||||
if '@' in path or path.endswith("ComfyUI-Manager"):
|
||||
if path.endswith("ComfyUI-Manager"):
|
||||
continue
|
||||
|
||||
fullpath = os.path.join(working_directory, path)
|
||||
@@ -467,5 +467,5 @@ try:
|
||||
except Exception as e:
|
||||
print(e)
|
||||
sys.exit(-1)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
32
glob/git_utils.py
Normal file
32
glob/git_utils.py
Normal file
@@ -0,0 +1,32 @@
|
||||
import os
|
||||
|
||||
import git
|
||||
|
||||
|
||||
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
|
||||
|
||||
|
||||
def get_commit_hash(fullpath):
|
||||
git_head = os.path.join(fullpath, '.git', 'HEAD')
|
||||
if os.path.exists(git_head):
|
||||
with open(git_head) as f:
|
||||
line = f.readline()
|
||||
|
||||
if line.startswith("ref: "):
|
||||
ref = os.path.join(fullpath, '.git', line[5:].strip())
|
||||
if os.path.exists(ref):
|
||||
with open(ref) as f2:
|
||||
return f2.readline().strip()
|
||||
else:
|
||||
return "unknown"
|
||||
else:
|
||||
return line
|
||||
|
||||
return "unknown"
|
||||
@@ -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]
|
||||
@@ -104,13 +105,13 @@ def check_invalid_nodes():
|
||||
if subdir in ['.disabled', '__pycache__']:
|
||||
continue
|
||||
|
||||
if '@' in subdir:
|
||||
spec = subdir.split('@')
|
||||
if spec[1] in ['unknown', 'nightly']:
|
||||
continue
|
||||
|
||||
if not os.path.exists(os.path.join(root, subdir, '.tracking')):
|
||||
invalid_nodes[spec[0]] = os.path.join(root, subdir)
|
||||
package = unified_manager.installed_node_packages.get(subdir)
|
||||
if not package:
|
||||
continue
|
||||
|
||||
if not package.isValid():
|
||||
invalid_nodes[subdir] = package.fullpath
|
||||
|
||||
node_paths = folder_paths.get_folder_paths("custom_nodes")
|
||||
for x in node_paths:
|
||||
@@ -308,27 +309,10 @@ class ManagedResult:
|
||||
return self
|
||||
|
||||
|
||||
def get_commit_hash(fullpath):
|
||||
git_head = os.path.join(fullpath, '.git', 'HEAD')
|
||||
if os.path.exists(git_head):
|
||||
with open(git_head) as f:
|
||||
line = f.readline()
|
||||
|
||||
if line.startswith("ref: "):
|
||||
ref = os.path.join(fullpath, '.git', line[5:].strip())
|
||||
if os.path.exists(ref):
|
||||
with open(ref) as f2:
|
||||
return f2.readline().strip()
|
||||
else:
|
||||
return "unknown"
|
||||
else:
|
||||
return line
|
||||
|
||||
return "unknown"
|
||||
|
||||
|
||||
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 +446,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:
|
||||
# NOTE: unknown package does not have a url.
|
||||
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:
|
||||
# NOTE: unknown package does not have a url.
|
||||
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 +638,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 +647,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):
|
||||
@@ -890,6 +806,7 @@ class UnifiedManager:
|
||||
|
||||
zip_url = node_info.download_url
|
||||
from_path = self.active_nodes[node_id][1]
|
||||
# PTAL(@ltdrdata): how to redesign and drop version_spec here?
|
||||
target = f"{node_id}@{version_spec.replace('.', '_')}"
|
||||
to_path = os.path.join(get_default_custom_nodes_path(), target)
|
||||
|
||||
@@ -957,6 +874,7 @@ class UnifiedManager:
|
||||
os.rmdir(x)
|
||||
|
||||
# 5. rename dir name <node_id>@<prev_ver> ==> <node_id>@<cur_ver>
|
||||
# PTAL(@ltdrdata): how to redesign and drop version_spec here
|
||||
new_install_path = os.path.join(get_default_custom_nodes_path(), f"{node_id}@{version_spec.replace('.', '_')}")
|
||||
print(f"'{install_path}' is moved to '{new_install_path}'")
|
||||
shutil.move(install_path, new_install_path)
|
||||
@@ -1038,6 +956,7 @@ class UnifiedManager:
|
||||
|
||||
from_path = cnr_info[version_spec]
|
||||
base_path = extract_base_custom_nodes_dir(from_path)
|
||||
# PTAL(@ltdrdata): how to redesign and drop version_spec here
|
||||
to_path = os.path.join(base_path, f"{node_id}@{version_spec.replace('.', '_')}")
|
||||
|
||||
if from_path is None or not os.path.exists(from_path):
|
||||
@@ -1099,6 +1018,7 @@ class UnifiedManager:
|
||||
return result.fail(f'Specified active node not exists: {node_id}')
|
||||
|
||||
base_path = extract_base_custom_nodes_dir(ver_and_path[1])
|
||||
# PTAL(@ltdrdata): how to redesign and drop version_spec here
|
||||
to_path = os.path.join(base_path, '.disabled', f"{node_id}@{ver_and_path[0].replace('.', '_')}")
|
||||
shutil.move(ver_and_path[1], to_path)
|
||||
result.append((ver_and_path[1], to_path))
|
||||
@@ -1112,7 +1032,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
|
||||
"""
|
||||
@@ -1189,6 +1109,7 @@ class UnifiedManager:
|
||||
os.remove(download_path)
|
||||
|
||||
# install_path
|
||||
# PTAL(@ltdrdata): how to redesign and drop version_spec here
|
||||
install_path = os.path.join(get_default_custom_nodes_path(), f"{node_id}@{version_spec.replace('.', '_')}")
|
||||
if os.path.exists(install_path):
|
||||
return result.fail(f'Install path already exists: {install_path}')
|
||||
@@ -1375,6 +1296,7 @@ class UnifiedManager:
|
||||
if version_spec == 'unknown':
|
||||
to_path = os.path.abspath(os.path.join(get_default_custom_nodes_path(), node_id)) # don't attach @unknown
|
||||
else:
|
||||
# PTAL(@ltdrdata): how to redesign and drop version_spec here
|
||||
to_path = os.path.abspath(os.path.join(get_default_custom_nodes_path(), f"{node_id}@{version_spec.replace('.', '_')}"))
|
||||
res = self.repo_install(repo_url, to_path, instant_execution=instant_execution, no_deps=no_deps, return_postinstall=return_postinstall)
|
||||
if res.result:
|
||||
|
||||
@@ -544,37 +544,12 @@ def populate_markdown(x):
|
||||
|
||||
@routes.get("/customnode/installed")
|
||||
async def installed_list(request):
|
||||
result = {}
|
||||
for x in folder_paths.get_folder_paths('custom_nodes'):
|
||||
for module_name in os.listdir(x):
|
||||
if (
|
||||
module_name.endswith('.disabled') or
|
||||
module_name == '__pycache__' or
|
||||
module_name.endswith('.py') or
|
||||
module_name.endswith('.example') or
|
||||
module_name.endswith('.pyc')
|
||||
):
|
||||
continue
|
||||
unified_manager = core.unified_manager
|
||||
|
||||
spec = module_name.split('@')
|
||||
|
||||
if len(spec) == 2:
|
||||
node_package_name = spec[0]
|
||||
ver = spec[1].replace('_', '.')
|
||||
|
||||
if ver == 'nightly':
|
||||
ver = None
|
||||
else:
|
||||
node_package_name = module_name
|
||||
ver = None
|
||||
|
||||
# extract commit hash
|
||||
if ver is None:
|
||||
ver = core.get_commit_hash(os.path.join(x, node_package_name))
|
||||
|
||||
result[node_package_name] = ver
|
||||
|
||||
return web.json_response(result, content_type='application/json')
|
||||
return web.json_response({
|
||||
node_id: package.version if package.is_from_cnr else package.get_commit_hash()
|
||||
for node_id, package in unified_manager.installed_node_packages.items()
|
||||
}, content_type='application/json')
|
||||
|
||||
|
||||
@routes.get("/customnode/getlist")
|
||||
|
||||
79
glob/node_package.py
Normal file
79
glob/node_package.py
Normal file
@@ -0,0 +1,79 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from dataclasses import dataclass
|
||||
import os
|
||||
|
||||
import toml
|
||||
|
||||
from git_utils import is_git_repo, get_commit_hash
|
||||
|
||||
|
||||
@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
|
||||
|
||||
def get_commit_hash(self) -> str:
|
||||
return get_commit_hash(self.fullpath)
|
||||
|
||||
def isValid(self) -> bool:
|
||||
if self.is_from_cnr:
|
||||
return os.path.exists(os.path.join(self.fullpath, '.tracking'))
|
||||
|
||||
return True
|
||||
|
||||
@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
|
||||
)
|
||||
@@ -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])) {
|
||||
|
||||
@@ -5,4 +5,5 @@ transformers
|
||||
huggingface-hub>0.20
|
||||
typer
|
||||
rich
|
||||
typing-extensions
|
||||
typing-extensions
|
||||
toml
|
||||
Reference in New Issue
Block a user