From 9be853f6b554ab6da4340b49eebb823a56b7ce3d Mon Sep 17 00:00:00 2001 From: guill Date: Wed, 28 Jan 2026 19:41:45 -0800 Subject: [PATCH] feat: support dev-only nodes (#8359) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Summary Support `dev_only` property to node definitions that hides nodes from search and menus unless dev mode is enabled. Dev-only nodes display a "DEV" badge when visible. This functionality is primarily intended to support unit-testing nodes on Comfy Cloud, but also has other uses. ## Changes - **What**: Nodes flagged as dev_only in the node schema will only appear in search and menus if Dev Mode is on. ## Screenshots (if applicable) With Dev Mode off: image With Dev Mode on: image ┆Issue is synchronized with this [Notion page](https://www.notion.so/PR-8359-feat-support-dev-only-nodes-2f66d73d36508102839ee7cd66a26129) by [Unito](https://www.unito.io) --- src/components/searchbox/NodeSearchItem.vue | 11 ++++---- src/lib/litegraph/src/LGraphNode.ts | 8 ++++++ src/locales/en/main.json | 1 + src/schemas/nodeDef/nodeDefSchemaV2.ts | 1 + src/schemas/nodeDefSchema.ts | 1 + src/services/litegraphService.ts | 11 ++++++-- src/stores/nodeDefStore.ts | 31 ++++++++++++++++++++- src/utils/nodeFilterUtil.test.ts | 8 +++--- 8 files changed, 60 insertions(+), 12 deletions(-) diff --git a/src/components/searchbox/NodeSearchItem.vue b/src/components/searchbox/NodeSearchItem.vue index da9a0cd83..a9859eca4 100644 --- a/src/components/searchbox/NodeSearchItem.vue +++ b/src/components/searchbox/NodeSearchItem.vue @@ -21,16 +21,17 @@
- + + { static comfyClass: string static override title: string static override category: string - static nodeData: ComfyNodeDefV1 & ComfyNodeDefV2 + static override nodeData: ComfyNodeDefV1 & ComfyNodeDefV2 _initialMinSize = { width: 1, height: 1 } @@ -394,7 +394,7 @@ export const useLitegraphService = () => { static comfyClass: string static override title: string static override category: string - static nodeData: ComfyNodeDefV1 & ComfyNodeDefV2 + static override nodeData: ComfyNodeDefV1 & ComfyNodeDefV2 _initialMinSize = { width: 1, height: 1 } @@ -496,6 +496,13 @@ export const useLitegraphService = () => { // because `registerNodeType` will overwrite the assignments. node.category = nodeDef.category node.title = nodeDef.display_name || nodeDef.name + + // Set skip_list for dev-only nodes based on current DevMode setting + // This ensures nodes registered after initial load respect the current setting + if (nodeDef.dev_only) { + const settingStore = useSettingStore() + node.skip_list = !settingStore.get('Comfy.DevMode') + } } /** diff --git a/src/stores/nodeDefStore.ts b/src/stores/nodeDefStore.ts index 217b784ca..7bfa17c2c 100644 --- a/src/stores/nodeDefStore.ts +++ b/src/stores/nodeDefStore.ts @@ -1,9 +1,10 @@ import axios from 'axios' import _ from 'es-toolkit/compat' import { defineStore } from 'pinia' -import { computed, ref } from 'vue' +import { computed, ref, watchEffect } from 'vue' import { isProxyWidget } from '@/core/graph/subgraph/proxyWidget' +import { LiteGraph } from '@/lib/litegraph/src/litegraph' import type { LGraphNode } from '@/lib/litegraph/src/litegraph' import { transformNodeDefV1ToV2 } from '@/schemas/nodeDef/migration' import type { @@ -17,6 +18,7 @@ import type { ComfyOutputTypesSpec as ComfyOutputSpecV1, PriceBadge } from '@/schemas/nodeDefSchema' +import { useSettingStore } from '@/platform/settings/settingStore' import { NodeSearchService } from '@/services/nodeSearchService' import { useSubgraphStore } from '@/stores/subgraphStore' import { NodeSourceType, getNodeSource } from '@/types/nodeSource' @@ -41,6 +43,7 @@ export class ComfyNodeDefImpl readonly help: string readonly deprecated: boolean readonly experimental: boolean + readonly dev_only: boolean readonly output_node: boolean readonly api_node: boolean /** @@ -133,6 +136,7 @@ export class ComfyNodeDefImpl this.deprecated = obj.deprecated ?? obj.category === '' this.experimental = obj.experimental ?? obj.category.startsWith('_for_testing') + this.dev_only = obj.dev_only ?? false this.output_node = obj.output_node this.api_node = !!obj.api_node this.input = obj.input ?? {} @@ -174,6 +178,7 @@ export class ComfyNodeDefImpl get nodeLifeCycleBadgeText(): string { if (this.deprecated) return '[DEPR]' if (this.experimental) return '[BETA]' + if (this.dev_only) return '[DEV]' return '' } } @@ -299,12 +304,27 @@ export interface NodeDefFilter { } export const useNodeDefStore = defineStore('nodeDef', () => { + const settingStore = useSettingStore() + const nodeDefsByName = ref>({}) const nodeDefsByDisplayName = ref>({}) const showDeprecated = ref(false) const showExperimental = ref(false) + const showDevOnly = computed(() => settingStore.get('Comfy.DevMode')) const nodeDefFilters = ref([]) + // Update skip_list on all registered node types when dev mode changes + // This ensures LiteGraph's getNodeTypesCategories/getNodeTypesInCategory + // correctly filter dev-only nodes from the right-click context menu + watchEffect(() => { + const devModeEnabled = showDevOnly.value + for (const nodeType of Object.values(LiteGraph.registered_node_types)) { + if (nodeType.nodeData?.dev_only) { + nodeType.skip_list = !devModeEnabled + } + } + }) + const nodeDefs = computed(() => { const subgraphStore = useSubgraphStore() // Blueprints first for discoverability in the node library sidebar @@ -422,6 +442,14 @@ export const useNodeDefStore = defineStore('nodeDef', () => { predicate: (nodeDef) => showExperimental.value || !nodeDef.experimental }) + // Dev-only nodes filter + registerNodeDefFilter({ + id: 'core.dev_only', + name: 'Hide Dev-Only Nodes', + description: 'Hides nodes marked as dev-only unless dev mode is enabled', + predicate: (nodeDef) => showDevOnly.value || !nodeDef.dev_only + }) + // Subgraph nodes filter // Filter out litegraph typed subgraphs, saved blueprints are added in separately registerNodeDefFilter({ @@ -446,6 +474,7 @@ export const useNodeDefStore = defineStore('nodeDef', () => { nodeDefsByDisplayName, showDeprecated, showExperimental, + showDevOnly, nodeDefFilters, nodeDefs, diff --git a/src/utils/nodeFilterUtil.test.ts b/src/utils/nodeFilterUtil.test.ts index 871f66109..0e6149c0d 100644 --- a/src/utils/nodeFilterUtil.test.ts +++ b/src/utils/nodeFilterUtil.test.ts @@ -11,7 +11,7 @@ describe('nodeFilterUtil', () => { ): LGraphNode => { // Create a custom class with the nodeData static property class MockNode extends LGraphNode { - static nodeData = isOutputNode ? { output_node: true } : {} + static override nodeData = isOutputNode ? { output_node: true } : {} } const node = new MockNode('') @@ -71,11 +71,11 @@ describe('nodeFilterUtil', () => { }) it('should handle nodes with undefined output_node', () => { - class MockNodeWithOtherData extends LGraphNode { - static nodeData = { someOtherProperty: true } + class MockNodeWithEmptyData extends LGraphNode { + static override nodeData = {} } - const node = new MockNodeWithOtherData('') + const node = new MockNodeWithEmptyData('') node.id = 1 const result = filterOutputNodes([node])