One declaration does three jobs: renders the panel, carries the defaults, names
the values that come back. A module writes no DOM and reads no DOM.
Validation refuses rather than repairs -- unknown type, a renderer its type does
not have, a label that is not text, a default outside its own bounds, a when
naming a key the module never declared. A module that is wrong does not get a
best-effort panel; it does not load, and says why once.
Ordering is by declared relation and stage, never priority numbers, and a cycle
drops only the modules in it. Compatibility is by trait, never model name.
The shell hands a module { composer, values, on, shell } and nothing else: no
node, no document, no kit internals. It cannot style anything because it is
never given anything that could.
catalogue/hide.html puts one good module and five broken ones through the real
shell and shows the result: one panel, five absent, no placeholders. The lint
fixtures make the scanner prove it catches each rule, so a clean scan means
something.
First slice: modules/system/appearance. Two settings, an optional ui.js, and
clicking Light actually turns the app light.
64 lines
2.3 KiB
Python
64 lines
2.3 KiB
Python
"""Dev server: serves the app exactly as ComfyUI will, without ComfyUI.
|
|
|
|
Routes through core.serve, so the extension allowlist and the traversal guard are
|
|
exercised every time you look at the catalogue.
|
|
|
|
python tools/devserver.py [port]
|
|
"""
|
|
|
|
import json
|
|
import sys
|
|
from http.server import BaseHTTPRequestHandler, HTTPServer
|
|
from pathlib import Path
|
|
from urllib.parse import parse_qs, unquote, urlparse
|
|
|
|
sys.path.insert(0, str(Path(__file__).resolve().parent.parent))
|
|
|
|
from core import config, routes, serve as static # noqa: E402
|
|
|
|
P = config.UI_PREFIX
|
|
|
|
|
|
class Handler(BaseHTTPRequestHandler):
|
|
def do_GET(self):
|
|
parsed = urlparse(self.path)
|
|
path = unquote(parsed.path)
|
|
|
|
# The same manifest the aiohttp route serves, so what you see in the
|
|
# browser here is what ComfyUI would send.
|
|
if path == P + "/api/modules":
|
|
raw = parse_qs(parsed.query).get("traits")
|
|
traits = [t for t in raw[0].split(",") if t] if raw else None
|
|
body = json.dumps(routes.manifest(traits)).encode()
|
|
served = static.Served(200, body, "application/json")
|
|
elif path in (P, P + "/"):
|
|
served = static.serve(config.APP_DIR, "index.html", config.APP_EXTS)
|
|
elif path.startswith(P + "/app/"):
|
|
served = static.serve(config.APP_DIR, path[len(P) + 5:], config.APP_EXTS)
|
|
elif path.startswith(P + "/modules/"):
|
|
served = static.serve(config.MODULES_DIR, path[len(P) + 9:], config.MODULE_EXTS)
|
|
elif path == "/":
|
|
self.send_response(302)
|
|
self.send_header("Location", P + "/")
|
|
self.end_headers()
|
|
return
|
|
else:
|
|
served = static.Served(404)
|
|
|
|
self.send_response(served.status)
|
|
self.send_header("Content-Type", served.content_type)
|
|
self.send_header("Content-Length", str(len(served.body)))
|
|
for k, v in served.headers.items():
|
|
self.send_header(k, v)
|
|
self.end_headers()
|
|
self.wfile.write(served.body)
|
|
|
|
def log_message(self, fmt, *args):
|
|
sys.stderr.write("%s %s\n" % (self.address_string(), fmt % args))
|
|
|
|
|
|
if __name__ == "__main__":
|
|
port = int(sys.argv[1]) if len(sys.argv) > 1 else 8188
|
|
print(f"FunPack v5 dev server: http://127.0.0.1:{port}{P}/")
|
|
HTTPServer(("127.0.0.1", port), Handler).serve_forever()
|