Compare commits
2
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7bc156a819 | ||
|
|
7a1f89ab15 |
@@ -2,6 +2,13 @@
|
||||
|
||||
All notable changes to ComfyUI-AusBoss are documented here.
|
||||
|
||||
## Unreleased
|
||||
|
||||
- **ComfyUI starts when the pack is installed twice.** Manager installs it in
|
||||
`custom_nodes/ausboss-nodes` and a git clone in `custom_nodes/ComfyUI-AusBoss`.
|
||||
With both there, ComfyUI used to stop at startup. Now the second copy loads
|
||||
nothing, and the console names both folders so you can delete one.
|
||||
|
||||
## 2.23.0 - 2026-11-09
|
||||
|
||||
- **Mask by Touch keeps your picks when the picture changes.** Your clicks
|
||||
|
||||
@@ -42,6 +42,8 @@ git clone https://github.com/ausboss/ComfyUI-AusBoss.git
|
||||
|
||||
Restart ComfyUI and search the node library for **AusBoss**.
|
||||
|
||||
Install it one way, not both. Manager puts the pack in `custom_nodes/ausboss-nodes` and a clone in `custom_nodes/ComfyUI-AusBoss`. They are the same pack, so if both folders are there, delete one. The console says which copy it skipped.
|
||||
|
||||
After an update, through Manager or `git pull`, restart ComfyUI and hard-refresh the browser with **Ctrl+Shift+R**. Otherwise the browser keeps the old JavaScript, and the pack warns you about it. Manager can install a version or two behind while the Comfy Registry reviews a new release. To get the newest one, pick it in Manager's version list, or update by hand: delete the pack's folder in `ComfyUI/custom_nodes` and clone the repository as shown above.
|
||||
|
||||
The pack uses the Pillow, NumPy, Torch and PyAV that come with ComfyUI. Each example lists the models it needs on its Workflow Note. LaMa Inpaint also needs [`big-lama.pt`](https://github.com/Sanster/models/releases/download/add_big_lama/big-lama.pt) in `ComfyUI/models/lama/`. The pack never downloads weights by itself.
|
||||
|
||||
+33
-5
@@ -7,12 +7,26 @@ name to NODE_MODULES below — nothing else to wire up.
|
||||
|
||||
A module that fails to import is skipped with a console warning instead
|
||||
of taking the whole pack down, so one broken node never hides the rest.
|
||||
|
||||
Only one copy of the pack loads. People install it from ComfyUI-Manager
|
||||
(custom_nodes/ausboss-nodes) and from GitHub (custom_nodes/ComfyUI-AusBoss),
|
||||
and two copies used to register the same server routes, which stopped
|
||||
ComfyUI at startup. A second copy now loads nothing and says which folder
|
||||
to delete.
|
||||
"""
|
||||
|
||||
import importlib
|
||||
import os
|
||||
import sys
|
||||
import tomllib
|
||||
import traceback
|
||||
import types
|
||||
|
||||
_HERE = os.path.dirname(os.path.abspath(__file__))
|
||||
_FIRST_COPY = sys.modules.setdefault(
|
||||
"_ausboss_nodes_first_copy", types.SimpleNamespace(folder=_HERE)
|
||||
)
|
||||
_IS_SECOND_COPY = _FIRST_COPY.folder != _HERE
|
||||
|
||||
# One entry per file in nodes/, kept alphabetical. The order has no
|
||||
# functional effect.
|
||||
@@ -64,6 +78,8 @@ NODE_DISPLAY_NAME_MAPPINGS = {}
|
||||
_failed_modules = []
|
||||
|
||||
for _module_name in NODE_MODULES:
|
||||
if _IS_SECOND_COPY:
|
||||
break
|
||||
try:
|
||||
_module = importlib.import_module(f".nodes.{_module_name}", __name__)
|
||||
NODE_CLASS_MAPPINGS.update(getattr(_module, "NODE_CLASS_MAPPINGS", {}))
|
||||
@@ -76,15 +92,15 @@ for _module_name in NODE_MODULES:
|
||||
|
||||
# Folder ComfyUI serves to the browser; every .js file in it is loaded as a
|
||||
# frontend extension. Shared modules use .mjs so they are import-only.
|
||||
WEB_DIRECTORY = "./js"
|
||||
WEB_DIRECTORY = None if _IS_SECOND_COPY else "./js"
|
||||
|
||||
__all__ = ["NODE_CLASS_MAPPINGS", "NODE_DISPLAY_NAME_MAPPINGS", "WEB_DIRECTORY"]
|
||||
|
||||
|
||||
def _read_version():
|
||||
def _read_version(folder=None):
|
||||
"""Version straight from pyproject.toml so it is never duplicated."""
|
||||
try:
|
||||
pyproject = os.path.join(os.path.dirname(__file__), "pyproject.toml")
|
||||
pyproject = os.path.join(folder or os.path.dirname(__file__), "pyproject.toml")
|
||||
with open(pyproject, "rb") as f:
|
||||
version = tomllib.load(f).get("project", {}).get("version")
|
||||
return version if isinstance(version, str) and version.strip() else "unknown"
|
||||
@@ -102,6 +118,17 @@ def _print_banner():
|
||||
bar = f"{teal}{'-' * 70}{reset}"
|
||||
|
||||
print(bar)
|
||||
if _IS_SECOND_COPY:
|
||||
def where(folder):
|
||||
text = f"{os.path.basename(folder)} (v{_read_version(folder)})"
|
||||
return text.encode("ascii", "replace").decode("ascii")
|
||||
|
||||
print(f" {red}AusBoss is installed twice, so this copy is not loaded.{reset}")
|
||||
print(f" In use: custom_nodes/{where(_FIRST_COPY.folder)}")
|
||||
print(f" Skipped: custom_nodes/{where(_HERE)}")
|
||||
print(" Delete one of the two folders and restart ComfyUI.")
|
||||
print(bar)
|
||||
return
|
||||
print(
|
||||
f" {teal}AusBoss{reset} v{_read_version()} | "
|
||||
f"{len(NODE_CLASS_MAPPINGS)} nodes loaded"
|
||||
@@ -113,9 +140,10 @@ def _print_banner():
|
||||
|
||||
|
||||
try:
|
||||
from .nodes._frontend_cache import register_frontend_cache_guard
|
||||
if not _IS_SECOND_COPY:
|
||||
from .nodes._frontend_cache import register_frontend_cache_guard
|
||||
|
||||
register_frontend_cache_guard()
|
||||
register_frontend_cache_guard()
|
||||
except Exception:
|
||||
traceback.print_exc()
|
||||
|
||||
|
||||
@@ -0,0 +1,128 @@
|
||||
"""A second install of the pack loads nothing, so ComfyUI still starts.
|
||||
|
||||
People install the pack from ComfyUI-Manager (custom_nodes/ausboss-nodes) and
|
||||
from GitHub (custom_nodes/ComfyUI-AusBoss). With both folders present, the two
|
||||
copies registered the same server routes, and ComfyUI stopped at startup with
|
||||
"Added route will never be executed, method HEAD is already registered".
|
||||
|
||||
This loads the pack twice, from two folders, in one fresh standard-library
|
||||
Python with the same stand-ins as tests/test_node_api.py. The second copy must
|
||||
add no routes, no nodes and no browser files, and must name both folders.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from pathlib import Path
|
||||
import contextlib
|
||||
import importlib.util
|
||||
import io
|
||||
import json
|
||||
import subprocess
|
||||
import sys
|
||||
import tempfile
|
||||
import types
|
||||
import unittest
|
||||
|
||||
ROOT = Path(__file__).resolve().parent.parent
|
||||
ROUTES: list[tuple[str, str]] = []
|
||||
|
||||
|
||||
def stub_server():
|
||||
"""A PromptServer whose route table records every route, the way aiohttp will add them."""
|
||||
|
||||
class Routes:
|
||||
def __getattr__(self, method):
|
||||
def register(path, *_args, **_kwargs):
|
||||
ROUTES.append((method.upper(), path))
|
||||
return lambda handler: handler
|
||||
|
||||
return register
|
||||
|
||||
class Instance:
|
||||
routes = Routes()
|
||||
|
||||
def __getattr__(self, _name):
|
||||
return lambda *_args, **_kwargs: None
|
||||
|
||||
module = types.ModuleType("server")
|
||||
module.PromptServer = type("PromptServer", (), {"instance": Instance()})
|
||||
sys.modules["server"] = module
|
||||
|
||||
|
||||
def load_copy(name: str, folder: Path):
|
||||
spec = importlib.util.spec_from_file_location(
|
||||
name, folder / "__init__.py", submodule_search_locations=[str(folder)]
|
||||
)
|
||||
pack = importlib.util.module_from_spec(spec)
|
||||
sys.modules[name] = pack
|
||||
out = io.StringIO()
|
||||
with contextlib.redirect_stdout(out):
|
||||
spec.loader.exec_module(pack)
|
||||
return pack, out.getvalue()
|
||||
|
||||
|
||||
def child(tmp: Path, report: Path):
|
||||
sys.path.insert(0, str(ROOT / "tests"))
|
||||
from test_node_api import StandInFinder, StandInModule
|
||||
|
||||
sys.meta_path.insert(0, StandInFinder())
|
||||
sys.modules["aiohttp"] = StandInModule("aiohttp") # route helpers import aiohttp.web first
|
||||
stub_server()
|
||||
# The two folder names people end up with: a git clone and Manager's install.
|
||||
first, second = tmp / "ComfyUI-AusBoss", tmp / "ausboss-nodes"
|
||||
first.symlink_to(ROOT, target_is_directory=True)
|
||||
second.symlink_to(ROOT, target_is_directory=True)
|
||||
|
||||
first_pack, _ = load_copy("ComfyUI-AusBoss", first)
|
||||
first_routes = list(ROUTES)
|
||||
second_pack, second_out = load_copy("ausboss-nodes", second)
|
||||
report.write_text(json.dumps({
|
||||
"first_nodes": len(first_pack.NODE_CLASS_MAPPINGS),
|
||||
"first_web": first_pack.WEB_DIRECTORY,
|
||||
"first_routes": len(first_routes),
|
||||
"second_nodes": len(second_pack.NODE_CLASS_MAPPINGS),
|
||||
"second_web": second_pack.WEB_DIRECTORY,
|
||||
"second_routes": len(ROUTES) - len(first_routes),
|
||||
"duplicates": sorted({f"{m} {p}" for m, p in ROUTES if ROUTES.count((m, p)) > 1}),
|
||||
"second_out": second_out,
|
||||
}), encoding="utf-8")
|
||||
|
||||
|
||||
class SecondCopyTest(unittest.TestCase):
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
with tempfile.TemporaryDirectory() as tmp:
|
||||
report = Path(tmp) / "report.json"
|
||||
result = subprocess.run(
|
||||
[sys.executable, "-S", str(Path(__file__).resolve()), "--child", tmp, str(report)],
|
||||
cwd=ROOT, capture_output=True, text=True, timeout=120,
|
||||
)
|
||||
if result.returncode or not report.exists():
|
||||
raise AssertionError(f"Loading two copies failed:\n{result.stdout}\n{result.stderr}")
|
||||
cls.found = json.loads(report.read_text(encoding="utf-8"))
|
||||
|
||||
def test_first_copy_loads_as_usual(self):
|
||||
self.assertGreater(self.found["first_nodes"], 0)
|
||||
self.assertGreater(self.found["first_routes"], 0)
|
||||
self.assertEqual(self.found["first_web"], "./js")
|
||||
|
||||
def test_second_copy_adds_no_routes(self):
|
||||
self.assertEqual(self.found["duplicates"], [])
|
||||
self.assertEqual(self.found["second_routes"], 0)
|
||||
|
||||
def test_second_copy_adds_no_nodes_or_browser_files(self):
|
||||
self.assertEqual(self.found["second_nodes"], 0)
|
||||
self.assertIsNone(self.found["second_web"])
|
||||
|
||||
def test_second_copy_names_both_folders(self):
|
||||
out = self.found["second_out"]
|
||||
self.assertIn("ComfyUI-AusBoss", out)
|
||||
self.assertIn("ausboss-nodes", out)
|
||||
out.encode("ascii") # cp1252 consoles: the message must be plain ASCII
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
if len(sys.argv) == 4 and sys.argv[1] == "--child":
|
||||
child(Path(sys.argv[2]), Path(sys.argv[3]))
|
||||
else:
|
||||
unittest.main()
|
||||
Reference in New Issue
Block a user