2019-06-23 03:35:23 -07:00
|
|
|
import {
|
|
|
|
CUSTOM_EXTENSION_ENV,
|
|
|
|
UserSettings,
|
|
|
|
} from 'n8n-core';
|
|
|
|
import {
|
|
|
|
ICredentialType,
|
|
|
|
INodeType,
|
2019-08-08 11:38:25 -07:00
|
|
|
INodeTypeData,
|
2019-06-23 03:35:23 -07:00
|
|
|
} from 'n8n-workflow';
|
|
|
|
|
2019-07-21 10:47:41 -07:00
|
|
|
import * as config from '../config';
|
2019-06-24 03:47:44 -07:00
|
|
|
import {
|
|
|
|
access as fsAccess,
|
|
|
|
readdir as fsReaddir,
|
|
|
|
readFile as fsReadFile,
|
|
|
|
stat as fsStat,
|
|
|
|
} from 'fs';
|
|
|
|
import * as glob from 'glob-promise';
|
|
|
|
import * as path from 'path';
|
2019-09-19 05:14:37 -07:00
|
|
|
import { promisify } from 'util';
|
2019-06-23 03:35:23 -07:00
|
|
|
|
2019-06-24 03:47:44 -07:00
|
|
|
const fsAccessAsync = promisify(fsAccess);
|
|
|
|
const fsReaddirAsync = promisify(fsReaddir);
|
|
|
|
const fsReadFileAsync = promisify(fsReadFile);
|
|
|
|
const fsStatAsync = promisify(fsStat);
|
2019-06-23 03:35:23 -07:00
|
|
|
|
|
|
|
|
|
|
|
class LoadNodesAndCredentialsClass {
|
2019-08-08 11:38:25 -07:00
|
|
|
nodeTypes: INodeTypeData = {};
|
2019-06-23 03:35:23 -07:00
|
|
|
|
|
|
|
credentialTypes: {
|
|
|
|
[key: string]: ICredentialType
|
|
|
|
} = {};
|
|
|
|
|
|
|
|
excludeNodes: string[] | undefined = undefined;
|
|
|
|
|
|
|
|
nodeModulesPath = '';
|
|
|
|
|
2019-08-08 11:38:25 -07:00
|
|
|
async init() {
|
2019-06-23 03:35:23 -07:00
|
|
|
// Get the path to the node-modules folder to be later able
|
|
|
|
// to load the credentials and nodes
|
|
|
|
const checkPaths = [
|
|
|
|
// In case "n8n" package is in same node_modules folder.
|
|
|
|
path.join(__dirname, '..', '..', '..', 'n8n-workflow'),
|
|
|
|
// In case "n8n" package is the root and the packages are
|
|
|
|
// in the "node_modules" folder underneath it.
|
|
|
|
path.join(__dirname, '..', '..', 'node_modules', 'n8n-workflow'),
|
|
|
|
];
|
|
|
|
for (const checkPath of checkPaths) {
|
|
|
|
try {
|
2019-06-24 03:47:44 -07:00
|
|
|
await fsAccessAsync(checkPath);
|
2019-06-23 03:35:23 -07:00
|
|
|
// Folder exists, so use it.
|
|
|
|
this.nodeModulesPath = path.dirname(checkPath);
|
|
|
|
break;
|
|
|
|
} catch (error) {
|
|
|
|
// Folder does not exist so get next one
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (this.nodeModulesPath === '') {
|
|
|
|
throw new Error('Could not find "node_modules" folder!');
|
|
|
|
}
|
|
|
|
|
2019-07-21 10:47:41 -07:00
|
|
|
this.excludeNodes = config.get('nodes.exclude');
|
2019-06-23 03:35:23 -07:00
|
|
|
|
|
|
|
// Get all the installed packages which contain n8n nodes
|
|
|
|
const packages = await this.getN8nNodePackages();
|
|
|
|
|
|
|
|
for (const packageName of packages) {
|
|
|
|
await this.loadDataFromPackage(packageName);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Read nodes and credentials from custom directories
|
|
|
|
const customDirectories = [];
|
|
|
|
|
|
|
|
// Add "custom" folder in user-n8n folder
|
|
|
|
customDirectories.push(UserSettings.getUserN8nFolderCustomExtensionPath());
|
|
|
|
|
|
|
|
// Add folders from special environment variable
|
|
|
|
if (process.env[CUSTOM_EXTENSION_ENV] !== undefined) {
|
|
|
|
const customExtensionFolders = process.env[CUSTOM_EXTENSION_ENV]!.split(';');
|
|
|
|
customDirectories.push.apply(customDirectories, customExtensionFolders);
|
|
|
|
}
|
|
|
|
|
|
|
|
for (const directory of customDirectories) {
|
|
|
|
await this.loadDataFromDirectory('CUSTOM', directory);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Returns all the names of the packages which could
|
|
|
|
* contain n8n nodes
|
|
|
|
*
|
|
|
|
* @returns {Promise<string[]>}
|
|
|
|
* @memberof LoadNodesAndCredentialsClass
|
|
|
|
*/
|
|
|
|
async getN8nNodePackages(): Promise<string[]> {
|
|
|
|
const packages: string[] = [];
|
2019-06-24 03:47:44 -07:00
|
|
|
for (const file of await fsReaddirAsync(this.nodeModulesPath)) {
|
2019-06-23 03:35:23 -07:00
|
|
|
if (file.indexOf('n8n-nodes-') !== 0) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Check if it is really a folder
|
2019-06-24 03:47:44 -07:00
|
|
|
if (!(await fsStatAsync(path.join(this.nodeModulesPath, file))).isDirectory()) {
|
2019-06-23 03:35:23 -07:00
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
packages.push(file);
|
|
|
|
}
|
|
|
|
|
|
|
|
return packages;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Loads credentials from a file
|
|
|
|
*
|
|
|
|
* @param {string} credentialName The name of the credentials
|
|
|
|
* @param {string} filePath The file to read credentials from
|
|
|
|
* @returns {Promise<void>}
|
|
|
|
* @memberof N8nPackagesInformationClass
|
|
|
|
*/
|
|
|
|
async loadCredentialsFromFile(credentialName: string, filePath: string): Promise<void> {
|
|
|
|
const tempModule = require(filePath);
|
|
|
|
|
|
|
|
let tempCredential: ICredentialType;
|
|
|
|
try {
|
|
|
|
tempCredential = new tempModule[credentialName]() as ICredentialType;
|
|
|
|
} catch (e) {
|
|
|
|
if (e instanceof TypeError) {
|
|
|
|
throw new Error(`Class with name "${credentialName}" could not be found. Please check if the class is named correctly!`);
|
|
|
|
} else {
|
|
|
|
throw e;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
this.credentialTypes[credentialName] = tempCredential;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Loads a node from a file
|
|
|
|
*
|
|
|
|
* @param {string} packageName The package name to set for the found nodes
|
|
|
|
* @param {string} nodeName Tha name of the node
|
|
|
|
* @param {string} filePath The file to read node from
|
|
|
|
* @returns {Promise<void>}
|
|
|
|
* @memberof N8nPackagesInformationClass
|
|
|
|
*/
|
|
|
|
async loadNodeFromFile(packageName: string, nodeName: string, filePath: string): Promise<void> {
|
|
|
|
let tempNode: INodeType;
|
|
|
|
let fullNodeName: string;
|
|
|
|
|
|
|
|
const tempModule = require(filePath);
|
|
|
|
try {
|
|
|
|
tempNode = new tempModule[nodeName]() as INodeType;
|
|
|
|
} catch (error) {
|
|
|
|
console.error(`Error loading node "${nodeName}" from: "${filePath}"`);
|
|
|
|
throw error;
|
|
|
|
}
|
|
|
|
|
|
|
|
fullNodeName = packageName + '.' + tempNode.description.name;
|
|
|
|
tempNode.description.name = fullNodeName;
|
|
|
|
|
|
|
|
if (tempNode.description.icon !== undefined &&
|
|
|
|
tempNode.description.icon.startsWith('file:')) {
|
|
|
|
// If a file icon gets used add the full path
|
|
|
|
tempNode.description.icon = 'file:' + path.join(path.dirname(filePath), tempNode.description.icon.substr(5));
|
|
|
|
}
|
|
|
|
|
2019-08-08 11:38:25 -07:00
|
|
|
// Check if the node should be skiped
|
2019-06-23 03:35:23 -07:00
|
|
|
if (this.excludeNodes !== undefined && this.excludeNodes.includes(fullNodeName)) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2019-08-08 11:38:25 -07:00
|
|
|
this.nodeTypes[fullNodeName] = {
|
|
|
|
type: tempNode,
|
|
|
|
sourcePath: filePath,
|
|
|
|
};
|
2019-06-23 03:35:23 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Loads nodes and credentials from the given directory
|
|
|
|
*
|
|
|
|
* @param {string} setPackageName The package name to set for the found nodes
|
|
|
|
* @param {string} directory The directory to look in
|
|
|
|
* @returns {Promise<void>}
|
|
|
|
* @memberof N8nPackagesInformationClass
|
|
|
|
*/
|
|
|
|
async loadDataFromDirectory(setPackageName: string, directory: string): Promise<void> {
|
|
|
|
const files = await glob(path.join(directory, '*\.@(node|credentials)\.js'));
|
|
|
|
|
|
|
|
let fileName: string;
|
|
|
|
let type: string;
|
|
|
|
|
|
|
|
const loadPromises = [];
|
|
|
|
for (const filePath of files) {
|
|
|
|
[fileName, type] = path.parse(filePath).name.split('.');
|
|
|
|
|
|
|
|
if (type === 'node') {
|
|
|
|
loadPromises.push(this.loadNodeFromFile(setPackageName, fileName, filePath));
|
|
|
|
} else if (type === 'credentials') {
|
|
|
|
loadPromises.push(this.loadCredentialsFromFile(fileName, filePath));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
await Promise.all(loadPromises);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Loads nodes and credentials from the package with the given name
|
|
|
|
*
|
|
|
|
* @param {string} packageName The name to read data from
|
|
|
|
* @returns {Promise<void>}
|
|
|
|
* @memberof N8nPackagesInformationClass
|
|
|
|
*/
|
|
|
|
async loadDataFromPackage(packageName: string): Promise<void> {
|
|
|
|
// Get the absolute path of the package
|
|
|
|
const packagePath = path.join(this.nodeModulesPath, packageName);
|
|
|
|
|
|
|
|
// Read the data from the package.json file to see if any n8n data is defiend
|
2019-06-24 03:47:44 -07:00
|
|
|
const packageFileString = await fsReadFileAsync(path.join(packagePath, 'package.json'), 'utf8');
|
2019-06-23 03:35:23 -07:00
|
|
|
const packageFile = JSON.parse(packageFileString);
|
|
|
|
if (!packageFile.hasOwnProperty('n8n')) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
let tempPath: string, filePath: string;
|
|
|
|
|
|
|
|
// Read all node types
|
|
|
|
let fileName: string, type: string;
|
|
|
|
if (packageFile.n8n.hasOwnProperty('nodes') && Array.isArray(packageFile.n8n.nodes)) {
|
|
|
|
for (filePath of packageFile.n8n.nodes) {
|
|
|
|
tempPath = path.join(packagePath, filePath);
|
|
|
|
[fileName, type] = path.parse(filePath).name.split('.');
|
|
|
|
await this.loadNodeFromFile(packageName, fileName, tempPath);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Read all credential types
|
|
|
|
if (packageFile.n8n.hasOwnProperty('credentials') && Array.isArray(packageFile.n8n.credentials)) {
|
|
|
|
for (filePath of packageFile.n8n.credentials) {
|
|
|
|
tempPath = path.join(packagePath, filePath);
|
|
|
|
[fileName, type] = path.parse(filePath).name.split('.');
|
|
|
|
this.loadCredentialsFromFile(fileName, tempPath);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
let packagesInformationInstance: LoadNodesAndCredentialsClass | undefined;
|
|
|
|
|
|
|
|
export function LoadNodesAndCredentials(): LoadNodesAndCredentialsClass {
|
|
|
|
if (packagesInformationInstance === undefined) {
|
|
|
|
packagesInformationInstance = new LoadNodesAndCredentialsClass();
|
|
|
|
}
|
|
|
|
|
|
|
|
return packagesInformationInstance;
|
|
|
|
}
|