Skip to content

Commit 3a4247a

Browse files
authoredMar 14, 2025··
fix: Execute method should be assigned to a Routing node even if it has webhook defined (#13910)
1 parent 796a58c commit 3a4247a

File tree

4 files changed

+86
-12
lines changed

4 files changed

+86
-12
lines changed
 
+68
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
import type { INodeType } from 'n8n-workflow';
2+
3+
import { shouldAssignExecuteMethod } from '../utils';
4+
5+
describe('shouldAssignExecuteMethod', () => {
6+
it('should return true when node has no execute, poll, trigger, webhook (unless declarative), or methods', () => {
7+
const nodeType = {
8+
description: { requestDefaults: {} }, // Declarative node
9+
execute: undefined,
10+
poll: undefined,
11+
trigger: undefined,
12+
webhook: undefined,
13+
methods: undefined,
14+
} as INodeType;
15+
16+
expect(shouldAssignExecuteMethod(nodeType)).toBe(true);
17+
});
18+
19+
it('should return false when node has execute', () => {
20+
const nodeType = {
21+
execute: jest.fn(),
22+
} as unknown as INodeType;
23+
24+
expect(shouldAssignExecuteMethod(nodeType)).toBe(false);
25+
});
26+
27+
it('should return false when node has poll', () => {
28+
const nodeType = {
29+
poll: jest.fn(),
30+
} as unknown as INodeType;
31+
32+
expect(shouldAssignExecuteMethod(nodeType)).toBe(false);
33+
});
34+
35+
it('should return false when node has trigger', () => {
36+
const nodeType = {
37+
trigger: jest.fn(),
38+
} as unknown as INodeType;
39+
40+
expect(shouldAssignExecuteMethod(nodeType)).toBe(false);
41+
});
42+
43+
it('should return false when node has webhook and is not declarative', () => {
44+
const nodeType = {
45+
description: {},
46+
webhook: jest.fn(),
47+
} as unknown as INodeType;
48+
49+
expect(shouldAssignExecuteMethod(nodeType)).toBe(false);
50+
});
51+
52+
it('should return true when node has webhook but is declarative', () => {
53+
const nodeType = {
54+
description: { requestDefaults: {} }, // Declarative node
55+
webhook: jest.fn(),
56+
} as unknown as INodeType;
57+
58+
expect(shouldAssignExecuteMethod(nodeType)).toBe(true);
59+
});
60+
61+
it('should return false when node has methods', () => {
62+
const nodeType = {
63+
methods: {},
64+
} as unknown as INodeType;
65+
66+
expect(shouldAssignExecuteMethod(nodeType)).toBe(false);
67+
});
68+
});

‎packages/cli/src/node-types.ts

+2-7
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import { NodeHelpers, UnexpectedError, UserError } from 'n8n-workflow';
1010
import { join, dirname } from 'path';
1111

1212
import { LoadNodesAndCredentials } from './load-nodes-and-credentials';
13+
import { shouldAssignExecuteMethod } from './utils';
1314

1415
@Service()
1516
export class NodeTypes implements INodeTypes {
@@ -55,13 +56,7 @@ export class NodeTypes implements INodeTypes {
5556
throw new UnexpectedError('Node already has a `supplyData` method', { extra: { nodeType } });
5657
}
5758

58-
if (
59-
!versionedNodeType.execute &&
60-
!versionedNodeType.poll &&
61-
!versionedNodeType.trigger &&
62-
!versionedNodeType.webhook &&
63-
!versionedNodeType.methods
64-
) {
59+
if (shouldAssignExecuteMethod(versionedNodeType)) {
6560
versionedNodeType.execute = async function (this: ExecuteContext) {
6661
const routingNode = new RoutingNode(this, versionedNodeType);
6762
const data = await routingNode.runNode();

‎packages/cli/src/utils.ts

+16-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { CliWorkflowOperationError, SubworkflowOperationError } from 'n8n-workflow';
2-
import type { INode } from 'n8n-workflow';
2+
import type { INode, INodeType } from 'n8n-workflow';
33

44
import { STARTING_NODES } from '@/constants';
55

@@ -90,3 +90,18 @@ export function rightDiff<T1, T2>(
9090
export const assertNever = (_value: never) => {};
9191

9292
export const isPositiveInteger = (maybeInt: string) => /^[1-9]\d*$/.test(maybeInt);
93+
94+
/**
95+
* Check if a execute method should be assigned to the node
96+
*/
97+
export const shouldAssignExecuteMethod = (nodeType: INodeType) => {
98+
const isDeclarativeNode = nodeType?.description?.requestDefaults !== undefined;
99+
100+
return (
101+
!nodeType.execute &&
102+
!nodeType.poll &&
103+
!nodeType.trigger &&
104+
(!nodeType.webhook || isDeclarativeNode) &&
105+
!nodeType.methods
106+
);
107+
};

‎packages/core/src/execution-engine/workflow-execute.ts

-4
Original file line numberDiff line numberDiff line change
@@ -1018,10 +1018,6 @@ export class WorkflowExecute {
10181018
private getCustomOperation(node: INode, type: INodeType) {
10191019
if (!type.customOperations) return undefined;
10201020

1021-
if (type.execute) {
1022-
throw new UnexpectedError('Node type cannot have both customOperations and execute defined');
1023-
}
1024-
10251021
if (!node.parameters) return undefined;
10261022

10271023
const { customOperations } = type;

0 commit comments

Comments
 (0)
Please sign in to comment.