mirror of
https://github.com/Abdulazizzn/n8n-enterprise-unlocked.git
synced 2025-12-21 20:00:02 +00:00
🎨 Set up linting and formatting (#2120)
* ⬆️ Upgrade TS to 4.3.5 * 👕 Add ESLint configs * 🎨 Add Prettier config * 📦 Add deps and commands * ⚡ Adjust global .editorconfig to new ruleset * 🔥 Remove unneeded local .editorconfig * 📦 Update deps in editor-ui * 🔨 Limit Prettier to only TS files * ⚡ Add recommended VSCode extensions * 👕 Fix build * 🔥 Remove Vue setting from global config * ⚡ Disable prefer-default-export per feedback * ✏️ Add forgotten divider * 👕 Disable no-plusplus * 👕 Disable class-methods-use-this * ✏️ Alphabetize overrides * 👕 Add one-var consecutive override * ⏪ Revert one-var consecutive override This reverts commit b9252cf935659ba6d76727ad484a1d3c00008fcc. * 🎨 👕 Lint and format workflow package (#2121) * 🎨 Format /workflow package * 👕 Lint /workflow package * 🎨 Re-format /workflow package * 👕 Re-lint /workflow package * ✏️ Fix typo * ⚡ Consolidate if-checks * 🔥 Remove prefer-default-export exceptions * 🔥 Remove no-plusplus exceptions * 🔥 Remove class-methods-use-this exceptions * 🎨 👕 Lint and format node-dev package (#2122) * 🎨 Format /node-dev package * ⚡ Exclude templates from ESLint config This keeps the templates consistent with the codebase while preventing lint exceptions from being made part of the templates. * 👕 Lint /node-dev package * 🔥 Remove prefer-default-export exceptions * 🔥 Remove no-plusplus exceptions * 🎨 👕 Lint and format core package (#2123) * 🎨 Format /core package * 👕 Lint /core package * 🎨 Re-format /core package * 👕 Re-lint /core package * 🔥 Remove prefer-default-export exceptions * 🔥 Remove no-plusplus exceptions * 🔥 Remove class-methods-use-this exceptions * 🎨 👕 Lint and format cli package (#2124) * 🎨 Format /cli package * 👕 Exclude migrations from linting * 👕 Lint /cli package * 🎨 Re-format /cli package * 👕 Re-lint /cli package * 👕 Fix build * 🔥 Remove prefer-default-export exceptions * ⚡ Update exceptions in ActiveExecutions * 🔥 Remove no-plusplus exceptions * 🔥 Remove class-methods-use-this exceptions * 👕 fix lint issues * 🔧 use package specific linter, remove tslint command * 🔨 resolve build issue, sync dependencies * 🔧 change lint command Co-authored-by: Ben Hesseldieck <b.hesseldieck@gmail.com>
This commit is contained in:
@@ -6,10 +6,8 @@ import {
|
||||
WorkflowExecuteMode,
|
||||
} from 'n8n-workflow';
|
||||
|
||||
import {
|
||||
NodeExecuteFunctions,
|
||||
} from './';
|
||||
|
||||
// eslint-disable-next-line import/no-cycle
|
||||
import { NodeExecuteFunctions } from '.';
|
||||
|
||||
export class ActiveWebhooks {
|
||||
private workflowWebhooks: {
|
||||
@@ -22,7 +20,6 @@ export class ActiveWebhooks {
|
||||
|
||||
testWebhooks = false;
|
||||
|
||||
|
||||
/**
|
||||
* Adds a new webhook
|
||||
*
|
||||
@@ -31,19 +28,31 @@ export class ActiveWebhooks {
|
||||
* @returns {Promise<void>}
|
||||
* @memberof ActiveWebhooks
|
||||
*/
|
||||
async add(workflow: Workflow, webhookData: IWebhookData, mode: WorkflowExecuteMode, activation: WorkflowActivateMode): Promise<void> {
|
||||
async add(
|
||||
workflow: Workflow,
|
||||
webhookData: IWebhookData,
|
||||
mode: WorkflowExecuteMode,
|
||||
activation: WorkflowActivateMode,
|
||||
): Promise<void> {
|
||||
if (workflow.id === undefined) {
|
||||
throw new Error('Webhooks can only be added for saved workflows as an id is needed!');
|
||||
}
|
||||
if (webhookData.path.endsWith('/')) {
|
||||
// eslint-disable-next-line no-param-reassign
|
||||
webhookData.path = webhookData.path.slice(0, -1);
|
||||
}
|
||||
|
||||
const webhookKey = this.getWebhookKey(webhookData.httpMethod, webhookData.path, webhookData.webhookId);
|
||||
const webhookKey = this.getWebhookKey(
|
||||
webhookData.httpMethod,
|
||||
webhookData.path,
|
||||
webhookData.webhookId,
|
||||
);
|
||||
|
||||
//check that there is not a webhook already registed with that path/method
|
||||
// check that there is not a webhook already registed with that path/method
|
||||
if (this.webhookUrls[webhookKey] && !webhookData.webhookId) {
|
||||
throw new Error(`Test-Webhook can not be activated because another one with the same method "${webhookData.httpMethod}" and path "${webhookData.path}" is already active!`);
|
||||
throw new Error(
|
||||
`Test-Webhook can not be activated because another one with the same method "${webhookData.httpMethod}" and path "${webhookData.path}" is already active!`,
|
||||
);
|
||||
}
|
||||
|
||||
if (this.workflowWebhooks[webhookData.workflowId] === undefined) {
|
||||
@@ -58,18 +67,33 @@ export class ActiveWebhooks {
|
||||
this.webhookUrls[webhookKey].push(webhookData);
|
||||
|
||||
try {
|
||||
const webhookExists = await workflow.runWebhookMethod('checkExists', webhookData, NodeExecuteFunctions, mode, activation, this.testWebhooks);
|
||||
const webhookExists = await workflow.runWebhookMethod(
|
||||
'checkExists',
|
||||
webhookData,
|
||||
NodeExecuteFunctions,
|
||||
mode,
|
||||
activation,
|
||||
this.testWebhooks,
|
||||
);
|
||||
if (webhookExists !== true) {
|
||||
// If webhook does not exist yet create it
|
||||
await workflow.runWebhookMethod('create', webhookData, NodeExecuteFunctions, mode, activation, this.testWebhooks);
|
||||
|
||||
await workflow.runWebhookMethod(
|
||||
'create',
|
||||
webhookData,
|
||||
NodeExecuteFunctions,
|
||||
mode,
|
||||
activation,
|
||||
this.testWebhooks,
|
||||
);
|
||||
}
|
||||
} catch (error) {
|
||||
// If there was a problem unregister the webhook again
|
||||
if (this.webhookUrls[webhookKey].length <= 1) {
|
||||
delete this.webhookUrls[webhookKey];
|
||||
} else {
|
||||
this.webhookUrls[webhookKey] = this.webhookUrls[webhookKey].filter(webhook => webhook.path !== webhookData.path);
|
||||
this.webhookUrls[webhookKey] = this.webhookUrls[webhookKey].filter(
|
||||
(webhook) => webhook.path !== webhookData.path,
|
||||
);
|
||||
}
|
||||
|
||||
throw error;
|
||||
@@ -77,7 +101,6 @@ export class ActiveWebhooks {
|
||||
this.workflowWebhooks[webhookData.workflowId].push(webhookData);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns webhookData if a webhook with matches is currently registered
|
||||
*
|
||||
@@ -98,9 +121,9 @@ export class ActiveWebhooks {
|
||||
const pathElementsSet = new Set(path.split('/'));
|
||||
// check if static elements match in path
|
||||
// if more results have been returned choose the one with the most static-route matches
|
||||
this.webhookUrls[webhookKey].forEach(dynamicWebhook => {
|
||||
const staticElements = dynamicWebhook.path.split('/').filter(ele => !ele.startsWith(':'));
|
||||
const allStaticExist = staticElements.every(staticEle => pathElementsSet.has(staticEle));
|
||||
this.webhookUrls[webhookKey].forEach((dynamicWebhook) => {
|
||||
const staticElements = dynamicWebhook.path.split('/').filter((ele) => !ele.startsWith(':'));
|
||||
const allStaticExist = staticElements.every((staticEle) => pathElementsSet.has(staticEle));
|
||||
|
||||
if (allStaticExist && staticElements.length > maxMatches) {
|
||||
maxMatches = staticElements.length;
|
||||
@@ -120,13 +143,14 @@ export class ActiveWebhooks {
|
||||
* @param path
|
||||
*/
|
||||
getWebhookMethods(path: string): string[] {
|
||||
const methods : string[] = [];
|
||||
const methods: string[] = [];
|
||||
|
||||
Object.keys(this.webhookUrls)
|
||||
.filter(key => key.includes(path))
|
||||
.map(key => {
|
||||
methods.push(key.split('|')[0]);
|
||||
});
|
||||
.filter((key) => key.includes(path))
|
||||
// eslint-disable-next-line array-callback-return
|
||||
.map((key) => {
|
||||
methods.push(key.split('|')[0]);
|
||||
});
|
||||
|
||||
return methods;
|
||||
}
|
||||
@@ -141,7 +165,6 @@ export class ActiveWebhooks {
|
||||
return Object.keys(this.workflowWebhooks);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns key to uniquely identify a webhook
|
||||
*
|
||||
@@ -155,6 +178,7 @@ export class ActiveWebhooks {
|
||||
if (webhookId) {
|
||||
if (path.startsWith(webhookId)) {
|
||||
const cutFromIndex = path.indexOf('/') + 1;
|
||||
// eslint-disable-next-line no-param-reassign
|
||||
path = path.slice(cutFromIndex);
|
||||
}
|
||||
return `${httpMethod}|${webhookId}|${path.split('/').length}`;
|
||||
@@ -162,7 +186,6 @@ export class ActiveWebhooks {
|
||||
return `${httpMethod}|${path}`;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Removes all webhooks of a workflow
|
||||
*
|
||||
@@ -171,6 +194,7 @@ export class ActiveWebhooks {
|
||||
* @memberof ActiveWebhooks
|
||||
*/
|
||||
async removeWorkflow(workflow: Workflow): Promise<boolean> {
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||
const workflowId = workflow.id!.toString();
|
||||
|
||||
if (this.workflowWebhooks[workflowId] === undefined) {
|
||||
@@ -183,10 +207,21 @@ export class ActiveWebhooks {
|
||||
const mode = 'internal';
|
||||
|
||||
// Go through all the registered webhooks of the workflow and remove them
|
||||
// eslint-disable-next-line no-restricted-syntax
|
||||
for (const webhookData of webhooks) {
|
||||
await workflow.runWebhookMethod('delete', webhookData, NodeExecuteFunctions, mode, 'update', this.testWebhooks);
|
||||
// eslint-disable-next-line no-await-in-loop
|
||||
await workflow.runWebhookMethod(
|
||||
'delete',
|
||||
webhookData,
|
||||
NodeExecuteFunctions,
|
||||
mode,
|
||||
'update',
|
||||
this.testWebhooks,
|
||||
);
|
||||
|
||||
delete this.webhookUrls[this.getWebhookKey(webhookData.httpMethod, webhookData.path, webhookData.webhookId)];
|
||||
delete this.webhookUrls[
|
||||
this.getWebhookKey(webhookData.httpMethod, webhookData.path, webhookData.webhookId)
|
||||
];
|
||||
}
|
||||
|
||||
// Remove also the workflow-webhook entry
|
||||
@@ -195,18 +230,16 @@ export class ActiveWebhooks {
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Removes all the webhooks of the given workflows
|
||||
*/
|
||||
async removeAll(workflows: Workflow[]): Promise<void> {
|
||||
const removePromises = [];
|
||||
// eslint-disable-next-line no-restricted-syntax
|
||||
for (const workflow of workflows) {
|
||||
removePromises.push(this.removeWorkflow(workflow));
|
||||
}
|
||||
|
||||
await Promise.all(removePromises);
|
||||
return;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
/* eslint-disable no-continue */
|
||||
/* eslint-disable no-await-in-loop */
|
||||
/* eslint-disable no-restricted-syntax */
|
||||
import { CronJob } from 'cron';
|
||||
|
||||
import {
|
||||
@@ -13,18 +16,14 @@ import {
|
||||
WorkflowExecuteMode,
|
||||
} from 'n8n-workflow';
|
||||
|
||||
import {
|
||||
ITriggerTime,
|
||||
IWorkflowData,
|
||||
} from './';
|
||||
|
||||
// eslint-disable-next-line import/no-cycle
|
||||
import { ITriggerTime, IWorkflowData } from '.';
|
||||
|
||||
export class ActiveWorkflows {
|
||||
private workflowData: {
|
||||
[key: string]: IWorkflowData;
|
||||
} = {};
|
||||
|
||||
|
||||
/**
|
||||
* Returns if the workflow is active
|
||||
*
|
||||
@@ -33,10 +32,10 @@ export class ActiveWorkflows {
|
||||
* @memberof ActiveWorkflows
|
||||
*/
|
||||
isActive(id: string): boolean {
|
||||
// eslint-disable-next-line no-prototype-builtins
|
||||
return this.workflowData.hasOwnProperty(id);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns the ids of the currently active workflows
|
||||
*
|
||||
@@ -47,7 +46,6 @@ export class ActiveWorkflows {
|
||||
return Object.keys(this.workflowData);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns the Workflow data for the workflow with
|
||||
* the given id if it is currently active
|
||||
@@ -60,7 +58,6 @@ export class ActiveWorkflows {
|
||||
return this.workflowData[id];
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Makes a workflow active
|
||||
*
|
||||
@@ -70,16 +67,31 @@ export class ActiveWorkflows {
|
||||
* @returns {Promise<void>}
|
||||
* @memberof ActiveWorkflows
|
||||
*/
|
||||
async add(id: string, workflow: Workflow, additionalData: IWorkflowExecuteAdditionalData, mode: WorkflowExecuteMode, activation: WorkflowActivateMode, getTriggerFunctions: IGetExecuteTriggerFunctions, getPollFunctions: IGetExecutePollFunctions): Promise<void> {
|
||||
async add(
|
||||
id: string,
|
||||
workflow: Workflow,
|
||||
additionalData: IWorkflowExecuteAdditionalData,
|
||||
mode: WorkflowExecuteMode,
|
||||
activation: WorkflowActivateMode,
|
||||
getTriggerFunctions: IGetExecuteTriggerFunctions,
|
||||
getPollFunctions: IGetExecutePollFunctions,
|
||||
): Promise<void> {
|
||||
this.workflowData[id] = {};
|
||||
const triggerNodes = workflow.getTriggerNodes();
|
||||
|
||||
let triggerResponse: ITriggerResponse | undefined;
|
||||
this.workflowData[id].triggerResponses = [];
|
||||
for (const triggerNode of triggerNodes) {
|
||||
triggerResponse = await workflow.runTrigger(triggerNode, getTriggerFunctions, additionalData, mode, activation);
|
||||
triggerResponse = await workflow.runTrigger(
|
||||
triggerNode,
|
||||
getTriggerFunctions,
|
||||
additionalData,
|
||||
mode,
|
||||
activation,
|
||||
);
|
||||
if (triggerResponse !== undefined) {
|
||||
// If a response was given save it
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||
this.workflowData[id].triggerResponses!.push(triggerResponse);
|
||||
}
|
||||
}
|
||||
@@ -88,12 +100,21 @@ export class ActiveWorkflows {
|
||||
if (pollNodes.length) {
|
||||
this.workflowData[id].pollResponses = [];
|
||||
for (const pollNode of pollNodes) {
|
||||
this.workflowData[id].pollResponses!.push(await this.activatePolling(pollNode, workflow, additionalData, getPollFunctions, mode, activation));
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||
this.workflowData[id].pollResponses!.push(
|
||||
await this.activatePolling(
|
||||
pollNode,
|
||||
workflow,
|
||||
additionalData,
|
||||
getPollFunctions,
|
||||
mode,
|
||||
activation,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Activates polling for the given node
|
||||
*
|
||||
@@ -104,7 +125,14 @@ export class ActiveWorkflows {
|
||||
* @returns {Promise<IPollResponse>}
|
||||
* @memberof ActiveWorkflows
|
||||
*/
|
||||
async activatePolling(node: INode, workflow: Workflow, additionalData: IWorkflowExecuteAdditionalData, getPollFunctions: IGetExecutePollFunctions, mode: WorkflowExecuteMode, activation: WorkflowActivateMode): Promise<IPollResponse> {
|
||||
async activatePolling(
|
||||
node: INode,
|
||||
workflow: Workflow,
|
||||
additionalData: IWorkflowExecuteAdditionalData,
|
||||
getPollFunctions: IGetExecutePollFunctions,
|
||||
mode: WorkflowExecuteMode,
|
||||
activation: WorkflowActivateMode,
|
||||
): Promise<IPollResponse> {
|
||||
const pollFunctions = getPollFunctions(workflow, node, additionalData, mode, activation);
|
||||
|
||||
const pollTimes = pollFunctions.getNodeParameter('pollTimes') as unknown as {
|
||||
@@ -113,12 +141,12 @@ export class ActiveWorkflows {
|
||||
|
||||
// Define the order the cron-time-parameter appear
|
||||
const parameterOrder = [
|
||||
'second', // 0 - 59
|
||||
'minute', // 0 - 59
|
||||
'hour', // 0 - 23
|
||||
'second', // 0 - 59
|
||||
'minute', // 0 - 59
|
||||
'hour', // 0 - 23
|
||||
'dayOfMonth', // 1 - 31
|
||||
'month', // 0 - 11(Jan - Dec)
|
||||
'weekday', // 0 - 6(Sun - Sat)
|
||||
'month', // 0 - 11(Jan - Dec)
|
||||
'weekday', // 0 - 6(Sun - Sat)
|
||||
];
|
||||
|
||||
// Get all the trigger times
|
||||
@@ -165,10 +193,15 @@ export class ActiveWorkflows {
|
||||
|
||||
// The trigger function to execute when the cron-time got reached
|
||||
const executeTrigger = async () => {
|
||||
Logger.info(`Polling trigger initiated for workflow "${workflow.name}"`, {workflowName: workflow.name, workflowId: workflow.id});
|
||||
// eslint-disable-next-line @typescript-eslint/restrict-template-expressions
|
||||
Logger.info(`Polling trigger initiated for workflow "${workflow.name}"`, {
|
||||
workflowName: workflow.name,
|
||||
workflowId: workflow.id,
|
||||
});
|
||||
const pollResponse = await workflow.runPoll(node, pollFunctions);
|
||||
|
||||
if (pollResponse !== null) {
|
||||
// eslint-disable-next-line no-underscore-dangle
|
||||
pollFunctions.__emit(pollResponse);
|
||||
}
|
||||
};
|
||||
@@ -180,6 +213,7 @@ export class ActiveWorkflows {
|
||||
|
||||
// Start the cron-jobs
|
||||
const cronJobs: CronJob[] = [];
|
||||
// eslint-disable-next-line @typescript-eslint/no-shadow
|
||||
for (const cronTime of cronTimes) {
|
||||
const cronTimeParts = cronTime.split(' ');
|
||||
if (cronTimeParts.length > 0 && cronTimeParts[0].includes('*')) {
|
||||
@@ -201,7 +235,6 @@ export class ActiveWorkflows {
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Makes a workflow inactive
|
||||
*
|
||||
@@ -212,7 +245,9 @@ export class ActiveWorkflows {
|
||||
async remove(id: string): Promise<void> {
|
||||
if (!this.isActive(id)) {
|
||||
// Workflow is currently not registered
|
||||
throw new Error(`The workflow with the id "${id}" is currently not active and can so not be removed`);
|
||||
throw new Error(
|
||||
`The workflow with the id "${id}" is currently not active and can so not be removed`,
|
||||
);
|
||||
}
|
||||
|
||||
const workflowData = this.workflowData[id];
|
||||
@@ -235,5 +270,4 @@ export class ActiveWorkflows {
|
||||
|
||||
delete this.workflowData[id];
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -7,16 +7,13 @@ import {
|
||||
|
||||
import { AES, enc } from 'crypto-js';
|
||||
|
||||
|
||||
export class Credentials extends ICredentials {
|
||||
|
||||
|
||||
/**
|
||||
* Returns if the given nodeType has access to data
|
||||
*/
|
||||
hasNodeAccess(nodeType: string): boolean {
|
||||
// eslint-disable-next-line no-restricted-syntax
|
||||
for (const accessData of this.nodesAccess) {
|
||||
|
||||
if (accessData.nodeType === nodeType) {
|
||||
return true;
|
||||
}
|
||||
@@ -25,7 +22,6 @@ export class Credentials extends ICredentials {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Sets new credential object
|
||||
*/
|
||||
@@ -33,7 +29,6 @@ export class Credentials extends ICredentials {
|
||||
this.data = AES.encrypt(JSON.stringify(data), encryptionKey).toString();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Sets new credentials for given key
|
||||
*/
|
||||
@@ -50,13 +45,14 @@ export class Credentials extends ICredentials {
|
||||
return this.setData(fullData, encryptionKey);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns the decrypted credential object
|
||||
*/
|
||||
getData(encryptionKey: string, nodeType?: string): ICredentialDataDecryptedObject {
|
||||
if (nodeType && !this.hasNodeAccess(nodeType)) {
|
||||
throw new Error(`The node of type "${nodeType}" does not have access to credentials "${this.name}" of type "${this.type}".`);
|
||||
throw new Error(
|
||||
`The node of type "${nodeType}" does not have access to credentials "${this.name}" of type "${this.type}".`,
|
||||
);
|
||||
}
|
||||
|
||||
if (this.data === undefined) {
|
||||
@@ -66,13 +62,15 @@ export class Credentials extends ICredentials {
|
||||
const decryptedData = AES.decrypt(this.data, encryptionKey);
|
||||
|
||||
try {
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-return
|
||||
return JSON.parse(decryptedData.toString(enc.Utf8));
|
||||
} catch (e) {
|
||||
throw new Error('Credentials could not be decrypted. The likely reason is that a different "encryptionKey" was used to encrypt the data.');
|
||||
throw new Error(
|
||||
'Credentials could not be decrypted. The likely reason is that a different "encryptionKey" was used to encrypt the data.',
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns the decrypted credentials for given key
|
||||
*/
|
||||
@@ -83,6 +81,7 @@ export class Credentials extends ICredentials {
|
||||
throw new Error(`No data was set.`);
|
||||
}
|
||||
|
||||
// eslint-disable-next-line no-prototype-builtins
|
||||
if (!fullData.hasOwnProperty(key)) {
|
||||
throw new Error(`No data for key "${key}" exists.`);
|
||||
}
|
||||
@@ -90,7 +89,6 @@ export class Credentials extends ICredentials {
|
||||
return fullData[key];
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns the encrypted credentials to be saved
|
||||
*/
|
||||
|
||||
@@ -5,10 +5,10 @@ export interface IDeferredPromise<T> {
|
||||
resolve: (result: T) => void;
|
||||
}
|
||||
|
||||
export function createDeferredPromise<T>(): Promise<IDeferredPromise<T>> {
|
||||
return new Promise<IDeferredPromise<T>>(resolveCreate => {
|
||||
export async function createDeferredPromise<T>(): Promise<IDeferredPromise<T>> {
|
||||
return new Promise<IDeferredPromise<T>>((resolveCreate) => {
|
||||
const promise = new Promise<T>((resolve, reject) => {
|
||||
resolveCreate({ promise: () => promise, resolve, reject });
|
||||
resolveCreate({ promise: async () => promise, resolve, reject });
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
import {
|
||||
IAllExecuteFunctions,
|
||||
IBinaryData,
|
||||
@@ -16,70 +17,116 @@ import {
|
||||
ITriggerResponse,
|
||||
IWebhookFunctions as IWebhookFunctionsBase,
|
||||
IWorkflowSettings as IWorkflowSettingsWorkflow,
|
||||
} from 'n8n-workflow';
|
||||
} from 'n8n-workflow';
|
||||
|
||||
import { OptionsWithUri, OptionsWithUrl } from 'request';
|
||||
import * as requestPromise from 'request-promise-native';
|
||||
|
||||
interface Constructable<T> {
|
||||
new(): T;
|
||||
new (): T;
|
||||
}
|
||||
|
||||
export interface IProcessMessage {
|
||||
data?: any; // tslint:disable-line:no-any
|
||||
data?: any;
|
||||
type: string;
|
||||
}
|
||||
|
||||
|
||||
export interface IExecuteFunctions extends IExecuteFunctionsBase {
|
||||
helpers: {
|
||||
prepareBinaryData(binaryData: Buffer, filePath?: string, mimeType?: string): Promise<IBinaryData>;
|
||||
prepareBinaryData(
|
||||
binaryData: Buffer,
|
||||
filePath?: string,
|
||||
mimeType?: string,
|
||||
): Promise<IBinaryData>;
|
||||
getBinaryDataBuffer(itemIndex: number, propertyName: string): Promise<Buffer>;
|
||||
request: requestPromise.RequestPromiseAPI;
|
||||
requestOAuth2(this: IAllExecuteFunctions, credentialsType: string, requestOptions: OptionsWithUri | requestPromise.RequestPromiseOptions, oAuth2Options?: IOAuth2Options): Promise<any>; // tslint:disable-line:no-any
|
||||
requestOAuth1(this: IAllExecuteFunctions, credentialsType: string, requestOptions: OptionsWithUrl | requestPromise.RequestPromiseOptions): Promise<any>; // tslint:disable-line:no-any
|
||||
requestOAuth2(
|
||||
this: IAllExecuteFunctions,
|
||||
credentialsType: string,
|
||||
requestOptions: OptionsWithUri | requestPromise.RequestPromiseOptions,
|
||||
oAuth2Options?: IOAuth2Options,
|
||||
): Promise<any>; // tslint:disable-line:no-any
|
||||
requestOAuth1(
|
||||
this: IAllExecuteFunctions,
|
||||
credentialsType: string,
|
||||
requestOptions: OptionsWithUrl | requestPromise.RequestPromiseOptions,
|
||||
): Promise<any>; // tslint:disable-line:no-any
|
||||
returnJsonArray(jsonData: IDataObject | IDataObject[]): INodeExecutionData[];
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
export interface IExecuteSingleFunctions extends IExecuteSingleFunctionsBase {
|
||||
helpers: {
|
||||
prepareBinaryData(binaryData: Buffer, filePath?: string, mimeType?: string): Promise<IBinaryData>;
|
||||
request: requestPromise.RequestPromiseAPI,
|
||||
requestOAuth2(this: IAllExecuteFunctions, credentialsType: string, requestOptions: OptionsWithUri | requestPromise.RequestPromiseOptions, oAuth2Options?: IOAuth2Options): Promise<any>, // tslint:disable-line:no-any
|
||||
requestOAuth1(this: IAllExecuteFunctions, credentialsType: string, requestOptions: OptionsWithUrl | requestPromise.RequestPromiseOptions): Promise<any>, // tslint:disable-line:no-any
|
||||
prepareBinaryData(
|
||||
binaryData: Buffer,
|
||||
filePath?: string,
|
||||
mimeType?: string,
|
||||
): Promise<IBinaryData>;
|
||||
request: requestPromise.RequestPromiseAPI;
|
||||
requestOAuth2(
|
||||
this: IAllExecuteFunctions,
|
||||
credentialsType: string,
|
||||
requestOptions: OptionsWithUri | requestPromise.RequestPromiseOptions,
|
||||
oAuth2Options?: IOAuth2Options,
|
||||
): Promise<any>; // tslint:disable-line:no-any
|
||||
requestOAuth1(
|
||||
this: IAllExecuteFunctions,
|
||||
credentialsType: string,
|
||||
requestOptions: OptionsWithUrl | requestPromise.RequestPromiseOptions,
|
||||
): Promise<any>; // tslint:disable-line:no-any
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
export interface IPollFunctions extends IPollFunctionsBase {
|
||||
helpers: {
|
||||
prepareBinaryData(binaryData: Buffer, filePath?: string, mimeType?: string): Promise<IBinaryData>;
|
||||
request: requestPromise.RequestPromiseAPI,
|
||||
requestOAuth2(this: IAllExecuteFunctions, credentialsType: string, requestOptions: OptionsWithUri | requestPromise.RequestPromiseOptions, oAuth2Options?: IOAuth2Options): Promise<any>, // tslint:disable-line:no-any
|
||||
requestOAuth1(this: IAllExecuteFunctions, credentialsType: string, requestOptions: OptionsWithUrl | requestPromise.RequestPromiseOptions): Promise<any>, // tslint:disable-line:no-any
|
||||
prepareBinaryData(
|
||||
binaryData: Buffer,
|
||||
filePath?: string,
|
||||
mimeType?: string,
|
||||
): Promise<IBinaryData>;
|
||||
request: requestPromise.RequestPromiseAPI;
|
||||
requestOAuth2(
|
||||
this: IAllExecuteFunctions,
|
||||
credentialsType: string,
|
||||
requestOptions: OptionsWithUri | requestPromise.RequestPromiseOptions,
|
||||
oAuth2Options?: IOAuth2Options,
|
||||
): Promise<any>; // tslint:disable-line:no-any
|
||||
requestOAuth1(
|
||||
this: IAllExecuteFunctions,
|
||||
credentialsType: string,
|
||||
requestOptions: OptionsWithUrl | requestPromise.RequestPromiseOptions,
|
||||
): Promise<any>; // tslint:disable-line:no-any
|
||||
returnJsonArray(jsonData: IDataObject | IDataObject[]): INodeExecutionData[];
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
export interface IResponseError extends Error {
|
||||
statusCode?: number;
|
||||
}
|
||||
|
||||
|
||||
export interface ITriggerFunctions extends ITriggerFunctionsBase {
|
||||
helpers: {
|
||||
prepareBinaryData(binaryData: Buffer, filePath?: string, mimeType?: string): Promise<IBinaryData>;
|
||||
request: requestPromise.RequestPromiseAPI,
|
||||
requestOAuth2(this: IAllExecuteFunctions, credentialsType: string, requestOptions: OptionsWithUri | requestPromise.RequestPromiseOptions, oAuth2Options?: IOAuth2Options): Promise<any>, // tslint:disable-line:no-any
|
||||
requestOAuth1(this: IAllExecuteFunctions, credentialsType: string, requestOptions: OptionsWithUrl | requestPromise.RequestPromiseOptions): Promise<any>, // tslint:disable-line:no-any
|
||||
prepareBinaryData(
|
||||
binaryData: Buffer,
|
||||
filePath?: string,
|
||||
mimeType?: string,
|
||||
): Promise<IBinaryData>;
|
||||
request: requestPromise.RequestPromiseAPI;
|
||||
requestOAuth2(
|
||||
this: IAllExecuteFunctions,
|
||||
credentialsType: string,
|
||||
requestOptions: OptionsWithUri | requestPromise.RequestPromiseOptions,
|
||||
oAuth2Options?: IOAuth2Options,
|
||||
): Promise<any>; // tslint:disable-line:no-any
|
||||
requestOAuth1(
|
||||
this: IAllExecuteFunctions,
|
||||
credentialsType: string,
|
||||
requestOptions: OptionsWithUrl | requestPromise.RequestPromiseOptions,
|
||||
): Promise<any>; // tslint:disable-line:no-any
|
||||
returnJsonArray(jsonData: IDataObject | IDataObject[]): INodeExecutionData[];
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
export interface ITriggerTime {
|
||||
mode: string;
|
||||
hour: number;
|
||||
@@ -89,7 +136,6 @@ export interface ITriggerTime {
|
||||
[key: string]: string | number;
|
||||
}
|
||||
|
||||
|
||||
export interface IUserSettings {
|
||||
encryptionKey?: string;
|
||||
tunnelSubdomain?: string;
|
||||
@@ -97,28 +143,57 @@ export interface IUserSettings {
|
||||
|
||||
export interface ILoadOptionsFunctions extends ILoadOptionsFunctionsBase {
|
||||
helpers: {
|
||||
request?: requestPromise.RequestPromiseAPI,
|
||||
requestOAuth2?: (this: IAllExecuteFunctions, credentialsType: string, requestOptions: OptionsWithUri | requestPromise.RequestPromiseOptions, oAuth2Options?: IOAuth2Options) => Promise<any>, // tslint:disable-line:no-any
|
||||
requestOAuth1?(this: IAllExecuteFunctions, credentialsType: string, requestOptions: OptionsWithUrl | requestPromise.RequestPromiseOptions): Promise<any>, // tslint:disable-line:no-any
|
||||
request?: requestPromise.RequestPromiseAPI;
|
||||
requestOAuth2?: (
|
||||
this: IAllExecuteFunctions,
|
||||
credentialsType: string,
|
||||
requestOptions: OptionsWithUri | requestPromise.RequestPromiseOptions,
|
||||
oAuth2Options?: IOAuth2Options,
|
||||
) => Promise<any>; // tslint:disable-line:no-any
|
||||
requestOAuth1?(
|
||||
this: IAllExecuteFunctions,
|
||||
credentialsType: string,
|
||||
requestOptions: OptionsWithUrl | requestPromise.RequestPromiseOptions,
|
||||
): Promise<any>; // tslint:disable-line:no-any
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
export interface IHookFunctions extends IHookFunctionsBase {
|
||||
helpers: {
|
||||
request: requestPromise.RequestPromiseAPI,
|
||||
requestOAuth2(this: IAllExecuteFunctions, credentialsType: string, requestOptions: OptionsWithUri | requestPromise.RequestPromiseOptions, oAuth2Options?: IOAuth2Options): Promise<any>, // tslint:disable-line:no-any
|
||||
requestOAuth1(this: IAllExecuteFunctions, credentialsType: string, requestOptions: OptionsWithUrl | requestPromise.RequestPromiseOptions): Promise<any>, // tslint:disable-line:no-any
|
||||
request: requestPromise.RequestPromiseAPI;
|
||||
requestOAuth2(
|
||||
this: IAllExecuteFunctions,
|
||||
credentialsType: string,
|
||||
requestOptions: OptionsWithUri | requestPromise.RequestPromiseOptions,
|
||||
oAuth2Options?: IOAuth2Options,
|
||||
): Promise<any>; // tslint:disable-line:no-any
|
||||
requestOAuth1(
|
||||
this: IAllExecuteFunctions,
|
||||
credentialsType: string,
|
||||
requestOptions: OptionsWithUrl | requestPromise.RequestPromiseOptions,
|
||||
): Promise<any>; // tslint:disable-line:no-any
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
export interface IWebhookFunctions extends IWebhookFunctionsBase {
|
||||
helpers: {
|
||||
prepareBinaryData(binaryData: Buffer, filePath?: string, mimeType?: string): Promise<IBinaryData>;
|
||||
request: requestPromise.RequestPromiseAPI,
|
||||
requestOAuth2(this: IAllExecuteFunctions, credentialsType: string, requestOptions: OptionsWithUri | requestPromise.RequestPromiseOptions, oAuth2Options?: IOAuth2Options): Promise<any>, // tslint:disable-line:no-any
|
||||
requestOAuth1(this: IAllExecuteFunctions, credentialsType: string, requestOptions: OptionsWithUrl | requestPromise.RequestPromiseOptions): Promise<any>, // tslint:disable-line:no-any
|
||||
prepareBinaryData(
|
||||
binaryData: Buffer,
|
||||
filePath?: string,
|
||||
mimeType?: string,
|
||||
): Promise<IBinaryData>;
|
||||
request: requestPromise.RequestPromiseAPI;
|
||||
requestOAuth2(
|
||||
this: IAllExecuteFunctions,
|
||||
credentialsType: string,
|
||||
requestOptions: OptionsWithUri | requestPromise.RequestPromiseOptions,
|
||||
oAuth2Options?: IOAuth2Options,
|
||||
): Promise<any>; // tslint:disable-line:no-any
|
||||
requestOAuth1(
|
||||
this: IAllExecuteFunctions,
|
||||
credentialsType: string,
|
||||
requestOptions: OptionsWithUrl | requestPromise.RequestPromiseOptions,
|
||||
): Promise<any>; // tslint:disable-line:no-any
|
||||
returnJsonArray(jsonData: IDataObject | IDataObject[]): INodeExecutionData[];
|
||||
};
|
||||
}
|
||||
@@ -129,19 +204,16 @@ export interface IWorkflowSettings extends IWorkflowSettingsWorkflow {
|
||||
saveManualRuns?: boolean;
|
||||
}
|
||||
|
||||
|
||||
// New node definition in file
|
||||
export interface INodeDefinitionFile {
|
||||
[key: string]: Constructable<INodeType | ICredentialType>;
|
||||
}
|
||||
|
||||
|
||||
// Is identical to TaskDataConnections but does not allow null value to be used as input for nodes
|
||||
export interface INodeInputDataConnections {
|
||||
[key: string]: INodeExecutionData[][];
|
||||
}
|
||||
|
||||
|
||||
export interface IWorkflowData {
|
||||
pollResponses?: IPollResponse[];
|
||||
triggerResponses?: ITriggerResponse[];
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
/* eslint-disable @typescript-eslint/no-non-null-assertion */
|
||||
import {
|
||||
INode,
|
||||
INodeCredentials,
|
||||
@@ -8,21 +9,24 @@ import {
|
||||
Workflow,
|
||||
} from 'n8n-workflow';
|
||||
|
||||
import {
|
||||
NodeExecuteFunctions,
|
||||
} from './';
|
||||
|
||||
// eslint-disable-next-line import/no-cycle
|
||||
import { NodeExecuteFunctions } from '.';
|
||||
|
||||
const TEMP_NODE_NAME = 'Temp-Node';
|
||||
const TEMP_WORKFLOW_NAME = 'Temp-Workflow';
|
||||
|
||||
|
||||
export class LoadNodeParameterOptions {
|
||||
path: string;
|
||||
|
||||
workflow: Workflow;
|
||||
|
||||
|
||||
constructor(nodeTypeName: string, nodeTypes: INodeTypes, path: string, currentNodeParameters: INodeParameters, credentials?: INodeCredentials) {
|
||||
constructor(
|
||||
nodeTypeName: string,
|
||||
nodeTypes: INodeTypes,
|
||||
path: string,
|
||||
currentNodeParameters: INodeParameters,
|
||||
credentials?: INodeCredentials,
|
||||
) {
|
||||
this.path = path;
|
||||
const nodeType = nodeTypes.getByName(nodeTypeName);
|
||||
|
||||
@@ -35,10 +39,7 @@ export class LoadNodeParameterOptions {
|
||||
name: TEMP_NODE_NAME,
|
||||
type: nodeTypeName,
|
||||
typeVersion: 1,
|
||||
position: [
|
||||
0,
|
||||
0,
|
||||
],
|
||||
position: [0, 0],
|
||||
};
|
||||
|
||||
if (credentials) {
|
||||
@@ -46,22 +47,25 @@ export class LoadNodeParameterOptions {
|
||||
}
|
||||
|
||||
const workflowData = {
|
||||
nodes: [
|
||||
nodeData,
|
||||
],
|
||||
nodes: [nodeData],
|
||||
connections: {},
|
||||
};
|
||||
|
||||
this.workflow = new Workflow({ nodes: workflowData.nodes, connections: workflowData.connections, active: false, nodeTypes });
|
||||
this.workflow = new Workflow({
|
||||
nodes: workflowData.nodes,
|
||||
connections: workflowData.connections,
|
||||
active: false,
|
||||
nodeTypes,
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns data of a fake workflow
|
||||
*
|
||||
* @returns
|
||||
* @memberof LoadNodeParameterOptions
|
||||
*/
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
|
||||
getWorkflowData() {
|
||||
return {
|
||||
name: TEMP_WORKFLOW_NAME,
|
||||
@@ -73,7 +77,6 @@ export class LoadNodeParameterOptions {
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns the available options
|
||||
*
|
||||
@@ -82,18 +85,31 @@ export class LoadNodeParameterOptions {
|
||||
* @returns {Promise<INodePropertyOptions[]>}
|
||||
* @memberof LoadNodeParameterOptions
|
||||
*/
|
||||
getOptions(methodName: string, additionalData: IWorkflowExecuteAdditionalData): Promise<INodePropertyOptions[]> {
|
||||
async getOptions(
|
||||
methodName: string,
|
||||
additionalData: IWorkflowExecuteAdditionalData,
|
||||
): Promise<INodePropertyOptions[]> {
|
||||
const node = this.workflow.getNode(TEMP_NODE_NAME);
|
||||
|
||||
const nodeType = this.workflow.nodeTypes.getByName(node!.type);
|
||||
|
||||
if (nodeType!.methods === undefined || nodeType!.methods.loadOptions === undefined || nodeType!.methods.loadOptions[methodName] === undefined) {
|
||||
throw new Error(`The node-type "${node!.type}" does not have the method "${methodName}" defined!`);
|
||||
if (
|
||||
nodeType!.methods === undefined ||
|
||||
nodeType!.methods.loadOptions === undefined ||
|
||||
nodeType!.methods.loadOptions[methodName] === undefined
|
||||
) {
|
||||
throw new Error(
|
||||
`The node-type "${node!.type}" does not have the method "${methodName}" defined!`,
|
||||
);
|
||||
}
|
||||
|
||||
const thisArgs = NodeExecuteFunctions.getLoadOptionsFunctions(this.workflow, node!, this.path, additionalData);
|
||||
const thisArgs = NodeExecuteFunctions.getLoadOptionsFunctions(
|
||||
this.workflow,
|
||||
node!,
|
||||
this.path,
|
||||
additionalData,
|
||||
);
|
||||
|
||||
return nodeType!.methods.loadOptions[methodName].call(thisArgs);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,3 +1,11 @@
|
||||
/* eslint-disable no-param-reassign */
|
||||
/* eslint-disable @typescript-eslint/no-use-before-define */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-call */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
|
||||
import * as fs from 'fs';
|
||||
import * as path from 'path';
|
||||
import { randomBytes } from 'crypto';
|
||||
// eslint-disable-next-line import/no-cycle
|
||||
import {
|
||||
ENCRYPTION_KEY_ENV_OVERWRITE,
|
||||
EXTENSIONS_SUBDIRECTORY,
|
||||
@@ -7,20 +15,15 @@ import {
|
||||
USER_SETTINGS_SUBFOLDER,
|
||||
} from '.';
|
||||
|
||||
|
||||
import * as fs from 'fs';
|
||||
import * as path from 'path';
|
||||
import { randomBytes } from 'crypto';
|
||||
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
||||
const { promisify } = require('util');
|
||||
|
||||
const fsAccess = promisify(fs.access);
|
||||
const fsReadFile = promisify(fs.readFile);
|
||||
const fsMkdir = promisify(fs.mkdir);
|
||||
const fsWriteFile = promisify(fs.writeFile);
|
||||
|
||||
|
||||
|
||||
let settingsCache: IUserSettings | undefined = undefined;
|
||||
|
||||
let settingsCache: IUserSettings | undefined;
|
||||
|
||||
/**
|
||||
* Creates the user settings if they do not exist yet
|
||||
@@ -49,12 +52,12 @@ export async function prepareUserSettings(): Promise<IUserSettings> {
|
||||
userSettings.encryptionKey = randomBytes(24).toString('base64');
|
||||
}
|
||||
|
||||
// eslint-disable-next-line no-console
|
||||
console.log(`UserSettings were generated and saved to: ${settingsPath}`);
|
||||
|
||||
return writeUserSettings(userSettings, settingsPath);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns the encryption key which is used to encrypt
|
||||
* the credentials.
|
||||
@@ -62,6 +65,7 @@ export async function prepareUserSettings(): Promise<IUserSettings> {
|
||||
* @export
|
||||
* @returns
|
||||
*/
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
|
||||
export async function getEncryptionKey() {
|
||||
if (process.env[ENCRYPTION_KEY_ENV_OVERWRITE] !== undefined) {
|
||||
return process.env[ENCRYPTION_KEY_ENV_OVERWRITE];
|
||||
@@ -80,7 +84,6 @@ export async function getEncryptionKey() {
|
||||
return userSettings.encryptionKey;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Adds/Overwrite the given settings in the currently
|
||||
* saved user settings
|
||||
@@ -90,7 +93,10 @@ export async function getEncryptionKey() {
|
||||
* @param {string} [settingsPath] Optional settings file path
|
||||
* @returns {Promise<IUserSettings>}
|
||||
*/
|
||||
export async function addToUserSettings(addSettings: IUserSettings, settingsPath?: string): Promise<IUserSettings> {
|
||||
export async function addToUserSettings(
|
||||
addSettings: IUserSettings,
|
||||
settingsPath?: string,
|
||||
): Promise<IUserSettings> {
|
||||
if (settingsPath === undefined) {
|
||||
settingsPath = getUserSettingsPath();
|
||||
}
|
||||
@@ -107,7 +113,6 @@ export async function addToUserSettings(addSettings: IUserSettings, settingsPath
|
||||
return writeUserSettings(userSettings, settingsPath);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Writes a user settings file
|
||||
*
|
||||
@@ -116,7 +121,10 @@ export async function addToUserSettings(addSettings: IUserSettings, settingsPath
|
||||
* @param {string} [settingsPath] Optional settings file path
|
||||
* @returns {Promise<IUserSettings>}
|
||||
*/
|
||||
export async function writeUserSettings(userSettings: IUserSettings, settingsPath?: string): Promise<IUserSettings> {
|
||||
export async function writeUserSettings(
|
||||
userSettings: IUserSettings,
|
||||
settingsPath?: string,
|
||||
): Promise<IUserSettings> {
|
||||
if (settingsPath === undefined) {
|
||||
settingsPath = getUserSettingsPath();
|
||||
}
|
||||
@@ -139,14 +147,16 @@ export async function writeUserSettings(userSettings: IUserSettings, settingsPat
|
||||
return userSettings;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns the content of the user settings
|
||||
*
|
||||
* @export
|
||||
* @returns {UserSettings}
|
||||
*/
|
||||
export async function getUserSettings(settingsPath?: string, ignoreCache?: boolean): Promise<IUserSettings | undefined> {
|
||||
export async function getUserSettings(
|
||||
settingsPath?: string,
|
||||
ignoreCache?: boolean,
|
||||
): Promise<IUserSettings | undefined> {
|
||||
if (settingsCache !== undefined && ignoreCache !== true) {
|
||||
return settingsCache;
|
||||
}
|
||||
@@ -167,13 +177,14 @@ export async function getUserSettings(settingsPath?: string, ignoreCache?: boole
|
||||
try {
|
||||
settingsCache = JSON.parse(settingsFile);
|
||||
} catch (error) {
|
||||
throw new Error(`Error parsing n8n-config file "${settingsPath}". It does not seem to be valid JSON.`);
|
||||
throw new Error(
|
||||
`Error parsing n8n-config file "${settingsPath}". It does not seem to be valid JSON.`,
|
||||
);
|
||||
}
|
||||
|
||||
return settingsCache as IUserSettings;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns the path to the user settings
|
||||
*
|
||||
@@ -186,8 +197,6 @@ export function getUserSettingsPath(): string {
|
||||
return path.join(n8nFolder, USER_SETTINGS_FILE_NAME);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Retruns the path to the n8n folder in which all n8n
|
||||
* related data gets saved
|
||||
@@ -206,7 +215,6 @@ export function getUserN8nFolderPath(): string {
|
||||
return path.join(userFolder, USER_SETTINGS_SUBFOLDER);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns the path to the n8n user folder with the custom
|
||||
* extensions like nodes and credentials
|
||||
@@ -218,7 +226,6 @@ export function getUserN8nFolderCustomExtensionPath(): string {
|
||||
return path.join(getUserN8nFolderPath(), EXTENSIONS_SUBDIRECTORY);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns the home folder path of the user if
|
||||
* none can be found it falls back to the current
|
||||
|
||||
@@ -1,3 +1,14 @@
|
||||
/* eslint-disable @typescript-eslint/prefer-optional-chain */
|
||||
/* eslint-disable no-await-in-loop */
|
||||
/* eslint-disable no-labels */
|
||||
/* eslint-disable @typescript-eslint/no-non-null-assertion */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
|
||||
/* eslint-disable no-continue */
|
||||
/* eslint-disable no-prototype-builtins */
|
||||
/* eslint-disable no-restricted-syntax */
|
||||
/* eslint-disable no-param-reassign */
|
||||
/* eslint-disable @typescript-eslint/prefer-nullish-coalescing */
|
||||
import * as PCancelable from 'p-cancelable';
|
||||
|
||||
import {
|
||||
@@ -20,23 +31,27 @@ import {
|
||||
WorkflowExecuteMode,
|
||||
WorkflowOperationError,
|
||||
} from 'n8n-workflow';
|
||||
import {
|
||||
NodeExecuteFunctions,
|
||||
} from './';
|
||||
|
||||
// eslint-disable-next-line import/no-extraneous-dependencies
|
||||
import { get } from 'lodash';
|
||||
// eslint-disable-next-line import/no-cycle
|
||||
import { NodeExecuteFunctions } from '.';
|
||||
|
||||
export class WorkflowExecute {
|
||||
runExecutionData: IRunExecutionData;
|
||||
|
||||
private additionalData: IWorkflowExecuteAdditionalData;
|
||||
|
||||
private mode: WorkflowExecuteMode;
|
||||
|
||||
constructor(additionalData: IWorkflowExecuteAdditionalData, mode: WorkflowExecuteMode, runExecutionData?: IRunExecutionData) {
|
||||
constructor(
|
||||
additionalData: IWorkflowExecuteAdditionalData,
|
||||
mode: WorkflowExecuteMode,
|
||||
runExecutionData?: IRunExecutionData,
|
||||
) {
|
||||
this.additionalData = additionalData;
|
||||
this.mode = mode;
|
||||
this.runExecutionData = runExecutionData || {
|
||||
startData: {
|
||||
},
|
||||
startData: {},
|
||||
resultData: {
|
||||
runData: {},
|
||||
},
|
||||
@@ -48,8 +63,6 @@ export class WorkflowExecute {
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Executes the given workflow.
|
||||
*
|
||||
@@ -59,7 +72,8 @@ export class WorkflowExecute {
|
||||
* @returns {(Promise<string>)}
|
||||
* @memberof WorkflowExecute
|
||||
*/
|
||||
run(workflow: Workflow, startNode?: INode, destinationNode?: string): PCancelable<IRun> {
|
||||
// @ts-ignore
|
||||
async run(workflow: Workflow, startNode?: INode, destinationNode?: string): PCancelable<IRun> {
|
||||
// Get the nodes to start workflow execution from
|
||||
startNode = startNode || workflow.getStartNode(destinationNode);
|
||||
|
||||
@@ -68,7 +82,7 @@ export class WorkflowExecute {
|
||||
}
|
||||
|
||||
// If a destination node is given we only run the direct parent nodes and no others
|
||||
let runNodeFilter: string[] | undefined = undefined;
|
||||
let runNodeFilter: string[] | undefined;
|
||||
if (destinationNode) {
|
||||
runNodeFilter = workflow.getParentNodes(destinationNode);
|
||||
runNodeFilter.push(destinationNode);
|
||||
@@ -108,8 +122,6 @@ export class WorkflowExecute {
|
||||
return this.processRunExecutionData(workflow);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Executes the given workflow but only
|
||||
*
|
||||
@@ -121,7 +133,13 @@ export class WorkflowExecute {
|
||||
* @memberof WorkflowExecute
|
||||
*/
|
||||
// @ts-ignore
|
||||
async runPartialWorkflow(workflow: Workflow, runData: IRunData, startNodes: string[], destinationNode: string): PCancelable<IRun> {
|
||||
async runPartialWorkflow(
|
||||
workflow: Workflow,
|
||||
runData: IRunData,
|
||||
startNodes: string[],
|
||||
destinationNode: string,
|
||||
// @ts-ignore
|
||||
): PCancelable<IRun> {
|
||||
let incomingNodeConnections: INodeConnections | undefined;
|
||||
let connection: IConnection;
|
||||
|
||||
@@ -149,7 +167,8 @@ export class WorkflowExecute {
|
||||
for (let inputIndex = 0; inputIndex < connections.length; inputIndex++) {
|
||||
connection = connections[inputIndex];
|
||||
incomingData.push(
|
||||
runData[connection.node!][runIndex].data![connection.type][connection.index]!,
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||
runData[connection.node][runIndex].data![connection.type][connection.index]!,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -182,11 +201,12 @@ export class WorkflowExecute {
|
||||
waitingExecution[destinationNode][runIndex][connection.type] = [];
|
||||
}
|
||||
|
||||
|
||||
if (runData[connection.node!] !== undefined) {
|
||||
if (runData[connection.node] !== undefined) {
|
||||
// Input data exists so add as waiting
|
||||
// incomingDataDestination.push(runData[connection.node!][runIndex].data![connection.type][connection.index]);
|
||||
waitingExecution[destinationNode][runIndex][connection.type].push(runData[connection.node!][runIndex].data![connection.type][connection.index]);
|
||||
waitingExecution[destinationNode][runIndex][connection.type].push(
|
||||
runData[connection.node][runIndex].data![connection.type][connection.index],
|
||||
);
|
||||
} else {
|
||||
waitingExecution[destinationNode][runIndex][connection.type].push(null);
|
||||
}
|
||||
@@ -196,7 +216,8 @@ export class WorkflowExecute {
|
||||
}
|
||||
|
||||
// Only run the parent nodes and no others
|
||||
let runNodeFilter: string[] | undefined = undefined;
|
||||
let runNodeFilter: string[] | undefined;
|
||||
// eslint-disable-next-line prefer-const
|
||||
runNodeFilter = workflow.getParentNodes(destinationNode);
|
||||
runNodeFilter.push(destinationNode);
|
||||
|
||||
@@ -218,8 +239,6 @@ export class WorkflowExecute {
|
||||
return this.processRunExecutionData(workflow);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Executes the hook with the given name
|
||||
*
|
||||
@@ -228,22 +247,31 @@ export class WorkflowExecute {
|
||||
* @returns {Promise<IRun>}
|
||||
* @memberof WorkflowExecute
|
||||
*/
|
||||
async executeHook(hookName: string, parameters: any[]): Promise<void> { // tslint:disable-line:no-any
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
async executeHook(hookName: string, parameters: any[]): Promise<void> {
|
||||
// tslint:disable-line:no-any
|
||||
if (this.additionalData.hooks === undefined) {
|
||||
return;
|
||||
}
|
||||
|
||||
// eslint-disable-next-line consistent-return
|
||||
return this.additionalData.hooks.executeHookFunctions(hookName, parameters);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Checks the incoming connection does not receive any data
|
||||
*/
|
||||
incomingConnectionIsEmpty(runData: IRunData, inputConnections: IConnection[], runIndex: number): boolean {
|
||||
incomingConnectionIsEmpty(
|
||||
runData: IRunData,
|
||||
inputConnections: IConnection[],
|
||||
runIndex: number,
|
||||
): boolean {
|
||||
// for (const inputConnection of workflow.connectionsByDestinationNode[nodeToAdd].main[0]) {
|
||||
for (const inputConnection of inputConnections) {
|
||||
const nodeIncomingData = get(runData, `[${inputConnection.node}][${runIndex}].data.main[${inputConnection.index}]`);
|
||||
const nodeIncomingData = get(
|
||||
runData,
|
||||
`[${inputConnection.node}][${runIndex}].data.main[${inputConnection.index}]`,
|
||||
);
|
||||
if (nodeIncomingData !== undefined && (nodeIncomingData as object[]).length !== 0) {
|
||||
return false;
|
||||
}
|
||||
@@ -251,79 +279,117 @@ export class WorkflowExecute {
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
addNodeToBeExecuted(workflow: Workflow, connectionData: IConnection, outputIndex: number, parentNodeName: string, nodeSuccessData: INodeExecutionData[][], runIndex: number): void {
|
||||
addNodeToBeExecuted(
|
||||
workflow: Workflow,
|
||||
connectionData: IConnection,
|
||||
outputIndex: number,
|
||||
parentNodeName: string,
|
||||
nodeSuccessData: INodeExecutionData[][],
|
||||
runIndex: number,
|
||||
): void {
|
||||
let stillDataMissing = false;
|
||||
|
||||
// Check if node has multiple inputs as then we have to wait for all input data
|
||||
// to be present before we can add it to the node-execution-stack
|
||||
if (workflow.connectionsByDestinationNode[connectionData.node]['main'].length > 1) {
|
||||
if (workflow.connectionsByDestinationNode[connectionData.node].main.length > 1) {
|
||||
// Node has multiple inputs
|
||||
let nodeWasWaiting = true;
|
||||
|
||||
// Check if there is already data for the node
|
||||
if (this.runExecutionData.executionData!.waitingExecution[connectionData.node] === undefined) {
|
||||
if (
|
||||
this.runExecutionData.executionData!.waitingExecution[connectionData.node] === undefined
|
||||
) {
|
||||
// Node does not have data yet so create a new empty one
|
||||
this.runExecutionData.executionData!.waitingExecution[connectionData.node] = {};
|
||||
nodeWasWaiting = false;
|
||||
}
|
||||
if (this.runExecutionData.executionData!.waitingExecution[connectionData.node][runIndex] === undefined) {
|
||||
if (
|
||||
this.runExecutionData.executionData!.waitingExecution[connectionData.node][runIndex] ===
|
||||
undefined
|
||||
) {
|
||||
// Node does not have data for runIndex yet so create also empty one and init it
|
||||
this.runExecutionData.executionData!.waitingExecution[connectionData.node][runIndex] = {
|
||||
main: [],
|
||||
};
|
||||
for (let i = 0; i < workflow.connectionsByDestinationNode[connectionData.node]['main'].length; i++) {
|
||||
this.runExecutionData.executionData!.waitingExecution[connectionData.node][runIndex].main.push(null);
|
||||
for (
|
||||
let i = 0;
|
||||
i < workflow.connectionsByDestinationNode[connectionData.node].main.length;
|
||||
i++
|
||||
) {
|
||||
this.runExecutionData.executionData!.waitingExecution[connectionData.node][
|
||||
runIndex
|
||||
].main.push(null);
|
||||
}
|
||||
}
|
||||
|
||||
// Add the new data
|
||||
if (nodeSuccessData === null) {
|
||||
this.runExecutionData.executionData!.waitingExecution[connectionData.node][runIndex].main[connectionData.index] = null;
|
||||
this.runExecutionData.executionData!.waitingExecution[connectionData.node][runIndex].main[
|
||||
connectionData.index
|
||||
] = null;
|
||||
} else {
|
||||
this.runExecutionData.executionData!.waitingExecution[connectionData.node][runIndex].main[connectionData.index] = nodeSuccessData[outputIndex];
|
||||
this.runExecutionData.executionData!.waitingExecution[connectionData.node][runIndex].main[
|
||||
connectionData.index
|
||||
] = nodeSuccessData[outputIndex];
|
||||
}
|
||||
|
||||
// Check if all data exists now
|
||||
let thisExecutionData: INodeExecutionData[] | null;
|
||||
let allDataFound = true;
|
||||
for (let i = 0; i < this.runExecutionData.executionData!.waitingExecution[connectionData.node][runIndex].main.length; i++) {
|
||||
thisExecutionData = this.runExecutionData.executionData!.waitingExecution[connectionData.node][runIndex].main[i];
|
||||
for (
|
||||
let i = 0;
|
||||
i <
|
||||
this.runExecutionData.executionData!.waitingExecution[connectionData.node][runIndex].main
|
||||
.length;
|
||||
i++
|
||||
) {
|
||||
thisExecutionData =
|
||||
this.runExecutionData.executionData!.waitingExecution[connectionData.node][runIndex].main[
|
||||
i
|
||||
];
|
||||
if (thisExecutionData === null) {
|
||||
allDataFound = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (allDataFound === true) {
|
||||
if (allDataFound) {
|
||||
// All data exists for node to be executed
|
||||
// So add it to the execution stack
|
||||
this.runExecutionData.executionData!.nodeExecutionStack.push({
|
||||
node: workflow.nodes[connectionData.node],
|
||||
data: this.runExecutionData.executionData!.waitingExecution[connectionData.node][runIndex],
|
||||
data: this.runExecutionData.executionData!.waitingExecution[connectionData.node][
|
||||
runIndex
|
||||
],
|
||||
});
|
||||
|
||||
// Remove the data from waiting
|
||||
delete this.runExecutionData.executionData!.waitingExecution[connectionData.node][runIndex];
|
||||
|
||||
if (Object.keys(this.runExecutionData.executionData!.waitingExecution[connectionData.node]).length === 0) {
|
||||
if (
|
||||
Object.keys(this.runExecutionData.executionData!.waitingExecution[connectionData.node])
|
||||
.length === 0
|
||||
) {
|
||||
// No more data left for the node so also delete that one
|
||||
delete this.runExecutionData.executionData!.waitingExecution[connectionData.node];
|
||||
}
|
||||
return;
|
||||
} else {
|
||||
stillDataMissing = true;
|
||||
}
|
||||
stillDataMissing = true;
|
||||
|
||||
if (nodeWasWaiting === false) {
|
||||
|
||||
if (!nodeWasWaiting) {
|
||||
// Get a list of all the output nodes that we can check for siblings easier
|
||||
const checkOutputNodes = [];
|
||||
// eslint-disable-next-line @typescript-eslint/no-for-in-array
|
||||
for (const outputIndexParent in workflow.connectionsBySourceNode[parentNodeName].main) {
|
||||
if (!workflow.connectionsBySourceNode[parentNodeName].main.hasOwnProperty(outputIndexParent)) {
|
||||
if (
|
||||
!workflow.connectionsBySourceNode[parentNodeName].main.hasOwnProperty(outputIndexParent)
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
for (const connectionDataCheck of workflow.connectionsBySourceNode[parentNodeName].main[outputIndexParent]) {
|
||||
for (const connectionDataCheck of workflow.connectionsBySourceNode[parentNodeName].main[
|
||||
outputIndexParent
|
||||
]) {
|
||||
checkOutputNodes.push(connectionDataCheck.node);
|
||||
}
|
||||
}
|
||||
@@ -332,14 +398,22 @@ export class WorkflowExecute {
|
||||
// checked. So we have to go through all the inputs and check if they
|
||||
// are already on the list to be processed.
|
||||
// If that is not the case add it.
|
||||
for (let inputIndex = 0; inputIndex < workflow.connectionsByDestinationNode[connectionData.node]['main'].length; inputIndex++) {
|
||||
for (const inputData of workflow.connectionsByDestinationNode[connectionData.node]['main'][inputIndex]) {
|
||||
for (
|
||||
let inputIndex = 0;
|
||||
inputIndex < workflow.connectionsByDestinationNode[connectionData.node].main.length;
|
||||
inputIndex++
|
||||
) {
|
||||
for (const inputData of workflow.connectionsByDestinationNode[connectionData.node].main[
|
||||
inputIndex
|
||||
]) {
|
||||
if (inputData.node === parentNodeName) {
|
||||
// Is the node we come from so its data will be available for sure
|
||||
continue;
|
||||
}
|
||||
|
||||
const executionStackNodes = this.runExecutionData.executionData!.nodeExecutionStack.map((stackData) => stackData.node.name);
|
||||
const executionStackNodes = this.runExecutionData.executionData!.nodeExecutionStack.map(
|
||||
(stackData) => stackData.node.name,
|
||||
);
|
||||
|
||||
// Check if that node is also an output connection of the
|
||||
// previously processed one
|
||||
@@ -348,7 +422,13 @@ export class WorkflowExecute {
|
||||
// will then process this node next. So nothing to do
|
||||
// unless the incoming data of the node is empty
|
||||
// because then it would not be executed
|
||||
if (!this.incomingConnectionIsEmpty(this.runExecutionData.resultData.runData, workflow.connectionsByDestinationNode[inputData.node].main[0], runIndex)) {
|
||||
if (
|
||||
!this.incomingConnectionIsEmpty(
|
||||
this.runExecutionData.resultData.runData,
|
||||
workflow.connectionsByDestinationNode[inputData.node].main[0],
|
||||
runIndex,
|
||||
)
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
@@ -401,7 +481,10 @@ export class WorkflowExecute {
|
||||
nodeToAdd = parentNode;
|
||||
}
|
||||
const parentNodesNodeToAdd = workflow.getParentNodes(nodeToAdd as string);
|
||||
if (parentNodesNodeToAdd.includes(parentNodeName) && nodeSuccessData[outputIndex].length === 0) {
|
||||
if (
|
||||
parentNodesNodeToAdd.includes(parentNodeName) &&
|
||||
nodeSuccessData[outputIndex].length === 0
|
||||
) {
|
||||
// We do not add the node if there is no input data and the node that should be connected
|
||||
// is a child of the parent node. Because else it would run a node even though it should be
|
||||
// specifically not run, as it did not receive any data.
|
||||
@@ -418,30 +501,32 @@ export class WorkflowExecute {
|
||||
if (workflow.connectionsByDestinationNode[nodeToAdd] === undefined) {
|
||||
// Add empty item if the node does not have any input connections
|
||||
addEmptyItem = true;
|
||||
} else {
|
||||
if (this.incomingConnectionIsEmpty(this.runExecutionData.resultData.runData, workflow.connectionsByDestinationNode[nodeToAdd].main[0], runIndex)) {
|
||||
// Add empty item also if the input data is empty
|
||||
addEmptyItem = true;
|
||||
}
|
||||
} else if (
|
||||
this.incomingConnectionIsEmpty(
|
||||
this.runExecutionData.resultData.runData,
|
||||
workflow.connectionsByDestinationNode[nodeToAdd].main[0],
|
||||
runIndex,
|
||||
)
|
||||
) {
|
||||
// Add empty item also if the input data is empty
|
||||
addEmptyItem = true;
|
||||
}
|
||||
|
||||
if (addEmptyItem === true) {
|
||||
if (addEmptyItem) {
|
||||
// Add only node if it does not have any inputs because else it will
|
||||
// be added by its input node later anyway.
|
||||
this.runExecutionData.executionData!.nodeExecutionStack.push(
|
||||
{
|
||||
node: workflow.getNode(nodeToAdd) as INode,
|
||||
data: {
|
||||
main: [
|
||||
[
|
||||
{
|
||||
json: {},
|
||||
},
|
||||
],
|
||||
this.runExecutionData.executionData!.nodeExecutionStack.push({
|
||||
node: workflow.getNode(nodeToAdd) as INode,
|
||||
data: {
|
||||
main: [
|
||||
[
|
||||
{
|
||||
json: {},
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -461,9 +546,11 @@ export class WorkflowExecute {
|
||||
connectionDataArray[connectionData.index] = nodeSuccessData[outputIndex];
|
||||
}
|
||||
|
||||
if (stillDataMissing === true) {
|
||||
if (stillDataMissing) {
|
||||
// Additional data is needed to run node so add it to waiting
|
||||
if (!this.runExecutionData.executionData!.waitingExecution.hasOwnProperty(connectionData.node)) {
|
||||
if (
|
||||
!this.runExecutionData.executionData!.waitingExecution.hasOwnProperty(connectionData.node)
|
||||
) {
|
||||
this.runExecutionData.executionData!.waitingExecution[connectionData.node] = {};
|
||||
}
|
||||
this.runExecutionData.executionData!.waitingExecution[connectionData.node][runIndex] = {
|
||||
@@ -480,7 +567,6 @@ export class WorkflowExecute {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Runs the given execution data.
|
||||
*
|
||||
@@ -488,14 +574,17 @@ export class WorkflowExecute {
|
||||
* @returns {Promise<string>}
|
||||
* @memberof WorkflowExecute
|
||||
*/
|
||||
processRunExecutionData(workflow: Workflow): PCancelable<IRun> {
|
||||
// @ts-ignore
|
||||
async processRunExecutionData(workflow: Workflow): PCancelable<IRun> {
|
||||
Logger.verbose('Workflow execution started', { workflowId: workflow.id });
|
||||
|
||||
const startedAt = new Date();
|
||||
|
||||
const workflowIssues = workflow.checkReadyForExecution();
|
||||
if (workflowIssues !== null) {
|
||||
throw new Error('The workflow has issues and can for that reason not be executed. Please fix them first.');
|
||||
throw new Error(
|
||||
'The workflow has issues and can for that reason not be executed. Please fix them first.',
|
||||
);
|
||||
}
|
||||
|
||||
// Variables which hold temporary data for each node-execution
|
||||
@@ -521,7 +610,7 @@ export class WorkflowExecute {
|
||||
let currentExecutionTry = '';
|
||||
let lastExecutionTry = '';
|
||||
|
||||
return new PCancelable((resolve, reject, onCancel) => {
|
||||
return new PCancelable(async (resolve, reject, onCancel) => {
|
||||
let gotCancel = false;
|
||||
|
||||
onCancel.shouldReject = false;
|
||||
@@ -533,7 +622,6 @@ export class WorkflowExecute {
|
||||
try {
|
||||
await this.executeHook('workflowExecuteBefore', [workflow]);
|
||||
} catch (error) {
|
||||
|
||||
// Set the error that it can be saved correctly
|
||||
executionError = {
|
||||
...error,
|
||||
@@ -542,16 +630,17 @@ export class WorkflowExecute {
|
||||
};
|
||||
|
||||
// Set the incoming data of the node that it can be saved correctly
|
||||
executionData = this.runExecutionData.executionData!.nodeExecutionStack[0] as IExecuteData;
|
||||
// eslint-disable-next-line prefer-destructuring
|
||||
executionData = this.runExecutionData.executionData!.nodeExecutionStack[0];
|
||||
this.runExecutionData.resultData = {
|
||||
runData: {
|
||||
[executionData.node.name]: [
|
||||
{
|
||||
startTime,
|
||||
executionTime: (new Date().getTime()) - startTime,
|
||||
data: ({
|
||||
'main': executionData.data.main,
|
||||
} as ITaskDataConnections),
|
||||
executionTime: new Date().getTime() - startTime,
|
||||
data: {
|
||||
main: executionData.data.main,
|
||||
} as ITaskDataConnections,
|
||||
},
|
||||
],
|
||||
},
|
||||
@@ -562,24 +651,31 @@ export class WorkflowExecute {
|
||||
throw error;
|
||||
}
|
||||
|
||||
executionLoop:
|
||||
while (this.runExecutionData.executionData!.nodeExecutionStack.length !== 0) {
|
||||
|
||||
if (this.additionalData.executionTimeoutTimestamp !== undefined && Date.now() >= this.additionalData.executionTimeoutTimestamp) {
|
||||
executionLoop: while (
|
||||
this.runExecutionData.executionData!.nodeExecutionStack.length !== 0
|
||||
) {
|
||||
if (
|
||||
this.additionalData.executionTimeoutTimestamp !== undefined &&
|
||||
Date.now() >= this.additionalData.executionTimeoutTimestamp
|
||||
) {
|
||||
gotCancel = true;
|
||||
}
|
||||
|
||||
// @ts-ignore
|
||||
if (gotCancel === true) {
|
||||
if (gotCancel) {
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
nodeSuccessData = null;
|
||||
executionError = undefined;
|
||||
executionData = this.runExecutionData.executionData!.nodeExecutionStack.shift() as IExecuteData;
|
||||
executionData =
|
||||
this.runExecutionData.executionData!.nodeExecutionStack.shift() as IExecuteData;
|
||||
executionNode = executionData.node;
|
||||
|
||||
Logger.debug(`Start processing node "${executionNode.name}"`, { node: executionNode.name, workflowId: workflow.id });
|
||||
Logger.debug(`Start processing node "${executionNode.name}"`, {
|
||||
node: executionNode.name,
|
||||
workflowId: workflow.id,
|
||||
});
|
||||
await this.executeHook('nodeExecuteBefore', [executionNode.name]);
|
||||
|
||||
// Get the index of the current run
|
||||
@@ -594,7 +690,10 @@ export class WorkflowExecute {
|
||||
throw new Error('Did stop execution because execution seems to be in endless loop.');
|
||||
}
|
||||
|
||||
if (this.runExecutionData.startData!.runNodeFilter !== undefined && this.runExecutionData.startData!.runNodeFilter!.indexOf(executionNode.name) === -1) {
|
||||
if (
|
||||
this.runExecutionData.startData!.runNodeFilter !== undefined &&
|
||||
this.runExecutionData.startData!.runNodeFilter.indexOf(executionNode.name) === -1
|
||||
) {
|
||||
// If filter is set and node is not on filter skip it, that avoids the problem that it executes
|
||||
// leafs that are parallel to a selected destinationNode. Normally it would execute them because
|
||||
// they have the same parent and it executes all child nodes.
|
||||
@@ -608,17 +707,24 @@ export class WorkflowExecute {
|
||||
let inputConnections: IConnection[][];
|
||||
let connectionIndex: number;
|
||||
|
||||
inputConnections = workflow.connectionsByDestinationNode[executionNode.name]['main'];
|
||||
// eslint-disable-next-line prefer-const
|
||||
inputConnections = workflow.connectionsByDestinationNode[executionNode.name].main;
|
||||
|
||||
for (connectionIndex = 0; connectionIndex < inputConnections.length; connectionIndex++) {
|
||||
if (workflow.getHighestNode(executionNode.name, 'main', connectionIndex).length === 0) {
|
||||
for (
|
||||
connectionIndex = 0;
|
||||
connectionIndex < inputConnections.length;
|
||||
connectionIndex++
|
||||
) {
|
||||
if (
|
||||
workflow.getHighestNode(executionNode.name, 'main', connectionIndex).length === 0
|
||||
) {
|
||||
// If there is no valid incoming node (if all are disabled)
|
||||
// then ignore that it has inputs and simply execute it as it is without
|
||||
// any data
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!executionData.data!.hasOwnProperty('main')) {
|
||||
if (!executionData.data.hasOwnProperty('main')) {
|
||||
// ExecutionData does not even have the connection set up so can
|
||||
// not have that data, so add it again to be executed later
|
||||
this.runExecutionData.executionData!.nodeExecutionStack.push(executionData);
|
||||
@@ -629,7 +735,10 @@ export class WorkflowExecute {
|
||||
// Check if it has the data for all the inputs
|
||||
// The most nodes just have one but merge node for example has two and data
|
||||
// of both inputs has to be available to be able to process the node.
|
||||
if (executionData.data!.main!.length < connectionIndex || executionData.data!.main![connectionIndex] === null) {
|
||||
if (
|
||||
executionData.data.main!.length < connectionIndex ||
|
||||
executionData.data.main![connectionIndex] === null
|
||||
) {
|
||||
// Does not have the data of the connections so add back to stack
|
||||
this.runExecutionData.executionData!.nodeExecutionStack.push(executionData);
|
||||
lastExecutionTry = currentExecutionTry;
|
||||
@@ -653,22 +762,25 @@ export class WorkflowExecute {
|
||||
let waitBetweenTries = 0;
|
||||
if (executionData.node.retryOnFail === true) {
|
||||
// TODO: Remove the hardcoded default-values here and also in NodeSettings.vue
|
||||
waitBetweenTries = Math.min(5000, Math.max(0, executionData.node.waitBetweenTries || 1000));
|
||||
waitBetweenTries = Math.min(
|
||||
5000,
|
||||
Math.max(0, executionData.node.waitBetweenTries || 1000),
|
||||
);
|
||||
}
|
||||
|
||||
for (let tryIndex = 0; tryIndex < maxTries; tryIndex++) {
|
||||
// @ts-ignore
|
||||
if (gotCancel === true) {
|
||||
if (gotCancel) {
|
||||
return Promise.resolve();
|
||||
}
|
||||
try {
|
||||
|
||||
if (tryIndex !== 0) {
|
||||
// Reset executionError from previous error try
|
||||
executionError = undefined;
|
||||
if (waitBetweenTries !== 0) {
|
||||
// TODO: Improve that in the future and check if other nodes can
|
||||
// be executed in the meantime
|
||||
// eslint-disable-next-line @typescript-eslint/no-shadow
|
||||
await new Promise((resolve) => {
|
||||
setTimeout(() => {
|
||||
resolve(undefined);
|
||||
@@ -677,9 +789,23 @@ export class WorkflowExecute {
|
||||
}
|
||||
}
|
||||
|
||||
Logger.debug(`Running node "${executionNode.name}" started`, { node: executionNode.name, workflowId: workflow.id });
|
||||
nodeSuccessData = await workflow.runNode(executionData.node, executionData.data, this.runExecutionData, runIndex, this.additionalData, NodeExecuteFunctions, this.mode);
|
||||
Logger.debug(`Running node "${executionNode.name}" finished successfully`, { node: executionNode.name, workflowId: workflow.id });
|
||||
Logger.debug(`Running node "${executionNode.name}" started`, {
|
||||
node: executionNode.name,
|
||||
workflowId: workflow.id,
|
||||
});
|
||||
nodeSuccessData = await workflow.runNode(
|
||||
executionData.node,
|
||||
executionData.data,
|
||||
this.runExecutionData,
|
||||
runIndex,
|
||||
this.additionalData,
|
||||
NodeExecuteFunctions,
|
||||
this.mode,
|
||||
);
|
||||
Logger.debug(`Running node "${executionNode.name}" finished successfully`, {
|
||||
node: executionNode.name,
|
||||
workflowId: workflow.id,
|
||||
});
|
||||
|
||||
if (nodeSuccessData === undefined) {
|
||||
// Node did not get executed
|
||||
@@ -699,7 +825,7 @@ export class WorkflowExecute {
|
||||
}
|
||||
}
|
||||
|
||||
if (nodeSuccessData === null && !this.runExecutionData.waitTill!!) {
|
||||
if (nodeSuccessData === null && !this.runExecutionData.waitTill!) {
|
||||
// If null gets returned it means that the node did succeed
|
||||
// but did not have any data. So the branch should end
|
||||
// (meaning the nodes afterwards should not be processed)
|
||||
@@ -708,7 +834,6 @@ export class WorkflowExecute {
|
||||
|
||||
break;
|
||||
} catch (error) {
|
||||
|
||||
this.runExecutionData.resultData.lastNodeExecuted = executionData.node.name;
|
||||
|
||||
executionError = {
|
||||
@@ -717,7 +842,10 @@ export class WorkflowExecute {
|
||||
stack: error.stack,
|
||||
};
|
||||
|
||||
Logger.debug(`Running node "${executionNode.name}" finished with error`, { node: executionNode.name, workflowId: workflow.id });
|
||||
Logger.debug(`Running node "${executionNode.name}" finished with error`, {
|
||||
node: executionNode.name,
|
||||
workflowId: workflow.id,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -729,7 +857,7 @@ export class WorkflowExecute {
|
||||
}
|
||||
taskData = {
|
||||
startTime,
|
||||
executionTime: (new Date().getTime()) - startTime,
|
||||
executionTime: new Date().getTime() - startTime,
|
||||
};
|
||||
|
||||
if (executionError !== undefined) {
|
||||
@@ -741,7 +869,7 @@ export class WorkflowExecute {
|
||||
// Simply get the input data of the node if it has any and pass it through
|
||||
// to the next node
|
||||
if (executionData.data.main[0] !== null) {
|
||||
nodeSuccessData = [executionData.data.main[0] as INodeExecutionData[]];
|
||||
nodeSuccessData = [executionData.data.main[0]];
|
||||
}
|
||||
}
|
||||
} else {
|
||||
@@ -751,30 +879,46 @@ export class WorkflowExecute {
|
||||
// Add the execution data again so that it can get restarted
|
||||
this.runExecutionData.executionData!.nodeExecutionStack.unshift(executionData);
|
||||
|
||||
await this.executeHook('nodeExecuteAfter', [executionNode.name, taskData, this.runExecutionData]);
|
||||
await this.executeHook('nodeExecuteAfter', [
|
||||
executionNode.name,
|
||||
taskData,
|
||||
this.runExecutionData,
|
||||
]);
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Node executed successfully. So add data and go on.
|
||||
taskData.data = ({
|
||||
'main': nodeSuccessData,
|
||||
} as ITaskDataConnections);
|
||||
taskData.data = {
|
||||
main: nodeSuccessData,
|
||||
} as ITaskDataConnections;
|
||||
|
||||
this.runExecutionData.resultData.runData[executionNode.name].push(taskData);
|
||||
|
||||
if (this.runExecutionData.startData && this.runExecutionData.startData.destinationNode && this.runExecutionData.startData.destinationNode === executionNode.name) {
|
||||
if (
|
||||
this.runExecutionData.startData &&
|
||||
this.runExecutionData.startData.destinationNode &&
|
||||
this.runExecutionData.startData.destinationNode === executionNode.name
|
||||
) {
|
||||
// Before stopping, make sure we are executing hooks so
|
||||
// That frontend is notified for example for manual executions.
|
||||
await this.executeHook('nodeExecuteAfter', [executionNode.name, taskData, this.runExecutionData]);
|
||||
await this.executeHook('nodeExecuteAfter', [
|
||||
executionNode.name,
|
||||
taskData,
|
||||
this.runExecutionData,
|
||||
]);
|
||||
|
||||
// If destination node is defined and got executed stop execution
|
||||
continue;
|
||||
}
|
||||
|
||||
if (this.runExecutionData.waitTill!!) {
|
||||
await this.executeHook('nodeExecuteAfter', [executionNode.name, taskData, this.runExecutionData]);
|
||||
if (this.runExecutionData.waitTill!) {
|
||||
await this.executeHook('nodeExecuteAfter', [
|
||||
executionNode.name,
|
||||
taskData,
|
||||
this.runExecutionData,
|
||||
]);
|
||||
|
||||
// Add the node back to the stack that the workflow can start to execute again from that node
|
||||
this.runExecutionData.executionData!.nodeExecutionStack.unshift(executionData);
|
||||
@@ -786,24 +930,46 @@ export class WorkflowExecute {
|
||||
// be executed next
|
||||
if (workflow.connectionsBySourceNode.hasOwnProperty(executionNode.name)) {
|
||||
if (workflow.connectionsBySourceNode[executionNode.name].hasOwnProperty('main')) {
|
||||
let outputIndex: string, connectionData: IConnection;
|
||||
let outputIndex: string;
|
||||
let connectionData: IConnection;
|
||||
// Iterate over all the outputs
|
||||
|
||||
// Add the nodes to be executed
|
||||
for (outputIndex in workflow.connectionsBySourceNode[executionNode.name]['main']) {
|
||||
if (!workflow.connectionsBySourceNode[executionNode.name]['main'].hasOwnProperty(outputIndex)) {
|
||||
// eslint-disable-next-line @typescript-eslint/no-for-in-array
|
||||
for (outputIndex in workflow.connectionsBySourceNode[executionNode.name].main) {
|
||||
if (
|
||||
!workflow.connectionsBySourceNode[executionNode.name].main.hasOwnProperty(
|
||||
outputIndex,
|
||||
)
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Iterate over all the different connections of this output
|
||||
for (connectionData of workflow.connectionsBySourceNode[executionNode.name]['main'][outputIndex]) {
|
||||
for (connectionData of workflow.connectionsBySourceNode[executionNode.name].main[
|
||||
outputIndex
|
||||
]) {
|
||||
if (!workflow.nodes.hasOwnProperty(connectionData.node)) {
|
||||
return Promise.reject(new Error(`The node "${executionNode.name}" connects to not found node "${connectionData.node}"`));
|
||||
return Promise.reject(
|
||||
new Error(
|
||||
`The node "${executionNode.name}" connects to not found node "${connectionData.node}"`,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
if (nodeSuccessData![outputIndex] && (nodeSuccessData![outputIndex].length !== 0 || connectionData.index > 0)) {
|
||||
if (
|
||||
nodeSuccessData![outputIndex] &&
|
||||
(nodeSuccessData![outputIndex].length !== 0 || connectionData.index > 0)
|
||||
) {
|
||||
// Add the node only if it did execute or if connected to second "optional" input
|
||||
this.addNodeToBeExecuted(workflow, connectionData, parseInt(outputIndex, 10), executionNode.name, nodeSuccessData!, runIndex);
|
||||
this.addNodeToBeExecuted(
|
||||
workflow,
|
||||
connectionData,
|
||||
parseInt(outputIndex, 10),
|
||||
executionNode.name,
|
||||
nodeSuccessData!,
|
||||
runIndex,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -814,58 +980,79 @@ export class WorkflowExecute {
|
||||
// Execute hooks now to make sure that all hooks are executed properly
|
||||
// Await is needed to make sure that we don't fall into concurrency problems
|
||||
// When saving node execution data
|
||||
await this.executeHook('nodeExecuteAfter', [executionNode.name, taskData, this.runExecutionData]);
|
||||
|
||||
await this.executeHook('nodeExecuteAfter', [
|
||||
executionNode.name,
|
||||
taskData,
|
||||
this.runExecutionData,
|
||||
]);
|
||||
}
|
||||
|
||||
return Promise.resolve();
|
||||
})()
|
||||
.then(async () => {
|
||||
if (gotCancel && executionError === undefined) {
|
||||
return this.processSuccessExecution(startedAt, workflow, new WorkflowOperationError('Workflow has been canceled or timed out!'));
|
||||
}
|
||||
return this.processSuccessExecution(startedAt, workflow, executionError);
|
||||
})
|
||||
.catch(async (error) => {
|
||||
const fullRunData = this.getFullRunData(startedAt);
|
||||
.then(async () => {
|
||||
if (gotCancel && executionError === undefined) {
|
||||
return this.processSuccessExecution(
|
||||
startedAt,
|
||||
workflow,
|
||||
new WorkflowOperationError('Workflow has been canceled or timed out!'),
|
||||
);
|
||||
}
|
||||
return this.processSuccessExecution(startedAt, workflow, executionError);
|
||||
})
|
||||
.catch(async (error) => {
|
||||
const fullRunData = this.getFullRunData(startedAt);
|
||||
|
||||
fullRunData.data.resultData.error = {
|
||||
...error,
|
||||
message: error.message,
|
||||
stack: error.stack,
|
||||
};
|
||||
fullRunData.data.resultData.error = {
|
||||
...error,
|
||||
message: error.message,
|
||||
stack: error.stack,
|
||||
};
|
||||
|
||||
// Check if static data changed
|
||||
let newStaticData: IDataObject | undefined;
|
||||
if (workflow.staticData.__dataChanged === true) {
|
||||
// Static data of workflow changed
|
||||
newStaticData = workflow.staticData;
|
||||
}
|
||||
await this.executeHook('workflowExecuteAfter', [fullRunData, newStaticData]).catch(error => {
|
||||
console.error('There was a problem running hook "workflowExecuteAfter"', error);
|
||||
// Check if static data changed
|
||||
let newStaticData: IDataObject | undefined;
|
||||
// eslint-disable-next-line no-underscore-dangle
|
||||
if (workflow.staticData.__dataChanged === true) {
|
||||
// Static data of workflow changed
|
||||
newStaticData = workflow.staticData;
|
||||
}
|
||||
await this.executeHook('workflowExecuteAfter', [fullRunData, newStaticData]).catch(
|
||||
// eslint-disable-next-line @typescript-eslint/no-shadow
|
||||
(error) => {
|
||||
// eslint-disable-next-line no-console
|
||||
console.error('There was a problem running hook "workflowExecuteAfter"', error);
|
||||
},
|
||||
);
|
||||
|
||||
return fullRunData;
|
||||
});
|
||||
|
||||
return fullRunData;
|
||||
});
|
||||
|
||||
return returnPromise.then(resolve);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
// @ts-ignore
|
||||
async processSuccessExecution(startedAt: Date, workflow: Workflow, executionError?: ExecutionError): PCancelable<IRun> {
|
||||
async processSuccessExecution(
|
||||
startedAt: Date,
|
||||
workflow: Workflow,
|
||||
executionError?: ExecutionError,
|
||||
// @ts-ignore
|
||||
): PCancelable<IRun> {
|
||||
const fullRunData = this.getFullRunData(startedAt);
|
||||
|
||||
if (executionError !== undefined) {
|
||||
Logger.verbose(`Workflow execution finished with error`, { error: executionError, workflowId: workflow.id });
|
||||
Logger.verbose(`Workflow execution finished with error`, {
|
||||
error: executionError,
|
||||
workflowId: workflow.id,
|
||||
});
|
||||
fullRunData.data.resultData.error = {
|
||||
...executionError,
|
||||
message: executionError.message,
|
||||
stack: executionError.stack,
|
||||
} as ExecutionError;
|
||||
} else if (this.runExecutionData.waitTill!!) {
|
||||
Logger.verbose(`Workflow execution will wait until ${this.runExecutionData.waitTill}`, { workflowId: workflow.id });
|
||||
} else if (this.runExecutionData.waitTill!) {
|
||||
// eslint-disable-next-line @typescript-eslint/restrict-template-expressions
|
||||
Logger.verbose(`Workflow execution will wait until ${this.runExecutionData.waitTill}`, {
|
||||
workflowId: workflow.id,
|
||||
});
|
||||
fullRunData.waitTill = this.runExecutionData.waitTill;
|
||||
} else {
|
||||
Logger.verbose(`Workflow execution finished successfully`, { workflowId: workflow.id });
|
||||
@@ -874,6 +1061,7 @@ export class WorkflowExecute {
|
||||
|
||||
// Check if static data changed
|
||||
let newStaticData: IDataObject | undefined;
|
||||
// eslint-disable-next-line no-underscore-dangle
|
||||
if (workflow.staticData.__dataChanged === true) {
|
||||
// Static data of workflow changed
|
||||
newStaticData = workflow.staticData;
|
||||
@@ -894,5 +1082,4 @@ export class WorkflowExecute {
|
||||
|
||||
return fullRunData;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,8 +1,12 @@
|
||||
try {
|
||||
require('source-map-support').install();
|
||||
} catch (error) {
|
||||
/* eslint-disable import/no-cycle */
|
||||
import * as NodeExecuteFunctions from './NodeExecuteFunctions';
|
||||
import * as UserSettings from './UserSettings';
|
||||
|
||||
}
|
||||
try {
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-unsafe-call, import/no-extraneous-dependencies, global-require, @typescript-eslint/no-var-requires
|
||||
require('source-map-support').install();
|
||||
// eslint-disable-next-line no-empty
|
||||
} catch (error) {}
|
||||
|
||||
export * from './ActiveWorkflows';
|
||||
export * from './ActiveWebhooks';
|
||||
@@ -13,10 +17,4 @@ export * from './Interfaces';
|
||||
export * from './LoadNodeParameterOptions';
|
||||
export * from './NodeExecuteFunctions';
|
||||
export * from './WorkflowExecute';
|
||||
|
||||
import * as NodeExecuteFunctions from './NodeExecuteFunctions';
|
||||
import * as UserSettings from './UserSettings';
|
||||
export {
|
||||
NodeExecuteFunctions,
|
||||
UserSettings,
|
||||
};
|
||||
export { NodeExecuteFunctions, UserSettings };
|
||||
|
||||
Reference in New Issue
Block a user