diff --git a/nodes/Teamorouter/Teamorouter.node.ts b/nodes/Teamorouter/Teamorouter.node.ts index cfe2cbd..332ccd8 100644 --- a/nodes/Teamorouter/Teamorouter.node.ts +++ b/nodes/Teamorouter/Teamorouter.node.ts @@ -390,8 +390,8 @@ export class Teamorouter implements INodeType { name: 'binaryProperty', type: 'string', default: 'data', - displayOptions: { show: { source: ['binary'] } }, - description: 'Name of the binary property that holds the image', + description: + 'Name of the binary property that holds the image. Used when Source is Binary File.', }, { displayName: 'Image URL', @@ -399,8 +399,8 @@ export class Teamorouter implements INodeType { type: 'string', default: '', placeholder: 'https://example.com/cat.png', - displayOptions: { show: { source: ['url'] } }, - description: 'Publicly reachable image link, or a data:image/...;base64,... URL', + description: + 'Publicly reachable image link, or a data:image/...;base64,... URL. Used when Source is Image URL.', }, ], }, @@ -596,7 +596,6 @@ export class Teamorouter implements INodeType { name: 'inputFidelity', type: 'options', default: 'high', - displayOptions: { show: { operation: ['edit'] } }, options: [ { name: 'High', @@ -609,19 +608,18 @@ export class Teamorouter implements INodeType { description: 'Allow the model to reinterpret the source image more freely', }, ], - description: 'How closely the edit follows the source image', + description: 'How closely the edit follows the source image. Only used when editing an image.', }, { displayName: 'Moderation', name: 'moderation', type: 'options', default: 'auto', - displayOptions: { show: { operation: ['generate'] } }, options: [ { name: 'Auto', value: 'auto', description: 'Standard content filter' }, { name: 'Low', value: 'low', description: 'Relaxed content filter' }, ], - description: 'Content filter applied by TeamoRouter', + description: 'Content filter applied by TeamoRouter. Only used when generating an image.', }, { displayName: 'Number of Images', diff --git a/test/validate-nodes.mjs b/test/validate-nodes.mjs index cb5cea5..6b95880 100644 --- a/test/validate-nodes.mjs +++ b/test/validate-nodes.mjs @@ -105,6 +105,45 @@ for (const { file, instance } of loaded.nodes) { } } +// n8n builds a Workflow object — resolving every parameter against its displayOptions — before an +// execution exists, and it builds it again from the parameters it wrote back. If that resolution +// loops the webhook request dies with "There was a problem executing the workflow" and no run at all. +// Building with every collection present but empty is the shape that used to break. +const { Workflow } = require('n8n-workflow'); + +for (const { instance } of loaded.nodes) { + const containers = instance.description.properties + .filter((property) => property.type === 'collection' || property.type === 'fixedCollection') + .map((property) => property.name); + const resources = instance.description.properties.find((property) => property.name === 'resource') + ?.options ?? [{ value: undefined }]; + + const build = (parameters) => + new Workflow({ + id: 'validation', + nodes: [ + { + id: 'validation-node', + name: instance.description.displayName, + type: instance.description.name, + typeVersion: instance.description.version, + position: [0, 0], + parameters: JSON.parse(JSON.stringify(parameters)), + }, + ], + connections: {}, + active: false, + nodeTypes: { getByNameAndVersion: () => instance, getByName: () => instance }, + }); + + for (const { value: resource } of resources) { + const parameters = { resource, ...Object.fromEntries(containers.map((name) => [name, {}])) }; + // The parameters of the first pass are the ones n8n stores and reads back. + const firstPass = build(parameters); + build(firstPass.nodes[instance.description.displayName].parameters); + } +} + const chatModel = loaded.nodes.find(({ instance }) => instance.description.name === 'lmChatTeamRouter').instance; assert.deepEqual(chatModel.description.inputs, [], 'chat model sub-node must not have a main input'); assert.ok(