From dfc2f0bf15c6a2d872fc70abd6715ac676e62cb0 Mon Sep 17 00:00:00 2001 From: Ilker Manap Date: Sat, 10 Oct 2026 18:54:15 +0200 Subject: [PATCH] A custom node's own range decides the packages it names TripoSR's checkpoint loads with transformers 4, and ComfyUI runs 5: of a package a node names, its range decides, and of everything else what ComfyUI runs. Co-Authored-By: Claude Opus 5.5 (1M context) --- docs/admin-api.openapi.json | 2 +- docs/reference.md | 9 +++++---- internal/comfy/nodes.go | 31 ++++++++++++++++++++++++++++- internal/comfy/progress_test.go | 9 +++++++++ internal/control/catalog/catalog.go | 9 +++++---- internal/docgen/reference_gen.go | 2 +- 6 files changed, 51 insertions(+), 11 deletions(-) diff --git a/docs/admin-api.openapi.json b/docs/admin-api.openapi.json index 33549f4e..4cfa23fb 100644 --- a/docs/admin-api.openapi.json +++ b/docs/admin-api.openapi.json @@ -8626,7 +8626,7 @@ "items": { "type": "string" }, - "description": "Requirements are the PyPI packages the node needs that ComfyUI does not have, by name and version range only. They are installed beside the replica, never into ComfyUI's environment, at the versions that environment already has of anything it shares." + "description": "Requirements are the PyPI packages the node needs, by name and version range only. They are installed beside the replica, never into ComfyUI's environment: a package named here at the range named, and every other one at the version ComfyUI already runs. One named here is first on ComfyUI's path, so its range must suit ComfyUI too." } } }, diff --git a/docs/reference.md b/docs/reference.md index 9f31aa0f..11486e68 100644 --- a/docs/reference.md +++ b/docs/reference.md @@ -912,10 +912,11 @@ node that changed under a model is a model that changed unasked. `[]string` -Requirements are the PyPI packages the node needs that ComfyUI does -not have, by name and version range only. They are installed beside -the replica, never into ComfyUI's environment, at the versions that -environment already has of anything it shares. +Requirements are the PyPI packages the node needs, by name and version +range only. They are installed beside the replica, never into +ComfyUI's environment: a package named here at the range named, and +every other one at the version ComfyUI already runs. One named here +is first on ComfyUI's path, so its range must suit ComfyUI too. ## Endpoint diff --git a/internal/comfy/nodes.go b/internal/comfy/nodes.go index b13f8418..1e0ffb12 100644 --- a/internal/comfy/nodes.go +++ b/internal/comfy/nodes.go @@ -83,7 +83,7 @@ func installNodes(ctx context.Context, state, python string, nodes []Node, uv st return "", fmt.Errorf("reading ComfyUI's packages: %w", err) } constraints := filepath.Join(state, "constraints.txt") - if err := os.WriteFile(constraints, freeze, 0o644); err != nil { //nolint:gosec // not secret + if err := os.WriteFile(constraints, withoutNamed(freeze, reqs), 0o644); err != nil { //nolint:gosec // not secret return "", err } _ = os.RemoveAll(site) @@ -136,3 +136,32 @@ func lastLines(b []byte) string { } return strings.Join(lines, " | ") } + +// withoutNamed is ComfyUI's packages less those a node names itself: of a +// package a node asks for by name, the node's range decides — TripoSR's +// checkpoint loads with transformers 4 and not 5 — and of everything else, +// what ComfyUI runs. A node that names a package must name a range ComfyUI +// can run with too: the copy it gets is first on ComfyUI's path. +func withoutNamed(freeze []byte, reqs []string) []byte { + named := map[string]bool{} + for _, r := range reqs { + named[packageName(r)] = true + } + var out []string + for _, line := range strings.Split(string(freeze), "\n") { + if strings.TrimSpace(line) == "" || named[packageName(line)] { + continue + } + out = append(out, line) + } + return []byte(strings.Join(out, "\n") + "\n") +} + +// packageName is a requirement's package, as PyPI compares them. +func packageName(spec string) string { + s := strings.TrimSpace(spec) + if i := strings.IndexAny(s, "<>=!~;[ @"); i >= 0 { + s = s[:i] + } + return strings.NewReplacer("_", "-", ".", "-").Replace(strings.ToLower(s)) +} diff --git a/internal/comfy/progress_test.go b/internal/comfy/progress_test.go index 5e763f9f..49ff8256 100644 --- a/internal/comfy/progress_test.go +++ b/internal/comfy/progress_test.go @@ -181,3 +181,12 @@ func TestANodeIsPutAtItsCommit(t *testing.T) { t.Errorf("packages without a uv: %v", err) } } + +func TestANodesOwnRangeDecidesThePackagesItNames(t *testing.T) { + freeze := []byte("numpy==2.3.1\ntransformers==5.19.0\nhuggingface-hub==1.33.0\nPillow==12.3.0\n") + got := string(withoutNamed(freeze, []string{"transformers>=4.50.3,<5", "Huggingface_Hub<1", "trimesh"})) + if strings.Contains(got, "transformers") || strings.Contains(got, "huggingface") || + !strings.Contains(got, "numpy==2.3.1") || !strings.Contains(got, "Pillow==12.3.0") { + t.Errorf("constraints %q", got) + } +} diff --git a/internal/control/catalog/catalog.go b/internal/control/catalog/catalog.go index 39a3209e..88544cec 100644 --- a/internal/control/catalog/catalog.go +++ b/internal/control/catalog/catalog.go @@ -564,10 +564,11 @@ type CustomNode struct { // Commit is the full commit the node is used at: a branch moves, and a // node that changed under a model is a model that changed unasked. Commit string `json:"commit" koanf:"commit"` - // Requirements are the PyPI packages the node needs that ComfyUI does - // not have, by name and version range only. They are installed beside - // the replica, never into ComfyUI's environment, at the versions that - // environment already has of anything it shares. + // Requirements are the PyPI packages the node needs, by name and version + // range only. They are installed beside the replica, never into + // ComfyUI's environment: a package named here at the range named, and + // every other one at the version ComfyUI already runs. One named here + // is first on ComfyUI's path, so its range must suit ComfyUI too. Requirements []string `json:"requirements,omitempty" koanf:"requirements"` } diff --git a/internal/docgen/reference_gen.go b/internal/docgen/reference_gen.go index cabf1ac4..c38d85a8 100644 --- a/internal/docgen/reference_gen.go +++ b/internal/docgen/reference_gen.go @@ -93,7 +93,7 @@ var Reference = []Block{ {Package: "catalog", Name: "CustomNode", Title: "CustomNode", Doc: "", Fields: []Field{ {Key: "repo", Type: "string", Doc: "Repo is the repository, https only.", Nested: ""}, {Key: "commit", Type: "string", Doc: "Commit is the full commit the node is used at: a branch moves, and a\nnode that changed under a model is a model that changed unasked.", Nested: ""}, - {Key: "requirements", Type: "[]string", Doc: "Requirements are the PyPI packages the node needs that ComfyUI does\nnot have, by name and version range only. They are installed beside\nthe replica, never into ComfyUI's environment, at the versions that\nenvironment already has of anything it shares.", Nested: ""}, + {Key: "requirements", Type: "[]string", Doc: "Requirements are the PyPI packages the node needs, by name and version\nrange only. They are installed beside the replica, never into\nComfyUI's environment: a package named here at the range named, and\nevery other one at the version ComfyUI already runs. One named here\nis first on ComfyUI's path, so its range must suit ComfyUI too.", Nested: ""}, }}, {Package: "config", Name: "Endpoint", Title: "Endpoint", Doc: "", Fields: []Field{ {Key: "id", Type: "string", Doc: "", Nested: ""},