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!
174 lines • 6.72 kB
JavaScript
/**
* Node Description Loader
*
* Loads real INodeTypeDescription from n8n node classes by instantiating them.
* This is how FlowEngine and @n8n/ai-workflow-builder get accurate parameter schemas.
*/
import { readFileSync } from 'fs';
import { join, dirname } from 'path';
import { createRequire } from 'module';
// Support both ESM and CommonJS
// In ESM, we need to create require. In CommonJS/bundled, it's already global.
let nodeRequire;
try {
// Try to use global require first (CommonJS/bundled environment)
if (typeof require !== 'undefined') {
nodeRequire = require;
}
else {
// ESM - need to create require from import.meta.url
// Use eval to prevent esbuild from seeing import.meta at build time
const getImportMetaUrl = new Function('return import.meta.url');
nodeRequire = createRequire(getImportMetaUrl());
}
}
catch (e) {
console.error('Failed to initialize require:', e);
// Fallback: try to use global require anyway
nodeRequire = typeof require !== 'undefined' ? require : null;
if (!nodeRequire) {
throw new Error('Cannot create require function - no require or import.meta available');
}
}
const descriptionCache = new Map();
/**
* Load node description by instantiating the node class
*/
export async function getNodeDescription(nodeType) {
if (descriptionCache.has(nodeType)) {
return descriptionCache.get(nodeType);
}
try {
// Extract package and node name
const [packageName, ...nodeNameParts] = nodeType.split('.');
const nodeName = nodeNameParts.join('.');
// Map package names to npm packages
const packageMap = {
'n8n-nodes-base': 'n8n-nodes-base',
'@n8n/n8n-nodes-langchain': '@n8n/n8n-nodes-langchain',
};
const npmPackage = packageMap[packageName];
if (!npmPackage) {
return null;
}
// Load package.json to find node files
const pkgPath = nodeRequire.resolve(`${npmPackage}/package.json`);
const pkg = JSON.parse(readFileSync(pkgPath, 'utf8'));
if (!pkg.n8n || !pkg.n8n.nodes) {
return null;
}
// Find the node file
const nodeFile = pkg.n8n.nodes.find((n) => {
const fileName = n.split('/').pop()?.replace('.node.js', '') || '';
const fileNodeName = fileName.charAt(0).toLowerCase() + fileName.slice(1);
return fileNodeName === nodeName || fileName.toLowerCase() === nodeName.toLowerCase();
});
if (!nodeFile) {
return null;
}
// Load the node module
const nodeModulePath = join(dirname(pkgPath), nodeFile);
const nodeModule = await import(nodeModulePath);
// Get the node class - find the class constructor export
let NodeClass = null;
for (const key of Object.keys(nodeModule)) {
if (key !== 'default' && key !== '__esModule' && key !== 'module.exports') {
const exported = nodeModule[key];
// Check if it's a class/constructor function
if (typeof exported === 'function' && exported.prototype) {
NodeClass = exported;
break;
}
}
}
// Fallback to default export
if (!NodeClass && nodeModule.default) {
NodeClass = nodeModule.default;
}
if (!NodeClass || typeof NodeClass !== 'function') {
return null;
}
// Instantiate the node to get its description
const instance = new NodeClass();
const description = instance.description;
if (!description) {
return null;
}
descriptionCache.set(nodeType, description);
return description;
}
catch (error) {
console.error(`Failed to load node description for ${nodeType}:`, error);
return null;
}
}
/**
* Get all node descriptions (loads all nodes)
*/
export async function getAllNodeDescriptions() {
const descriptions = new Map();
try {
// Load from n8n-nodes-base
const nodesBasePkgPath = nodeRequire.resolve('n8n-nodes-base/package.json');
const nodesBasePkg = JSON.parse(readFileSync(nodesBasePkgPath, 'utf8'));
if (nodesBasePkg.n8n && nodesBasePkg.n8n.nodes) {
for (const nodePath of nodesBasePkg.n8n.nodes) {
try {
const fileName = nodePath.split('/').pop()?.replace('.node.js', '') || '';
const nodeType = `n8n-nodes-base.${fileName.charAt(0).toLowerCase() + fileName.slice(1)}`;
const desc = await getNodeDescription(nodeType);
if (desc) {
descriptions.set(nodeType, desc);
}
}
catch (error) {
// Skip nodes that fail to load
}
}
}
// Load from @n8n/n8n-nodes-langchain 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) {
for (const nodePath of langchainPkg.n8n.nodes) {
try {
const fileName = nodePath.split('/').pop()?.replace('.node.js', '') || '';
const nodeType = `@n8n/n8n-nodes-langchain.${fileName.charAt(0).toLowerCase() + fileName.slice(1)}`;
const desc = await getNodeDescription(nodeType);
if (desc) {
descriptions.set(nodeType, desc);
}
}
catch (error) {
// Skip nodes that fail to load
}
}
}
}
catch {
// LangChain package not available
}
console.log(`Loaded ${descriptions.size} node descriptions with full parameter schemas`);
}
catch (error) {
console.error('Failed to load node descriptions:', error);
}
return descriptions;
}
/**
* Get default parameters from node description
*/
export function getDefaultParameters(description) {
const params = {};
if (!description.properties) {
return params;
}
for (const prop of description.properties) {
if (prop.default !== undefined) {
params[prop.name] = prop.default;
}
}
return params;
}
//# sourceMappingURL=node-descriptions.js.map