diff --git a/.changeset/heavy-donuts-boil.md b/.changeset/heavy-donuts-boil.md new file mode 100644 index 000000000..d22177f05 --- /dev/null +++ b/.changeset/heavy-donuts-boil.md @@ -0,0 +1,5 @@ +--- +"worker-comfyui": patch +--- + +install custom node dependencies into the ComfyUI runtime environment diff --git a/scripts/comfy-node-install.sh b/scripts/comfy-node-install.sh index e45e62f71..109888863 100644 --- a/scripts/comfy-node-install.sh +++ b/scripts/comfy-node-install.sh @@ -10,12 +10,15 @@ if [[ $# -eq 0 ]]; then exit 64 # EX_USAGE fi +custom_nodes_dir="${COMFYUI_CUSTOM_NODES_DIR:-/comfyui/custom_nodes}" +runtime_python="${COMFYUI_RUNTIME_PYTHON:-/opt/venv/bin/python}" + log=$(mktemp) -# run installation – some modes return non-zero even on success, so we -# ignore the exit status and rely on log parsing instead. +# Let comfy-cli use the ComfyUI workspace environment to install the node. +# The worker runtime uses /opt/venv, so mirror node requirements there below. set +e -comfy node install --mode=remote "$@" 2>&1 | tee "$log" +env -u VIRTUAL_ENV comfy node install --mode=remote "$@" 2>&1 | tee "$log" cli_status=$? set -e @@ -35,10 +38,17 @@ if [[ -n "$failed_nodes" ]]; then exit 1 fi +for requirements_file in "$custom_nodes_dir"/*/requirements.txt; do + if [[ ! -f "$requirements_file" ]]; then + continue + fi + uv pip install --python "$runtime_python" -r "$requirements_file" +done + # If we reach here no failed nodes were detected. Warn if CLI exit status # was non-zero but treat it as success. if [[ $cli_status -ne 0 ]]; then echo "Warning: comfy node install exited with status $cli_status but no errors were detected in the log — assuming success." >&2 fi -exit 0 \ No newline at end of file +exit 0 diff --git a/tests/test_comfy_node_install.py b/tests/test_comfy_node_install.py new file mode 100644 index 000000000..dc4d92160 --- /dev/null +++ b/tests/test_comfy_node_install.py @@ -0,0 +1,73 @@ +import os +from pathlib import Path +import subprocess +import tempfile +import unittest + + +_REPO_ROOT = Path(__file__).resolve().parents[1] +_INSTALL_SCRIPT = _REPO_ROOT / "scripts" / "comfy-node-install.sh" + + +class TestComfyNodeInstall(unittest.TestCase): + def test_installs_node_in_workspace_and_mirrors_requirements_to_runtime(self): + with tempfile.TemporaryDirectory() as temp_dir: + temp_path = Path(temp_dir) + bin_dir = temp_path / "bin" + bin_dir.mkdir() + nodes_dir = temp_path / "custom_nodes" + node_dir = nodes_dir / "test-node" + node_dir.mkdir(parents=True) + requirements_file = node_dir / "requirements.txt" + requirements_file.write_text("test-dependency\n") + + captured_venv = temp_path / "virtual-env" + captured_uv_args = temp_path / "uv-args" + comfy = bin_dir / "comfy" + comfy.write_text( + "#!/usr/bin/env bash\n" + 'printf "%s" "${VIRTUAL_ENV-}" > "$CAPTURED_VENV"\n' + 'echo "Installed test-node"\n' + ) + comfy.chmod(0o755) + uv = bin_dir / "uv" + uv.write_text( + "#!/usr/bin/env bash\n" + 'printf "%s\\n" "$*" >> "$CAPTURED_UV_ARGS"\n' + ) + uv.chmod(0o755) + + env = os.environ.copy() + env.pop("VIRTUAL_ENV", None) + env.update( + { + "CAPTURED_VENV": str(captured_venv), + "CAPTURED_UV_ARGS": str(captured_uv_args), + "COMFYUI_CUSTOM_NODES_DIR": str(nodes_dir), + "COMFYUI_RUNTIME_PYTHON": str(temp_path / "runtime" / "bin" / "python"), + "PATH": f"{bin_dir}{os.pathsep}{env['PATH']}", + } + ) + + result = subprocess.run( + ["bash", str(_INSTALL_SCRIPT), "test-node"], + env=env, + capture_output=True, + text=True, + check=False, + ) + + self.assertEqual(result.returncode, 0, result.stderr) + self.assertEqual(captured_venv.read_text(), "") + self.assertEqual( + captured_uv_args.read_text().splitlines(), + [ + "pip install --python " + f"{temp_path / 'runtime' / 'bin' / 'python'} " + f"-r {requirements_file}" + ], + ) + + +if __name__ == "__main__": + unittest.main()