import { readFileSync, existsSync } from 'fs'; import { jsonParse, type INodeTypeDescription } from 'n8n-workflow'; import { join } from 'path'; interface NodeWithVersion extends INodeTypeDescription { version: number | number[]; defaultVersion?: number; } // These types are ignored because they tend to cause issues when generating workflows // Same as in ai-workflow-builder-agent.service.ts const IGNORED_TYPES = new Set([ '@n8n/n8n-nodes-langchain.toolVectorStore', '@n8n/n8n-nodes-langchain.documentGithubLoader', '@n8n/n8n-nodes-langchain.code', ]); // Parse disabled nodes from environment variable (comma-separated) function getDisabledNodes(): Set { const disabledNodesEnv = process.env.N8N_EVALS_DISABLED_NODES ?? ''; return new Set( disabledNodesEnv .split(',') .map((s) => s.trim()) .filter(Boolean), ); } /** * Filter node types similar to production service: * - Remove ignored types (hardcoded) * - Remove disabled types (from env var) * - Remove hidden nodes (except DataTable) * - Merge tool nodes with their non-tool counterparts */ function filterNodeTypes( nodeTypes: INodeTypeDescription[], disabledNodes: Set, ): INodeTypeDescription[] { const visibleNodeTypes = nodeTypes.filter( (nodeType) => !IGNORED_TYPES.has(nodeType.name) && !disabledNodes.has(nodeType.name) && // Filter out hidden nodes, except for the Data Table node which has custom hiding logic (nodeType.hidden !== true || nodeType.name === 'n8n-nodes-base.dataTable'), ); return visibleNodeTypes.map((nodeType) => { // If the node type is a tool, merge it with the corresponding non-tool node type const isTool = nodeType.name.endsWith('Tool'); if (!isTool) return nodeType; const nonToolNode = nodeTypes.find((nt) => nt.name === nodeType.name.replace('Tool', '')); if (!nonToolNode) return nodeType; return { ...nonToolNode, ...nodeType }; }); } export function loadNodesFromFile(): INodeTypeDescription[] { const preferredPath = join(__dirname, '..', '.data', 'nodes.json'); const legacyPath = join(__dirname, '..', 'nodes.json'); const nodesPath = existsSync(preferredPath) ? preferredPath : legacyPath; if (!existsSync(nodesPath)) { throw new Error( `nodes.json not found at ${nodesPath}. ` + 'Run n8n and export node definitions to evaluations/.data/nodes.json', ); } const nodesData = readFileSync(nodesPath, 'utf-8'); const allNodes = jsonParse(nodesData); // Keep all version entries instead of selecting only the latest version. // Workflows may use older node versions (e.g., removeDuplicates v1.1), and // discarding those entries causes "Node type not found" validation errors. const disabledNodes = getDisabledNodes(); return filterNodeTypes(allNodes, disabledNodes); }