File size: 9,212 Bytes
da2e594
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
import { logger } from './logger';

/**
 * Resolves various node type input formats to all possible template node type formats.
 * Templates store node types in full n8n format (e.g., "n8n-nodes-base.slack").
 * This function handles various input formats and expands them to all possible matches.
 * 
 * @param nodeTypes Array of node types in various formats
 * @returns Array of all possible template node type formats
 * 
 * @example
 * resolveTemplateNodeTypes(['slack']) 
 * // Returns: ['n8n-nodes-base.slack', 'n8n-nodes-base.slackTrigger']
 * 
 * resolveTemplateNodeTypes(['nodes-base.webhook'])
 * // Returns: ['n8n-nodes-base.webhook']
 * 
 * resolveTemplateNodeTypes(['httpRequest'])
 * // Returns: ['n8n-nodes-base.httpRequest']
 */
export function resolveTemplateNodeTypes(nodeTypes: string[]): string[] {
  const resolvedTypes = new Set<string>();
  
  for (const nodeType of nodeTypes) {
    // Add all variations for this node type
    const variations = generateTemplateNodeVariations(nodeType);
    variations.forEach(v => resolvedTypes.add(v));
  }
  
  const result = Array.from(resolvedTypes);
  logger.debug(`Resolved ${nodeTypes.length} input types to ${result.length} template variations`, {
    input: nodeTypes,
    output: result
  });
  
  return result;
}

/**
 * Generates all possible template node type variations for a single input.
 * 
 * @param nodeType Single node type in any format
 * @returns Array of possible template formats
 */
function generateTemplateNodeVariations(nodeType: string): string[] {
  const variations = new Set<string>();
  
  // If it's already in full n8n format, just return it
  if (nodeType.startsWith('n8n-nodes-base.') || nodeType.startsWith('@n8n/n8n-nodes-langchain.')) {
    variations.add(nodeType);
    return Array.from(variations);
  }
  
  // Handle partial prefix formats (e.g., "nodes-base.slack" -> "n8n-nodes-base.slack")
  if (nodeType.startsWith('nodes-base.')) {
    const nodeName = nodeType.replace('nodes-base.', '');
    variations.add(`n8n-nodes-base.${nodeName}`);
    // Also try camelCase variations
    addCamelCaseVariations(variations, nodeName, 'n8n-nodes-base');
  } else if (nodeType.startsWith('nodes-langchain.')) {
    const nodeName = nodeType.replace('nodes-langchain.', '');
    variations.add(`@n8n/n8n-nodes-langchain.${nodeName}`);
    // Also try camelCase variations
    addCamelCaseVariations(variations, nodeName, '@n8n/n8n-nodes-langchain');
  } else if (!nodeType.includes('.')) {
    // Bare node name (e.g., "slack", "webhook", "httpRequest")
    // Try both packages with various case combinations
    
    // For n8n-nodes-base
    variations.add(`n8n-nodes-base.${nodeType}`);
    addCamelCaseVariations(variations, nodeType, 'n8n-nodes-base');
    
    // For langchain (less common for bare names, but include for completeness)
    variations.add(`@n8n/n8n-nodes-langchain.${nodeType}`);
    addCamelCaseVariations(variations, nodeType, '@n8n/n8n-nodes-langchain');
    
    // Add common related node types (e.g., "slack" -> also include "slackTrigger")
    addRelatedNodeTypes(variations, nodeType);
  }
  
  return Array.from(variations);
}

/**
 * Adds camelCase variations for a node name.
 * 
 * @param variations Set to add variations to
 * @param nodeName The node name to create variations for
 * @param packagePrefix The package prefix to use
 */
