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