diff --git a/packages/cli/src/__tests__/utils.test.ts b/packages/cli/src/__tests__/utils.test.ts new file mode 100644 index 00000000000..a73a512488d --- /dev/null +++ b/packages/cli/src/__tests__/utils.test.ts @@ -0,0 +1,68 @@ +import type { INodeType } from 'n8n-workflow'; + +import { shouldAssignExecuteMethod } from '../utils'; + +describe('shouldAssignExecuteMethod', () => { + it('should return true when node has no execute, poll, trigger, webhook (unless declarative), or methods', () => { + const nodeType = { + description: { requestDefaults: {} }, // Declarative node + execute: undefined, + poll: undefined, + trigger: undefined, + webhook: undefined, + methods: undefined, + } as INodeType; + + expect(shouldAssignExecuteMethod(nodeType)).toBe(true); + }); + + it('should return false when node has execute', () => { + const nodeType = { + execute: jest.fn(), + } as unknown as INodeType; + + expect(shouldAssignExecuteMethod(nodeType)).toBe(false); + }); + + it('should return false when node has poll', () => { + const nodeType = { + poll: jest.fn(), + } as unknown as INodeType; + + expect(shouldAssignExecuteMethod(nodeType)).toBe(false); + }); + + it('should return false when node has trigger', () => { + const nodeType = { + trigger: jest.fn(), + } as unknown as INodeType; + + expect(shouldAssignExecuteMethod(nodeType)).toBe(false); + }); + + it('should return false when node has webhook and is not declarative', () => { + const nodeType = { + description: {}, + webhook: jest.fn(), + } as unknown as INodeType; + + expect(shouldAssignExecuteMethod(nodeType)).toBe(false); + }); + + it('should return true when node has webhook but is declarative', () => { + const nodeType = { + description: { requestDefaults: {} }, // Declarative node + webhook: jest.fn(), + } as unknown as INodeType; + + expect(shouldAssignExecuteMethod(nodeType)).toBe(true); + }); + + it('should return false when node has methods', () => { + const nodeType = { + methods: {}, + } as unknown as INodeType; + + expect(shouldAssignExecuteMethod(nodeType)).toBe(false); + }); +}); diff --git a/packages/cli/src/node-types.ts b/packages/cli/src/node-types.ts index 2572b93e4dc..c8db3b41ea9 100644 --- a/packages/cli/src/node-types.ts +++ b/packages/cli/src/node-types.ts @@ -10,6 +10,7 @@ import { NodeHelpers, UnexpectedError, UserError } from 'n8n-workflow'; import { join, dirname } from 'path'; import { LoadNodesAndCredentials } from './load-nodes-and-credentials'; +import { shouldAssignExecuteMethod } from './utils'; @Service() export class NodeTypes implements INodeTypes { @@ -55,13 +56,7 @@ export class NodeTypes implements INodeTypes { throw new UnexpectedError('Node already has a `supplyData` method', { extra: { nodeType } }); } - if ( - !versionedNodeType.execute && - !versionedNodeType.poll && - !versionedNodeType.trigger && - !versionedNodeType.webhook && - !versionedNodeType.methods - ) { + if (shouldAssignExecuteMethod(versionedNodeType)) { versionedNodeType.execute = async function (this: ExecuteContext) { const routingNode = new RoutingNode(this, versionedNodeType); const data = await routingNode.runNode(); diff --git a/packages/cli/src/utils.ts b/packages/cli/src/utils.ts index d701707a115..6f19f33dc62 100644 --- a/packages/cli/src/utils.ts +++ b/packages/cli/src/utils.ts @@ -1,5 +1,5 @@ import { CliWorkflowOperationError, SubworkflowOperationError } from 'n8n-workflow'; -import type { INode } from 'n8n-workflow'; +import type { INode, INodeType } from 'n8n-workflow'; import { STARTING_NODES } from '@/constants'; @@ -90,3 +90,18 @@ export function rightDiff( export const assertNever = (_value: never) => {}; export const isPositiveInteger = (maybeInt: string) => /^[1-9]\d*$/.test(maybeInt); + +/** + * Check if a execute method should be assigned to the node + */ +export const shouldAssignExecuteMethod = (nodeType: INodeType) => { + const isDeclarativeNode = nodeType?.description?.requestDefaults !== undefined; + + return ( + !nodeType.execute && + !nodeType.poll && + !nodeType.trigger && + (!nodeType.webhook || isDeclarativeNode) && + !nodeType.methods + ); +}; diff --git a/packages/core/src/execution-engine/workflow-execute.ts b/packages/core/src/execution-engine/workflow-execute.ts index cdb23c79fe8..d8894259dc3 100644 --- a/packages/core/src/execution-engine/workflow-execute.ts +++ b/packages/core/src/execution-engine/workflow-execute.ts @@ -1018,10 +1018,6 @@ export class WorkflowExecute { private getCustomOperation(node: INode, type: INodeType) { if (!type.customOperations) return undefined; - if (type.execute) { - throw new UnexpectedError('Node type cannot have both customOperations and execute defined'); - } - if (!node.parameters) return undefined; const { customOperations } = type;