fix: recognize all trigger node types including executeWorkflowTrigger (#351) (#352)

This fix addresses issue #351 where Execute Workflow Trigger and other
trigger nodes were incorrectly treated as regular nodes, causing
"disconnected node" errors during partial workflow updates.

## Changes

**1. Created Shared Trigger Detection Utilities**
- src/utils/node-type-utils.ts:
  - isTriggerNode(): Recognizes ALL trigger types using flexible pattern matching
  - isActivatableTrigger(): Returns false for executeWorkflowTrigger (not activatable)
  - getTriggerTypeDescription(): Human-readable trigger descriptions

**2. Updated Workflow Validation**
- src/services/n8n-validation.ts:
  - Replaced hardcoded webhookTypes Set with isTriggerNode() function
  - Added validation preventing activation of workflows with only executeWorkflowTrigger
  - Now recognizes 200+ trigger types across n8n packages

**3. Updated Workflow Validator**
- src/services/workflow-validator.ts:
  - Replaced inline trigger detection with shared isTriggerNode() function
  - Ensures consistency across all validation code paths

**4. Comprehensive Tests**
- tests/unit/utils/node-type-utils.test.ts:
  - Added 30+ tests for trigger detection functions
  - Validates all trigger types are recognized correctly
  - Confirms executeWorkflowTrigger is trigger but not activatable

## Impact

Before:
- Execute Workflow Trigger flagged as disconnected node
- Schedule/email/polling triggers also rejected
- Users forced to keep unnecessary webhook triggers

After:
- ALL trigger types recognized (executeWorkflowTrigger, scheduleTrigger, etc.)
- No disconnected node errors for triggers
- Clear error when activating workflow with only executeWorkflowTrigger
- Future-proof (new triggers automatically supported)

## Testing

- Build:  Passes
- Typecheck:  Passes
- Unit tests:  All pass
- Validation test:  Trigger detection working correctly

Conceived by Romuald Członkowski - https://www.aiadvisors.pl/en
This commit is contained in:
Romuald Członkowski
2025-10-23 09:42:46 +02:00
committed by GitHub
parent c76ffd9fb1
commit eac4e67101
7 changed files with 366 additions and 124 deletions

View File

@@ -1,6 +1,7 @@
import { z } from 'zod';
import { WorkflowNode, WorkflowConnection, Workflow } from '../types/n8n-api';
import { isNonExecutableNode, isTriggerNode } from '../utils/node-classification';
import { isTriggerNode, isActivatableTrigger } from '../utils/node-type-utils';
import { isNonExecutableNode } from '../utils/node-classification';
// Zod schemas for n8n API validation
@@ -257,10 +258,10 @@ export function validateWorkflowStructure(workflow: Partial<Workflow>): string[]
}
const isConnected = connectedNodes.has(node.name);
const isTrigger = isTriggerNode(node.type);
const isNodeTrigger = isTriggerNode(node.type);
// Trigger nodes only need outgoing connections
if (isTrigger) {
if (isNodeTrigger) {
return !workflow.connections?.[node.name]; // Disconnected if no outgoing connections
}
@@ -315,6 +316,29 @@ export function validateWorkflowStructure(workflow: Partial<Workflow>): string[]
}
}
// Validate active workflows have activatable triggers
// Issue #351: executeWorkflowTrigger cannot activate a workflow
// It can only be invoked by other workflows
if ((workflow as any).active === true && workflow.nodes && workflow.nodes.length > 0) {
const activatableTriggers = workflow.nodes.filter(node =>
!node.disabled && isActivatableTrigger(node.type)
);
const executeWorkflowTriggers = workflow.nodes.filter(node =>
!node.disabled && node.type.toLowerCase().includes('executeworkflow')
);
if (activatableTriggers.length === 0 && executeWorkflowTriggers.length > 0) {
// Workflow is active but only has executeWorkflowTrigger nodes
const triggerNames = executeWorkflowTriggers.map(n => n.name).join(', ');
errors.push(
`Cannot activate workflow with only Execute Workflow Trigger nodes (${triggerNames}). ` +
'Execute Workflow Trigger can only be invoked by other workflows, not activated. ' +
'Either deactivate the workflow or add a webhook/schedule/polling trigger.'
);
}
}
// Validate Switch and IF node connection structures match their rules
if (workflow.nodes && workflow.connections) {
const switchNodes = workflow.nodes.filter(n => {