function addCamelCaseVariations(variations: Set<string>, nodeName: string, packagePrefix: string): void {
  const lowerName = nodeName.toLowerCase();
  
  // Common patterns in n8n node names
  const patterns = [
    // Pattern: somethingTrigger (e.g., slackTrigger, webhookTrigger)
    { suffix: 'trigger', capitalize: true },
    { suffix: 'Trigger', capitalize: false },
    // Pattern: somethingRequest (e.g., httpRequest)
    { suffix: 'request', capitalize: true },
    { suffix: 'Request', capitalize: false },
    // Pattern: somethingDatabase (e.g., mysqlDatabase, postgresDatabase)
    { suffix: 'database', capitalize: true },
    { suffix: 'Database', capitalize: false },
    // Pattern: somethingSheet/Sheets (e.g., googleSheets)
    { suffix: 'sheet', capitalize: true },
    { suffix: 'Sheet', capitalize: false },
    { suffix: 'sheets', capitalize: true },
    { suffix: 'Sheets', capitalize: false },
  ];
  
  // Check if the lowercase name matches any pattern
  for (const pattern of patterns) {
    const lowerSuffix = pattern.suffix.toLowerCase();
    
    if (lowerName.endsWith(lowerSuffix)) {
      // Name already has the suffix, try different capitalizations
      const baseName = lowerName.slice(0, -lowerSuffix.length);
      if (baseName) {
        if (pattern.capitalize) {
          // Capitalize the suffix
          const capitalizedSuffix = pattern.suffix.charAt(0).toUpperCase() + pattern.suffix.slice(1).toLowerCase();
          variations.add(`${packagePrefix}.${baseName}${capitalizedSuffix}`);
        } else {
          // Use the suffix as-is
          variations.add(`${packagePrefix}.${baseName}${pattern.suffix}`);
        }
      }
    } else if (!lowerName.includes(lowerSuffix)) {
      // Name doesn't have the suffix, try adding it
      if (pattern.capitalize) {
        const capitalizedSuffix = pattern.suffix.charAt(0).toUpperCase() + pattern.suffix.slice(1).toLowerCase();
        variations.add(`${packagePrefix}.${lowerName}${capitalizedSuffix}`);
      }
    }
  }
  
  // Handle specific known cases
  const specificCases: Record<string, string[]> = {
    'http': ['httpRequest'],
    'httprequest': ['httpRequest'],
    'mysql': ['mysql', 'mysqlDatabase'],
    'postgres': ['postgres', 'postgresDatabase'],
    'postgresql': ['postgres', 'postgresDatabase'],
    'mongo': ['mongoDb', 'mongodb'],
    'mongodb': ['mongoDb', 'mongodb'],
    'google': ['googleSheets', 'googleDrive', 'googleCalendar'],
    'googlesheet': ['googleSheets'],
    'googlesheets': ['googleSheets'],
    'microsoft': ['microsoftTeams', 'microsoftExcel', 'microsoftOutlook'],
    'slack': ['slack'],
    'discord': ['discord'],
    'telegram': ['telegram'],
    'webhook': ['webhook'],
    'schedule': ['scheduleTrigger'],
    'cron': ['cron', 'scheduleTrigger'],
    'email': ['emailSend', 'emailReadImap', 'gmail'],
    'gmail': ['gmail', 'gmailTrigger'],
    'code': ['code'],
    'javascript': ['code'],
    'python': ['code'],
    'js': ['code'],
    'set': ['set'],
    'if': ['if'],
    'switch': ['switch'],
    'merge': ['merge'],
    'loop': ['splitInBatches'],
    'split': ['splitInBatches', 'splitOut'],
    'ai': ['openAi'],
    'openai': ['openAi'],
    'chatgpt': ['openAi'],
    'gpt': ['openAi'],
    'api': ['httpRequest', 'graphql', 'webhook'],
    'csv': ['spreadsheetFile', 'readBinaryFile'],
    'excel': ['microsoftExcel', 'spreadsheetFile'],
    'spreadsheet': ['spreadsheetFile', 'googleSheets', 'microsoftExcel'],
  };
  
  const cases = specificCases[lowerName];
  if (cases) {
    cases.forEach(c => variations.add(`${packagePrefix}.${c}`));
  }
}

/**
 * Adds related node types for common patterns.
 * For example, "slack" should also include "slackTrigger".
 * 
 * @param variations Set to add variations to
 * @param nodeName The base node name
 */
function addRelatedNodeTypes(variations: Set<string>, nodeName: string): void {
  const lowerName = nodeName.toLowerCase();
  
  // Map of base names to their related node types
  const relatedTypes: Record<string, string[]> = {
    'slack': ['slack', 'slackTrigger'],
    'gmail': ['gmail', 'gmailTrigger'],
    'telegram': ['telegram', 'telegramTrigger'],
    'discord': ['discord', 'discordTrigger'],
    'webhook': ['webhook', 'webhookTrigger'],
    'http': ['httpRequest', 'webhook'],
    'email': ['emailSend', 'emailReadImap', 'gmail', 'gmailTrigger'],
    'google': ['googleSheets', 'googleDrive', 'googleCalendar', 'googleDocs'],
    'microsoft': ['microsoftTeams', 'microsoftExcel', 'microsoftOutlook', 'microsoftOneDrive'],
    'database': ['postgres', 'mysql', 'mongoDb', 'redis', 'postgresDatabase', 'mysqlDatabase'],
    'db': ['postgres', 'mysql', 'mongoDb', 'redis'],
    'sql': ['postgres', 'mysql', 'mssql'],
    'nosql': ['mongoDb', 'redis', 'couchDb'],
    'schedule': ['scheduleTrigger', 'cron'],
    'time': ['scheduleTrigger', 'cron', 'wait'],
    'file': ['readBinaryFile', 'writeBinaryFile', 'moveBinaryFile'],
    'binary': ['readBinaryFile', 'writeBinaryFile', 'moveBinaryFile'],
    'csv': ['spreadsheetFile', 'readBinaryFile'],
    'excel': ['microsoftExcel', 'spreadsheetFile'],
    'json': ['code', 'set'],
    'transform': ['code', 'set', 'merge', 'splitInBatches'],
    'ai': ['openAi', 'agent', 'lmChatOpenAi', 'lmChatAnthropic'],
    'llm': ['openAi', 'agent', 'lmChatOpenAi', 'lmChatAnthropic', 'lmChatGoogleGemini'],
    'agent': ['agent', 'toolAgent'],
    'chat': ['chatTrigger', 'agent'],
  };
  
  const related = relatedTypes[lowerName];
  if (related) {
    related.forEach(r => {
      variations.add(`n8n-nodes-base.${r}`);
      // Also check if it might be a langchain node
      if (['agent', 'toolAgent', 'chatTrigger', 'lmChatOpenAi', 'lmChatAnthropic', 'lmChatGoogleGemini'].includes(r)) {
        variations.add(`@n8n/n8n-nodes-langchain.${r}`);
      }
    });
  }
}