fix: handle version arrays in node parser for correct version extraction

- Fixed extractVersion to properly handle nodes with version arrays like [1, 1.1, 1.2]
- Updated detectVersioned to mark nodes with version arrays as versioned
- Added test script to verify version extraction
- Gmail Trigger now correctly shows version 1.2 instead of 1
- All 97 nodes with version arrays now show their latest version
- Database rebuilt with correct versions for all affected nodes

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
czlonkowski
2025-06-18 22:38:26 +02:00
parent ca936c7b0c
commit e209bf3a81
4 changed files with 280 additions and 5 deletions

Binary file not shown.

View File

@@ -120,27 +120,56 @@ export class NodeParser {
} }
private extractVersion(nodeClass: any): string { private extractVersion(nodeClass: any): string {
// Handle VersionedNodeType with defaultVersion
if (nodeClass.baseDescription?.defaultVersion) { if (nodeClass.baseDescription?.defaultVersion) {
return nodeClass.baseDescription.defaultVersion.toString(); return nodeClass.baseDescription.defaultVersion.toString();
} }
// Handle VersionedNodeType with nodeVersions
if (nodeClass.nodeVersions) { if (nodeClass.nodeVersions) {
const versions = Object.keys(nodeClass.nodeVersions); const versions = Object.keys(nodeClass.nodeVersions);
return Math.max(...versions.map(Number)).toString(); return Math.max(...versions.map(Number)).toString();
} }
// Check instance for nodeVersions // Check instance for nodeVersions and version arrays
try { try {
const instance = typeof nodeClass === 'function' ? new nodeClass() : nodeClass; const instance = typeof nodeClass === 'function' ? new nodeClass() : nodeClass;
// Handle instance-level nodeVersions
if (instance?.nodeVersions) { if (instance?.nodeVersions) {
const versions = Object.keys(instance.nodeVersions); const versions = Object.keys(instance.nodeVersions);
return Math.max(...versions.map(Number)).toString(); return Math.max(...versions.map(Number)).toString();
} }
// Handle version array in description (e.g., [1, 1.1, 1.2])
if (instance?.description?.version) {
const version = instance.description.version;
if (Array.isArray(version)) {
// Find the maximum version from the array
const maxVersion = Math.max(...version.map((v: any) => parseFloat(v.toString())));
return maxVersion.toString();
} else if (typeof version === 'number' || typeof version === 'string') {
return version.toString();
}
}
} catch (e) { } catch (e) {
// Ignore // Some nodes might require parameters to instantiate
// Try to get version from class-level description
} }
return nodeClass.description?.version || '1'; // Also check class-level description for version array
const description = this.getNodeDescription(nodeClass);
if (description?.version) {
if (Array.isArray(description.version)) {
const maxVersion = Math.max(...description.version.map((v: any) => parseFloat(v.toString())));
return maxVersion.toString();
} else if (typeof description.version === 'number' || typeof description.version === 'string') {
return description.version.toString();
}
}
// Default to version 1
return '1';
} }
private detectVersioned(nodeClass: any): boolean { private detectVersioned(nodeClass: any): boolean {
@@ -149,14 +178,28 @@ export class NodeParser {
return true; return true;
} }
// Check instance-level nodeVersions // Check instance-level nodeVersions and version arrays
try { try {
const instance = typeof nodeClass === 'function' ? new nodeClass() : nodeClass; const instance = typeof nodeClass === 'function' ? new nodeClass() : nodeClass;
// Check for nodeVersions
if (instance?.nodeVersions) { if (instance?.nodeVersions) {
return true; return true;
} }
// Check for version array in description
if (instance?.description?.version && Array.isArray(instance.description.version)) {
return true;
}
} catch (e) { } catch (e) {
// Ignore // Some nodes might require parameters to instantiate
// Try to check class-level description
}
// Also check class-level description for version array
const description = this.getNodeDescription(nodeClass);
if (description?.version && Array.isArray(description.version)) {
return true;
} }
return false; return false;

View File

@@ -0,0 +1,90 @@
import { NodeParser } from '../parsers/node-parser';
// Test script to verify version extraction from different node types
async function testVersionExtraction() {
console.log('Testing version extraction from different node types...\n');
const parser = new NodeParser();
// Test cases
const testCases = [
{
name: 'Gmail Trigger (version array)',
nodeType: 'nodes-base.gmailTrigger',
expectedVersion: '1.2',
expectedVersioned: true
},
{
name: 'HTTP Request (VersionedNodeType)',
nodeType: 'nodes-base.httpRequest',
expectedVersion: '4.2',
expectedVersioned: true
},
{
name: 'Code (version array)',
nodeType: 'nodes-base.code',
expectedVersion: '2',
expectedVersioned: true
}
];
// Load nodes from packages
const basePackagePath = process.cwd() + '/node_modules/n8n/node_modules/n8n-nodes-base';
for (const testCase of testCases) {
console.log(`\nTesting: ${testCase.name}`);
console.log(`Node Type: ${testCase.nodeType}`);
try {
// Find the node file
const nodeName = testCase.nodeType.split('.')[1];
// Try different paths
const possiblePaths = [
`${basePackagePath}/dist/nodes/${nodeName}.node.js`,
`${basePackagePath}/dist/nodes/Google/Gmail/GmailTrigger.node.js`,
`${basePackagePath}/dist/nodes/HttpRequest/HttpRequest.node.js`,
`${basePackagePath}/dist/nodes/Code/Code.node.js`
];
let nodeClass = null;
for (const path of possiblePaths) {
try {
const module = require(path);
nodeClass = module[Object.keys(module)[0]];
if (nodeClass) break;
} catch (e) {
// Try next path
}
}
if (!nodeClass) {
console.log('❌ Could not load node');
continue;
}
// Parse the node
const parsed = parser.parse(nodeClass, 'n8n-nodes-base');
console.log(`Loaded node: ${parsed.displayName} (${parsed.nodeType})`);
console.log(`Extracted version: ${parsed.version}`);
console.log(`Is versioned: ${parsed.isVersioned}`);
console.log(`Expected version: ${testCase.expectedVersion}`);
console.log(`Expected versioned: ${testCase.expectedVersioned}`);
if (parsed.version === testCase.expectedVersion &&
parsed.isVersioned === testCase.expectedVersioned) {
console.log('✅ PASS');
} else {
console.log('❌ FAIL');
}
} catch (error) {
console.log(`❌ Error: ${error instanceof Error ? error.message : String(error)}`);
}
}
}
// Run the test
testVersionExtraction().catch(console.error);

142
versioned-nodes.md Normal file
View File

@@ -0,0 +1,142 @@
# Versioned Nodes in n8n
This document lists all nodes that have `version` defined as an array in their description.
## From n8n-nodes-base package:
1. **Airtop** - `Airtop.node.js`
2. **Cal Trigger** - `CalTrigger.node.js`
3. **Coda** - `Coda.node.js`
4. **Code** - `Code.node.js` - version: [1, 2]
5. **Compare Datasets** - `CompareDatasets.node.js`
6. **Compression** - `Compression.node.js`
7. **Convert To File** - `ConvertToFile.node.js`
8. **Email Send V2** - `EmailSendV2.node.js`
9. **Execute Workflow** - `ExecuteWorkflow.node.js`
10. **Execute Workflow Trigger** - `ExecuteWorkflowTrigger.node.js`
11. **Filter V2** - `FilterV2.node.js`
12. **Form Trigger V2** - `FormTriggerV2.node.js`
13. **GitHub** - `Github.node.js`
14. **Gmail Trigger** - `GmailTrigger.node.js`
15. **Gmail V2** - `GmailV2.node.js`
16. **Google Books** - `GoogleBooks.node.js`
17. **Google Calendar** - `GoogleCalendar.node.js`
18. **Google Docs** - `GoogleDocs.node.js`
19. **Google Drive V1** - `GoogleDriveV1.node.js`
20. **Google Firebase Cloud Firestore** - `GoogleFirebaseCloudFirestore.node.js`
21. **Google Slides** - `GoogleSlides.node.js`
22. **Google Translate** - `GoogleTranslate.node.js`
23. **GraphQL** - `GraphQL.node.js`
24. **HTML** - `Html.node.js`
25. **HTTP Request V3** - `HttpRequestV3.node.js` - version: [3, 4, 4.1, 4.2]
26. **HubSpot V2** - `HubspotV2.node.js`
27. **If V2** - `IfV2.node.js`
28. **Invoice Ninja** - `InvoiceNinja.node.js`
29. **Invoice Ninja Trigger** - `InvoiceNinjaTrigger.node.js`
30. **Item Lists V2** - `ItemListsV2.node.js`
31. **Jira Trigger** - `JiraTrigger.node.js`
32. **Kafka Trigger** - `KafkaTrigger.node.js`
33. **MailerLite Trigger V2** - `MailerLiteTriggerV2.node.js`
34. **MailerLite V2** - `MailerLiteV2.node.js`
35. **Merge V2** - `MergeV2.node.js`
36. **Microsoft SQL** - `MicrosoftSql.node.js`
37. **Microsoft Teams V1** - `MicrosoftTeamsV1.node.js`
38. **Mindee** - `Mindee.node.js`
39. **MongoDB** - `MongoDb.node.js`
40. **Move Binary Data** - `MoveBinaryData.node.js`
41. **NocoDB** - `NocoDB.node.js`
42. **OpenAI** - `OpenAi.node.js`
43. **Pipedrive Trigger** - `PipedriveTrigger.node.js`
44. **RabbitMQ** - `RabbitMQ.node.js`
45. **Remove Duplicates V1** - `RemoveDuplicatesV1.node.js`
46. **Remove Duplicates V2** - `RemoveDuplicatesV2.node.js`
47. **Respond To Webhook** - `RespondToWebhook.node.js`
48. **RSS Feed Read** - `RssFeedRead.node.js`
49. **Schedule Trigger** - `ScheduleTrigger.node.js`
50. **Set V1** - `SetV1.node.js`
51. **Set V2** - `SetV2.node.js`
52. **Slack V2** - `SlackV2.node.js`
53. **Strava** - `Strava.node.js`
54. **Summarize** - `Summarize.node.js`
55. **Switch V1** - `SwitchV1.node.js`
56. **Switch V2** - `SwitchV2.node.js`
57. **Switch V3** - `SwitchV3.node.js`
58. **Telegram** - `Telegram.node.js`
59. **Telegram Trigger** - `TelegramTrigger.node.js`
60. **The Hive Trigger** - `TheHiveTrigger.node.js`
61. **Todoist V2** - `TodoistV2.node.js`
62. **Twilio Trigger** - `TwilioTrigger.node.js`
63. **Typeform Trigger** - `TypeformTrigger.node.js`
64. **Wait** - `Wait.node.js`
65. **Webhook** - `Webhook.node.js` - version: [1, 1.1, 2]
## From @n8n/n8n-nodes-langchain package:
1. **Agent V1** - `AgentV1.node.js`
2. **Chain LLM** - `ChainLlm.node.js`
3. **Chain Retrieval QA** - `ChainRetrievalQa.node.js`
4. **Chain Summarization V2** - `ChainSummarizationV2.node.js`
5. **Chat Trigger** - `ChatTrigger.node.js`
6. **Document Default Data Loader** - `DocumentDefaultDataLoader.node.js`
7. **Document GitHub Loader** - `DocumentGithubLoader.node.js`
8. **Embeddings OpenAI** - `EmbeddingsOpenAi.node.js`
9. **Information Extractor** - `InformationExtractor.node.js`
10. **LM Chat Anthropic** - `LmChatAnthropic.node.js`
11. **LM Chat DeepSeek** - `LmChatDeepSeek.node.js`
12. **LM Chat OpenAI** - `LmChatOpenAi.node.js`
13. **LM Chat OpenRouter** - `LmChatOpenRouter.node.js`
14. **LM Chat xAI Grok** - `LmChatXAiGrok.node.js`
15. **Manual Chat Trigger** - `ManualChatTrigger.node.js`
16. **MCP Trigger** - `McpTrigger.node.js`
17. **Memory Buffer Window** - `MemoryBufferWindow.node.js`
18. **Memory Manager** - `MemoryManager.node.js`
19. **Memory MongoDB Chat** - `MemoryMongoDbChat.node.js`
20. **Memory Motorhead** - `MemoryMotorhead.node.js`
21. **Memory Postgres Chat** - `MemoryPostgresChat.node.js`
22. **Memory Redis Chat** - `MemoryRedisChat.node.js`
23. **Memory Xata** - `MemoryXata.node.js`
24. **Memory Zep** - `MemoryZep.node.js`
25. **OpenAI Assistant** - `OpenAiAssistant.node.js`
26. **Output Parser Structured** - `OutputParserStructured.node.js`
27. **Retriever Workflow** - `RetrieverWorkflow.node.js`
28. **Sentiment Analysis** - `SentimentAnalysis.node.js`
29. **Text Classifier** - `TextClassifier.node.js`
30. **Tool Code** - `ToolCode.node.js`
31. **Tool HTTP Request** - `ToolHttpRequest.node.js`
32. **Tool Vector Store** - `ToolVectorStore.node.js`
## Examples of Version Arrays Found
Here are some specific examples of version arrays from actual nodes:
### n8n-nodes-base:
- **Code**: `version: [1, 2]`
- **HTTP Request V3**: `version: [3, 4, 4.1, 4.2]`
- **Webhook**: `version: [1, 1.1, 2]`
- **Wait**: `version: [1, 1.1]`
- **Schedule Trigger**: `version: [1, 1.1, 1.2]`
- **Switch V3**: `version: [3, 3.1, 3.2]`
- **Set V2**: `version: [3, 3.1, 3.2, 3.3, 3.4]`
### @n8n/n8n-nodes-langchain:
- **LM Chat OpenAI**: `version: [1, 1.1, 1.2]`
- **Chain LLM**: `version: [1, 1.1, 1.2, 1.3, 1.4, 1.5, 1.6, 1.7]`
- **Tool HTTP Request**: `version: [1, 1.1]`
## Summary
Total nodes with version arrays: **97 nodes**
- From n8n-nodes-base: 65 nodes
- From @n8n/n8n-nodes-langchain: 32 nodes
These nodes use versioning to maintain backward compatibility while introducing new features or changes to their interface. The version array pattern allows n8n to:
1. Support multiple versions of the same node
2. Maintain backward compatibility with existing workflows
3. Introduce breaking changes in newer versions while keeping old versions functional
4. Use `defaultVersion` to specify which version new instances should use
Common version patterns observed:
- Simple incremental: `[1, 2]`, `[1, 2, 3]`
- Minor versions: `[1, 1.1, 1.2]` (common for bug fixes)
- Patch versions: `[3, 4, 4.1, 4.2]` (detailed version tracking)
- Extended versions: `[1, 1.1, 1.2, 1.3, 1.4, 1.5, 1.6, 1.7]` (Chain LLM has the most versions)