UNPKG

flowengine-n8n-workflow-builder

Version:

Build n8n workflows from text using AI. Connect to Claude, Cursor, or any LLM to generate and validate n8n workflows with expert knowledge and intelligent auto-fixing. Built by FlowEngine. Now with real node parameter schemas from n8n packages!

313 lines 12.7 kB
/** * Node Registry - Complete n8n Node Knowledge Base * * Dynamically loads ALL nodes from n8n packages (1000+ nodes) * Falls back to static registry in bundled environments */ import { readFileSync } from 'fs'; import { join, dirname } from 'path'; import { createRequire } from 'module'; import { STATIC_NODE_REGISTRY, STATIC_CATEGORIES } from './staticNodeRegistry.js'; import { MINIMAL_NODE_REGISTRY, MINIMAL_CATEGORIES } from './minimal-registry.js'; // Support both ESM and CommonJS // In ESM, we need to create require. In CommonJS/bundled, it's already global. let nodeRequire; // Get the current file URL/path for createRequire const getModuleURL = () => { // Try CommonJS first (bundled environments like Smithery) if (typeof __filename !== 'undefined') { return __filename; } // ESM - use import.meta.url directly (TypeScript will handle this) // @ts-ignore - import.meta is available in ESM if (typeof import.meta !== 'undefined' && import.meta.url) { // @ts-ignore return import.meta.url; } // Last resort fallback return process.cwd(); }; try { // Try to use global require first (CommonJS/bundled environment) if (typeof require !== 'undefined') { nodeRequire = require; } else { // ESM - create require from import.meta.url const moduleURL = getModuleURL(); nodeRequire = createRequire(moduleURL); } } catch (e) { console.error('Failed to initialize require:', e); // Fallback: try to use global require anyway nodeRequire = typeof require !== 'undefined' ? require : null; if (!nodeRequire) { console.error('Cannot create require function - node loading will be limited'); // Don't throw, just set to null and handle gracefully nodeRequire = null; } } // Pre-load minimal registry to ensure it's bundled const BUNDLED_FALLBACK = MINIMAL_NODE_REGISTRY; let nodeRegistry = null; let allCategories = null; /** * Load all nodes from n8n packages * This dynamically loads 600+ nodes from n8n-nodes-base and LangChain * Falls back to static registry in bundled environments */ function loadNodesFromPackage() { if (nodeRegistry) { return nodeRegistry; } const nodes = {}; const categories = new Set(); // Check if nodeRequire is available if (!nodeRequire) { console.warn('Node require not available - trying to load static registry from JSON file'); // Try multiple paths for the static registry JSON file const possibleJsonPaths = [ join(__dirname, 'staticNodeRegistry.json'), join(process.cwd(), 'staticNodeRegistry.json'), './staticNodeRegistry.json', '/opt/render/project/src/render-deploy/staticNodeRegistry.json' ]; for (const jsonPath of possibleJsonPaths) { try { console.log(`Trying to load static registry from: ${jsonPath}`); const jsonContent = readFileSync(jsonPath, 'utf8'); const registryData = JSON.parse(jsonContent); nodeRegistry = registryData.nodes; allCategories = registryData.categories; console.log(`✅ Loaded ${Object.keys(nodeRegistry).length} nodes from JSON registry: ${jsonPath}`); return nodeRegistry; } catch (fileError) { console.warn(`Failed to load from ${jsonPath}:`, fileError.message); } } // Try imported static registry (from .ts file) try { console.warn('All JSON paths failed, trying imported static registry (543 nodes)'); nodeRegistry = STATIC_NODE_REGISTRY; allCategories = STATIC_CATEGORIES; console.log(`✅ Loaded ${Object.keys(nodeRegistry).length} nodes from imported static registry`); return nodeRegistry; } catch (importError) { console.error('Failed to load static registry from import:', importError); } // Use minimal inline registry as absolute last resort console.warn('Using minimal inline registry (25 core nodes)'); nodeRegistry = BUNDLED_FALLBACK; // Use pre-loaded fallback to ensure bundling allCategories = MINIMAL_CATEGORIES; console.log(`✅ Loaded ${Object.keys(nodeRegistry).length} nodes from minimal inline registry`); return nodeRegistry; } try { // Load from n8n-nodes-base package const nodesBasePkgPath = nodeRequire.resolve('n8n-nodes-base/package.json'); const nodesBasePkg = JSON.parse(readFileSync(nodesBasePkgPath, 'utf8')); if (nodesBasePkg.n8n && nodesBasePkg.n8n.nodes) { nodesBasePkg.n8n.nodes.forEach((nodePath) => { try { // Extract node type from path const nodeFileName = nodePath.split('/').pop()?.replace('.node.js', '') || ''; const nodeType = `n8n-nodes-base.${nodeFileName.charAt(0).toLowerCase() + nodeFileName.slice(1)}`; // Try to load the .node.json file for metadata const jsonPath = nodePath.replace('.node.js', '.node.json'); let nodeMetadata = {}; try { const fullJsonPath = join(dirname(nodesBasePkgPath), jsonPath); nodeMetadata = JSON.parse(readFileSync(fullJsonPath, 'utf8')); } catch { // If no .node.json, use defaults } const category = nodeMetadata.categories?.[0] || 'Other'; categories.add(category); // Create display name from file name const displayName = nodeFileName .replace(/Trigger$/, ' Trigger') .replace(/([A-Z])/g, ' $1') .trim() .replace(/\s+/g, ' '); nodes[nodeType] = { type: nodeType, displayName: displayName, typeVersion: nodeMetadata.nodeVersion || 1, description: `${displayName} node for workflow automation`, category: category.toLowerCase(), categories: nodeMetadata.categories || [category], alias: nodeMetadata.alias || [], subcategories: nodeMetadata.subcategories || {}, defaultParameters: {}, requiresCredentials: !!nodeMetadata.resources?.credentialDocumentation, credentialType: nodeFileName.toLowerCase(), inputs: ['main'], outputs: ['main'], isTrigger: nodeFileName.includes('Trigger') || nodePath.includes('Trigger'), documentation: nodeMetadata.resources?.primaryDocumentation?.[0]?.url || null, }; } catch (error) { // Skip nodes that fail to load console.error(`Failed to load node from ${nodePath}:`, error); } }); } // Try to load LangChain nodes if available try { const langchainPkgPath = nodeRequire.resolve('@n8n/n8n-nodes-langchain/package.json'); const langchainPkg = JSON.parse(readFileSync(langchainPkgPath, 'utf8')); if (langchainPkg.n8n && langchainPkg.n8n.nodes) { langchainPkg.n8n.nodes.forEach((nodePath) => { try { const nodeFileName = nodePath.split('/').pop()?.replace('.node.js', '') || ''; const nodeType = `@n8n/n8n-nodes-langchain.${nodeFileName.charAt(0).toLowerCase() + nodeFileName.slice(1)}`; const displayName = nodeFileName .replace(/([A-Z])/g, ' $1') .trim() .replace(/\s+/g, ' '); nodes[nodeType] = { type: nodeType, displayName: displayName, typeVersion: 1, description: `${displayName} LangChain node`, category: 'ai', categories: ['AI', 'LangChain'], alias: [], subcategories: {}, defaultParameters: {}, requiresCredentials: false, inputs: ['main'], outputs: ['main'], isTrigger: false, documentation: null, }; categories.add('AI'); categories.add('LangChain'); } catch (error) { console.error(`Failed to load LangChain node from ${nodePath}:`, error); } }); } } catch { // LangChain package not available, skip } nodeRegistry = nodes; allCategories = Array.from(categories).sort(); console.log(`Loaded ${Object.keys(nodes).length} nodes from n8n packages`); } catch (error) { console.error('Failed to load nodes from package:', error); console.warn('Falling back to static node registry'); // Return static registry on error nodeRegistry = STATIC_NODE_REGISTRY; allCategories = STATIC_CATEGORIES; return nodeRegistry; } return nodes; } /** * Get all nodes (lazy loaded) */ export function getAllNodes() { const registry = loadNodesFromPackage(); return Object.values(registry); } /** * Get node by type */ export function getNode(type) { const registry = loadNodesFromPackage(); return registry[type] || null; } /** * Get nodes by category */ export function getNodesByCategory(category) { return getAllNodes().filter(node => node.categories.some(cat => cat.toLowerCase() === category.toLowerCase())); } /** * Search nodes by keyword */ export function searchNodes(query) { const lowerQuery = query.toLowerCase(); return getAllNodes().filter(node => node.displayName.toLowerCase().includes(lowerQuery) || node.type.toLowerCase().includes(lowerQuery) || node.description.toLowerCase().includes(lowerQuery) || node.alias.some(a => a.toLowerCase().includes(lowerQuery)) || node.categories.some(c => c.toLowerCase().includes(lowerQuery))); } /** * Get all available categories */ export function getCategories() { if (!allCategories) { loadNodesFromPackage(); } return allCategories || []; } /** * Get trigger nodes only */ export function getTriggerNodes() { return getAllNodes().filter(node => node.isTrigger); } /** * Get action nodes only */ export function getActionNodes() { return getAllNodes().filter(node => !node.isTrigger); } /** * Get nodes that require credentials */ export function getNodesRequiringCredentials() { return getAllNodes().filter(node => node.requiresCredentials); } /** * Get recommended nodes for a task description */ export function getRecommendedNodes(taskDescription) { const keywords = taskDescription.toLowerCase().split(/\s+/); const scored = getAllNodes().map(node => { let score = 0; keywords.forEach(keyword => { if (node.displayName.toLowerCase().includes(keyword)) score += 10; if (node.type.toLowerCase().includes(keyword)) score += 8; if (node.description.toLowerCase().includes(keyword)) score += 5; if (node.alias.some(a => a.toLowerCase().includes(keyword))) score += 7; if (node.categories.some(c => c.toLowerCase().includes(keyword))) score += 6; }); return { node, score }; }); return scored .filter(s => s.score > 0) .sort((a, b) => b.score - a.score) .slice(0, 10) .map(s => s.node); } /** * Get node count */ export function getNodeCount() { return getAllNodes().length; } /** * Check if a node type exists */ export function nodeExists(type) { const registry = loadNodesFromPackage(); return type in registry; } //# sourceMappingURL=nodes.js.map