n8n
Version:
n8n Workflow Automation Tool
423 lines • 19.8 kB
JavaScript
"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
return c > 3 && r && Object.defineProperty(target, key, r), r;
};
var __importStar = (this && this.__importStar) || (function () {
var ownKeys = function(o) {
ownKeys = Object.getOwnPropertyNames || function (o) {
var ar = [];
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
return ar;
};
return ownKeys(o);
};
return function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
__setModuleDefault(result, mod);
return result;
};
})();
var __metadata = (this && this.__metadata) || function (k, v) {
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
};
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.LoadNodesAndCredentials = void 0;
const config_1 = require("@n8n/config");
const di_1 = require("@n8n/di");
const fast_glob_1 = __importDefault(require("fast-glob"));
const promises_1 = __importDefault(require("fs/promises"));
const n8n_core_1 = require("n8n-core");
const n8n_workflow_1 = require("n8n-workflow");
const path_1 = __importDefault(require("path"));
const picocolors_1 = __importDefault(require("picocolors"));
const constants_1 = require("./constants");
const path_util_1 = require("./utils/path-util");
let LoadNodesAndCredentials = class LoadNodesAndCredentials {
constructor(logger, errorReporter, instanceSettings, globalConfig) {
this.logger = logger;
this.errorReporter = errorReporter;
this.instanceSettings = instanceSettings;
this.globalConfig = globalConfig;
this.known = { nodes: {}, credentials: {} };
this.loaded = { nodes: {}, credentials: {} };
this.types = { nodes: [], credentials: [] };
this.loaders = {};
this.excludeNodes = this.globalConfig.nodes.exclude;
this.includeNodes = this.globalConfig.nodes.include;
this.postProcessors = [];
}
async init() {
if (constants_1.inTest)
throw new n8n_workflow_1.UnexpectedError('Not available in tests');
const delimiter = process.platform === 'win32' ? ';' : ':';
process.env.NODE_PATH = module.paths.join(delimiter);
module.constructor._initPaths();
if (!constants_1.inE2ETests) {
this.excludeNodes = this.excludeNodes ?? [];
this.excludeNodes.push('n8n-nodes-base.e2eTest');
}
const basePathsToScan = [
path_1.default.join(constants_1.CLI_DIR, '..'),
path_1.default.join(constants_1.CLI_DIR, 'node_modules'),
];
for (const nodeModulesDir of basePathsToScan) {
await this.loadNodesFromNodeModules(nodeModulesDir, 'n8n-nodes-base');
await this.loadNodesFromNodeModules(nodeModulesDir, '@n8n/n8n-nodes-langchain');
}
await this.loadNodesFromNodeModules(path_1.default.join(this.instanceSettings.nodesDownloadDir, 'node_modules'));
await this.loadNodesFromCustomDirectories();
await this.postProcessLoaders();
}
addPostProcessor(fn) {
this.postProcessors.push(fn);
}
isKnownNode(type) {
return type in this.known.nodes;
}
get loadedCredentials() {
return this.loaded.credentials;
}
get loadedNodes() {
return this.loaded.nodes;
}
get knownCredentials() {
return this.known.credentials;
}
get knownNodes() {
return this.known.nodes;
}
async loadNodesFromNodeModules(nodeModulesDir, packageName) {
const globOptions = {
cwd: nodeModulesDir,
onlyDirectories: true,
deep: 1,
};
const installedPackagePaths = packageName
? await (0, fast_glob_1.default)(packageName, globOptions)
: [
...(await (0, fast_glob_1.default)('n8n-nodes-*', globOptions)),
...(await (0, fast_glob_1.default)('@*/n8n-nodes-*', { ...globOptions, deep: 2 })),
];
for (const packagePath of installedPackagePaths) {
try {
await this.runDirectoryLoader(n8n_core_1.LazyPackageDirectoryLoader, path_1.default.join(nodeModulesDir, packagePath));
}
catch (error) {
this.logger.error(error.message);
this.errorReporter.error(error);
}
}
}
resolveIcon(packageName, url) {
const loader = this.loaders[packageName];
if (!loader) {
return undefined;
}
const pathPrefix = `/icons/${packageName}/`;
const filePath = path_1.default.resolve(loader.directory, url.substring(pathPrefix.length));
return (0, path_util_1.isContainedWithin)(loader.directory, filePath) ? filePath : undefined;
}
resolveSchema({ node, version, resource, operation, }) {
const nodePath = this.known.nodes[node]?.sourcePath;
if (!nodePath) {
return undefined;
}
const nodeParentPath = path_1.default.dirname(nodePath);
const schemaPath = ['__schema__', `v${version}`, resource, operation].filter(Boolean).join('/');
const filePath = path_1.default.resolve(nodeParentPath, schemaPath + '.json');
return (0, path_util_1.isContainedWithin)(nodeParentPath, filePath) ? filePath : undefined;
}
getCustomDirectories() {
const customDirectories = [this.instanceSettings.customExtensionDir];
if (process.env[n8n_core_1.CUSTOM_EXTENSION_ENV] !== undefined) {
const customExtensionFolders = process.env[n8n_core_1.CUSTOM_EXTENSION_ENV].split(';');
customDirectories.push(...customExtensionFolders);
}
return customDirectories;
}
async loadNodesFromCustomDirectories() {
for (const directory of this.getCustomDirectories()) {
await this.runDirectoryLoader(n8n_core_1.CustomDirectoryLoader, directory);
}
}
async loadPackage(packageName) {
const finalNodeUnpackedPath = path_1.default.join(this.instanceSettings.nodesDownloadDir, 'node_modules', packageName);
return await this.runDirectoryLoader(n8n_core_1.PackageDirectoryLoader, finalNodeUnpackedPath);
}
async unloadPackage(packageName) {
if (packageName in this.loaders) {
this.loaders[packageName].reset();
delete this.loaders[packageName];
}
}
supportsProxyAuth(description) {
if (!description.credentials)
return false;
return description.credentials.some(({ name }) => {
const credType = this.types.credentials.find((t) => t.name === name);
if (!credType) {
this.logger.warn(`Failed to load Custom API options for the node "${description.name}": Unknown credential name "${name}"`);
return false;
}
if (credType.authenticate !== undefined)
return true;
return (Array.isArray(credType.extends) &&
credType.extends.some((parentType) => ['oAuth2Api', 'googleOAuth2Api', 'oAuth1Api'].includes(parentType)));
});
}
injectCustomApiCallOptions() {
this.types.nodes.forEach((node) => {
const isLatestVersion = node.defaultVersion === undefined || node.defaultVersion === node.version;
if (isLatestVersion) {
if (!this.supportsProxyAuth(node))
return;
node.properties.forEach((p) => {
if (['resource', 'operation'].includes(p.name) &&
Array.isArray(p.options) &&
p.options[p.options.length - 1].name !== constants_1.CUSTOM_API_CALL_NAME) {
p.options.push({
name: constants_1.CUSTOM_API_CALL_NAME,
value: constants_1.CUSTOM_API_CALL_KEY,
});
}
});
}
});
}
async runDirectoryLoader(constructor, dir) {
const loader = new constructor(dir, this.excludeNodes, this.includeNodes);
if (loader instanceof n8n_core_1.PackageDirectoryLoader && loader.packageName in this.loaders) {
throw new n8n_workflow_1.UserError(picocolors_1.default.red(`nodes package ${loader.packageName} is already loaded.\n Please delete this second copy at path ${dir}`));
}
await loader.loadAll();
this.loaders[loader.packageName] = loader;
return loader;
}
createAiTools() {
const usableNodes = this.types.nodes.filter((nodeType) => nodeType.usableAsTool);
for (const usableNode of usableNodes) {
const description = typeof usableNode.usableAsTool === 'object'
? {
...(0, n8n_workflow_1.deepCopy)(usableNode),
...usableNode.usableAsTool?.replacements,
}
: (0, n8n_workflow_1.deepCopy)(usableNode);
const wrapped = this.convertNodeToAiTool({ description }).description;
this.types.nodes.push(wrapped);
this.known.nodes[wrapped.name] = { ...this.known.nodes[usableNode.name] };
const credentialNames = Object.entries(this.known.credentials)
.filter(([_, credential]) => credential?.supportedNodes?.includes(usableNode.name))
.map(([credentialName]) => credentialName);
credentialNames.forEach((name) => this.known.credentials[name]?.supportedNodes?.push(wrapped.name));
}
}
async postProcessLoaders() {
this.known = { nodes: {}, credentials: {} };
this.loaded = { nodes: {}, credentials: {} };
this.types = { nodes: [], credentials: [] };
for (const loader of Object.values(this.loaders)) {
const { known, types, directory, packageName } = loader;
this.types.nodes = this.types.nodes.concat(types.nodes.map(({ name, ...rest }) => ({
...rest,
name: `${packageName}.${name}`,
})));
this.types.credentials = this.types.credentials.concat(types.credentials.map(({ supportedNodes, ...rest }) => ({
...rest,
supportedNodes: loader instanceof n8n_core_1.PackageDirectoryLoader
? supportedNodes?.map((nodeName) => `${loader.packageName}.${nodeName}`)
: undefined,
})));
for (const nodeTypeName in loader.nodeTypes) {
this.loaded.nodes[`${packageName}.${nodeTypeName}`] = loader.nodeTypes[nodeTypeName];
}
for (const credentialTypeName in loader.credentialTypes) {
this.loaded.credentials[credentialTypeName] = loader.credentialTypes[credentialTypeName];
}
for (const type in known.nodes) {
const { className, sourcePath } = known.nodes[type];
this.known.nodes[`${packageName}.${type}`] = {
className,
sourcePath: path_1.default.join(directory, sourcePath),
};
}
for (const type in known.credentials) {
const { className, sourcePath, supportedNodes, extends: extendsArr, } = known.credentials[type];
this.known.credentials[type] = {
className,
sourcePath: path_1.default.join(directory, sourcePath),
supportedNodes: loader instanceof n8n_core_1.PackageDirectoryLoader
? supportedNodes?.map((nodeName) => `${loader.packageName}.${nodeName}`)
: undefined,
extends: extendsArr,
};
}
}
this.createAiTools();
this.injectCustomApiCallOptions();
for (const postProcessor of this.postProcessors) {
await postProcessor();
}
}
recognizesNode(fullNodeType) {
const [packageName, nodeType] = fullNodeType.split('.');
const { loaders } = this;
const loader = loaders[packageName];
return !!loader && nodeType in loader.known.nodes;
}
getNode(fullNodeType) {
const [packageName, nodeType] = fullNodeType.split('.');
const { loaders } = this;
const loader = loaders[packageName];
if (!loader) {
throw new n8n_core_1.UnrecognizedNodeTypeError(packageName, nodeType);
}
return loader.getNode(nodeType);
}
getCredential(credentialType) {
const { loadedCredentials } = this;
for (const loader of Object.values(this.loaders)) {
if (credentialType in loader.known.credentials) {
const loaded = loader.getCredential(credentialType);
loadedCredentials[credentialType] = loaded;
}
}
if (credentialType in loadedCredentials) {
return loadedCredentials[credentialType];
}
throw new n8n_core_1.UnrecognizedCredentialTypeError(credentialType);
}
convertNodeToAiTool(item) {
function isFullDescription(obj) {
return typeof obj === 'object' && obj !== null && 'properties' in obj;
}
if (isFullDescription(item.description)) {
item.description.name += 'Tool';
item.description.inputs = [];
item.description.outputs = [n8n_workflow_1.NodeConnectionTypes.AiTool];
item.description.displayName += ' Tool';
delete item.description.usableAsTool;
const hasResource = item.description.properties.some((prop) => prop.name === 'resource');
const hasOperation = item.description.properties.some((prop) => prop.name === 'operation');
if (!item.description.properties.map((prop) => prop.name).includes('toolDescription')) {
const descriptionType = {
displayName: 'Tool Description',
name: 'descriptionType',
type: 'options',
noDataExpression: true,
options: [
{
name: 'Set Automatically',
value: 'auto',
description: 'Automatically set based on resource and operation',
},
{
name: 'Set Manually',
value: 'manual',
description: 'Manually set the description',
},
],
default: 'auto',
};
const descProp = {
displayName: 'Description',
name: 'toolDescription',
type: 'string',
default: item.description.description,
required: true,
typeOptions: { rows: 2 },
description: 'Explain to the LLM what this tool does, a good, specific description would allow LLMs to produce expected results much more often',
placeholder: `e.g. ${item.description.description}`,
};
item.description.properties.unshift(descProp);
if (hasResource || hasOperation) {
item.description.properties.unshift(descriptionType);
descProp.displayOptions = {
show: {
descriptionType: ['manual'],
},
};
}
}
}
const resources = item.description.codex?.resources ?? {};
item.description.codex = {
categories: ['AI'],
subcategories: {
AI: ['Tools'],
Tools: item.description.codex?.subcategories?.Tools ?? ['Other Tools'],
},
resources,
};
return item;
}
async setupHotReload() {
const { default: debounce } = await Promise.resolve().then(() => __importStar(require('lodash/debounce')));
const { watch } = await Promise.resolve().then(() => __importStar(require('chokidar')));
const { Push } = await Promise.resolve().then(() => __importStar(require('./push')));
const push = di_1.Container.get(Push);
Object.values(this.loaders).forEach(async (loader) => {
try {
await promises_1.default.access(loader.directory);
}
catch {
return;
}
const realModulePath = path_1.default.join(await promises_1.default.realpath(loader.directory), path_1.default.sep);
const reloader = debounce(async () => {
const modulesToUnload = Object.keys(require.cache).filter((filePath) => filePath.startsWith(realModulePath));
modulesToUnload.forEach((filePath) => {
delete require.cache[filePath];
});
loader.reset();
await loader.loadAll();
await this.postProcessLoaders();
push.broadcast({ type: 'nodeDescriptionUpdated', data: {} });
}, 100);
const toWatch = loader.isLazyLoaded
? ['**/nodes.json', '**/credentials.json']
: ['**/*.js', '**/*.json'];
const files = await (0, fast_glob_1.default)(toWatch, {
cwd: realModulePath,
ignore: ['node_modules/**'],
});
const watcher = watch(files, {
cwd: realModulePath,
ignoreInitial: true,
});
watcher.on('add', reloader).on('change', reloader).on('unlink', reloader);
});
}
};
exports.LoadNodesAndCredentials = LoadNodesAndCredentials;
exports.LoadNodesAndCredentials = LoadNodesAndCredentials = __decorate([
(0, di_1.Service)(),
__metadata("design:paramtypes", [n8n_core_1.Logger,
n8n_core_1.ErrorReporter,
n8n_core_1.InstanceSettings,
config_1.GlobalConfig])
], LoadNodesAndCredentials);
//# sourceMappingURL=load-nodes-and-credentials.js.map