diff --git a/src/parsers/node-parser.ts b/src/parsers/node-parser.ts index a56ecf5..197ac34 100644 --- a/src/parsers/node-parser.ts +++ b/src/parsers/node-parser.ts @@ -134,6 +134,24 @@ export class NodeParser { description.name?.toLowerCase().includes('webhook'); } + /** + * Extracts the version from a node class. + * + * Priority Chain: + * 1. Instance currentVersion (VersionedNodeType's computed property) + * 2. Instance description.defaultVersion (explicit default) + * 3. Instance nodeVersions (fallback to max available version) + * 4. Description version array (legacy nodes) + * 5. Description version scalar (simple versioning) + * 6. Class-level properties (if instantiation fails) + * 7. Default to "1" + * + * Critical Fix (v2.17.4): Removed check for non-existent instance.baseDescription.defaultVersion + * which caused AI Agent to incorrectly return version "3" instead of "2.2" + * + * @param nodeClass - The node class or instance to extract version from + * @returns The version as a string + */ private extractVersion(nodeClass: any): string { // Check instance properties first try { diff --git a/src/services/workflow-validator.ts b/src/services/workflow-validator.ts index 3bde273..b99e7dc 100644 --- a/src/services/workflow-validator.ts +++ b/src/services/workflow-validator.ts @@ -445,7 +445,9 @@ export class WorkflowValidator { } // Validate typeVersion for ALL versioned nodes (including langchain nodes) - // This validation runs BEFORE the langchain skip to ensure typeVersion is checked + // CRITICAL: This MUST run BEFORE the langchain skip below! + // Otherwise, langchain nodes with invalid typeVersion (e.g., 99999) would pass validation + // but fail at runtime in n8n. This was the bug fixed in v2.17.4. if (nodeInfo.isVersioned) { // Check if typeVersion is missing if (!node.typeVersion) { @@ -485,9 +487,9 @@ export class WorkflowValidator { } } - // Skip parameter validation for langchain nodes - // They have dedicated AI-specific validators in validateAISpecificNodes() - // This prevents parameter validation conflicts and ensures proper AI validation + // Skip PARAMETER validation for langchain nodes (but NOT typeVersion validation above!) + // Langchain nodes have dedicated AI-specific validators in validateAISpecificNodes() + // which handle their unique parameter structures (AI connections, tool ports, etc.) if (normalizedType.startsWith('nodes-langchain.')) { continue; } diff --git a/tests/unit/parsers/node-parser.test.ts b/tests/unit/parsers/node-parser.test.ts index 35057a8..a07f344 100644 --- a/tests/unit/parsers/node-parser.test.ts +++ b/tests/unit/parsers/node-parser.test.ts @@ -286,20 +286,64 @@ describe('NodeParser', () => { }); describe('version extraction', () => { - it('should extract version from baseDescription.defaultVersion', () => { + it('should prioritize currentVersion over description.defaultVersion', () => { const NodeClass = class { - baseDescription = { + currentVersion = 2.2; // Should be returned + description = { + name: 'AI Agent', + displayName: 'AI Agent', + defaultVersion: 3 // Should be ignored when currentVersion exists + }; + }; + + const result = parser.parse(NodeClass, 'n8n-nodes-base'); + + expect(result.version).toBe('2.2'); + }); + + it('should extract version from description.defaultVersion', () => { + const NodeClass = class { + description = { name: 'test', displayName: 'Test', defaultVersion: 3 }; }; - + const result = parser.parse(NodeClass, 'n8n-nodes-base'); - + expect(result.version).toBe('3'); }); + it('should handle currentVersion = 0 correctly', () => { + const NodeClass = class { + currentVersion = 0; // Edge case: version 0 should be valid + description = { + name: 'test', + displayName: 'Test', + defaultVersion: 5 // Should be ignored + }; + }; + + const result = parser.parse(NodeClass, 'n8n-nodes-base'); + + expect(result.version).toBe('0'); + }); + + it('should NOT extract version from non-existent baseDescription (legacy bug)', () => { + const NodeClass = class { + baseDescription = { // This property doesn't exist on VersionedNodeType! + name: 'test', + displayName: 'Test', + defaultVersion: 3 + }; + }; + + const result = parser.parse(NodeClass, 'n8n-nodes-base'); + + expect(result.version).toBe('1'); // Should fallback to default + }); + it('should extract version from nodeVersions keys', () => { const NodeClass = class { description = { name: 'test', displayName: 'Test' };