🎨 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:
Iván Ovejero
2021-08-29 20:58:11 +02:00
committed by GitHub
parent 223cd75685
commit 56c4c6991f
108 changed files with 11832 additions and 8416 deletions

View File

@@ -1,14 +1,14 @@
interface IResult {
totalWorkflows: number;
summary: {
failedExecutions: number,
successfulExecutions: number,
warningExecutions: number,
errors: IExecutionError[],
warnings: IExecutionError[],
failedExecutions: number;
successfulExecutions: number;
warningExecutions: number;
errors: IExecutionError[];
warnings: IExecutionError[];
};
coveredNodes: {
[nodeType: string]: number
[nodeType: string]: number;
};
executions: IExecutionResult[];
}
@@ -21,7 +21,7 @@ interface IExecutionResult {
error?: string;
changes?: string;
coveredNodes: {
[nodeType: string]: number
[nodeType: string]: number;
};
}

View File

@@ -1,11 +1,9 @@
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
/* eslint-disable no-console */
import { promises as fs } from 'fs';
import { Command, flags } from '@oclif/command';
import {
UserSettings,
} from 'n8n-core';
import {
INode,
} from 'n8n-workflow';
import { UserSettings } from 'n8n-core';
import { INode, LoggerProxy } from 'n8n-workflow';
import {
ActiveExecutions,
@@ -17,26 +15,18 @@ import {
IWorkflowExecutionDataProcess,
LoadNodesAndCredentials,
NodeTypes,
// eslint-disable-next-line @typescript-eslint/no-unused-vars
WorkflowCredentials,
WorkflowHelpers,
WorkflowRunner,
} from '../src';
import {
getLogger,
} from '../src/Logger';
import {
LoggerProxy,
} from 'n8n-workflow';
import { getLogger } from '../src/Logger';
export class Execute extends Command {
static description = '\nExecutes a given workflow';
static examples = [
`$ n8n execute --id=5`,
`$ n8n execute --file=workflow.json`,
];
static examples = [`$ n8n execute --id=5`, `$ n8n execute --file=workflow.json`];
static flags = {
help: flags.help({ char: 'h' }),
@@ -51,11 +41,12 @@ export class Execute extends Command {
}),
};
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
async run() {
const logger = getLogger();
LoggerProxy.init(logger);
// eslint-disable-next-line @typescript-eslint/no-shadow
const { flags } = this.parse(Execute);
// Start directly with the init of the database to improve startup time
@@ -76,12 +67,14 @@ export class Execute extends Command {
}
let workflowId: string | undefined;
let workflowData: IWorkflowBase | undefined = undefined;
let workflowData: IWorkflowBase | undefined;
if (flags.file) {
// Path to workflow is given
try {
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
workflowData = JSON.parse(await fs.readFile(flags.file, 'utf8'));
} catch (error) {
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
if (error.code === 'ENOENT') {
console.info(`The file "${flags.file}" could not be found.`);
return;
@@ -92,10 +85,15 @@ export class Execute extends Command {
// Do a basic check if the data in the file looks right
// TODO: Later check with the help of TypeScript data if it is valid or not
if (workflowData === undefined || workflowData.nodes === undefined || workflowData.connections === undefined) {
if (
workflowData === undefined ||
workflowData.nodes === undefined ||
workflowData.connections === undefined
) {
console.info(`The file "${flags.file}" does not contain valid workflow data.`);
return;
}
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
workflowId = workflowData.id!.toString();
}
@@ -105,7 +103,8 @@ export class Execute extends Command {
if (flags.id) {
// Id of workflow is given
workflowId = flags.id;
workflowData = await Db.collections!.Workflow!.findOne(workflowId);
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
workflowData = await Db.collections.Workflow!.findOne(workflowId);
if (workflowData === undefined) {
console.info(`The workflow with the id "${workflowId}" does not exist.`);
process.exit(1);
@@ -139,7 +138,8 @@ export class Execute extends Command {
// Check if the workflow contains the required "Start" node
// "requiredNodeTypes" are also defined in editor-ui/views/NodeView.vue
const requiredNodeTypes = ['n8n-nodes-base.start'];
let startNode: INode | undefined = undefined;
let startNode: INode | undefined;
// eslint-disable-next-line no-restricted-syntax, @typescript-eslint/no-non-null-assertion
for (const node of workflowData!.nodes) {
if (requiredNodeTypes.includes(node.type)) {
startNode = node;
@@ -151,6 +151,7 @@ export class Execute extends Command {
// If the workflow does not contain a start-node we can not know what
// should be executed and with which data to start.
console.info(`The workflow does not contain a "Start" node. So it can not be executed.`);
// eslint-disable-next-line consistent-return
return Promise.resolve();
}
@@ -158,6 +159,7 @@ export class Execute extends Command {
const runData: IWorkflowExecutionDataProcess = {
executionMode: 'cli',
startNodes: [startNode.name],
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
workflowData: workflowData!,
};
@@ -178,6 +180,7 @@ export class Execute extends Command {
logger.info(JSON.stringify(data, null, 2));
const { error } = data.data.resultData;
// eslint-disable-next-line @typescript-eslint/no-throw-literal
throw {
...error,
stack: error.stack,

View File

@@ -1,18 +1,26 @@
/* eslint-disable @typescript-eslint/prefer-optional-chain */
/* eslint-disable array-callback-return */
/* eslint-disable @typescript-eslint/no-non-null-assertion */
/* eslint-disable no-await-in-loop */
/* eslint-disable no-async-promise-executor */
/* eslint-disable no-param-reassign */
/* eslint-disable @typescript-eslint/unbound-method */
/* eslint-disable no-console */
import * as fs from 'fs';
import {
Command,
flags,
} from '@oclif/command';
import { Command, flags } from '@oclif/command';
import {
UserSettings,
} from 'n8n-core';
import { UserSettings } from 'n8n-core';
import {
INode,
INodeExecutionData,
ITaskData,
} from 'n8n-workflow';
// eslint-disable-next-line @typescript-eslint/no-unused-vars
import { INode, INodeExecutionData, ITaskData, LoggerProxy } from 'n8n-workflow';
import { sep } from 'path';
import { diff } from 'json-diff';
// eslint-disable-next-line import/no-extraneous-dependencies
import { pick } from 'lodash';
import { getLogger } from '../src/Logger';
import {
ActiveExecutions,
@@ -20,35 +28,17 @@ import {
CredentialTypes,
Db,
ExternalHooks,
// eslint-disable-next-line @typescript-eslint/no-unused-vars
IExecutionsCurrentSummary,
IWorkflowDb,
IWorkflowExecutionDataProcess,
LoadNodesAndCredentials,
NodeTypes,
// eslint-disable-next-line @typescript-eslint/no-unused-vars
WorkflowCredentials,
WorkflowRunner,
} from '../src';
import {
sep,
} from 'path';
import {
diff,
} from 'json-diff';
import {
getLogger,
} from '../src/Logger';
import {
LoggerProxy,
} from 'n8n-workflow';
import {
pick,
} from 'lodash';
export class ExecuteBatch extends Command {
static description = '\nExecutes multiple workflows once';
@@ -87,19 +77,24 @@ export class ExecuteBatch extends Command {
}),
concurrency: flags.integer({
default: 1,
description: 'How many workflows can run in parallel. Defaults to 1 which means no concurrency.',
description:
'How many workflows can run in parallel. Defaults to 1 which means no concurrency.',
}),
output: flags.string({
description: 'Enable execution saving, You must inform an existing folder to save execution via this param',
description:
'Enable execution saving, You must inform an existing folder to save execution via this param',
}),
snapshot: flags.string({
description: 'Enables snapshot saving. You must inform an existing folder to save snapshots via this param.',
description:
'Enables snapshot saving. You must inform an existing folder to save snapshots via this param.',
}),
compare: flags.string({
description: 'Compares current execution with an existing snapshot. You must inform an existing folder where the snapshots are saved.',
description:
'Compares current execution with an existing snapshot. You must inform an existing folder where the snapshots are saved.',
}),
shallow: flags.boolean({
description: 'Compares only if attributes output from node are the same, with no regards to neste JSON objects.',
description:
'Compares only if attributes output from node are the same, with no regards to neste JSON objects.',
}),
skipList: flags.string({
description: 'File containing a comma separated list of workflow IDs to skip.',
@@ -117,15 +112,16 @@ export class ExecuteBatch extends Command {
* Gracefully handles exit.
* @param {boolean} skipExit Whether to skip exit or number according to received signal
*/
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
static async stopProcess(skipExit: boolean | number = false) {
if (ExecuteBatch.cancelled === true) {
if (ExecuteBatch.cancelled) {
process.exit(0);
}
ExecuteBatch.cancelled = true;
const activeExecutionsInstance = ActiveExecutions.getInstance();
const stopPromises = activeExecutionsInstance.getActiveExecutions().map(async execution => {
const stopPromises = activeExecutionsInstance.getActiveExecutions().map(async (execution) => {
// eslint-disable-next-line @typescript-eslint/no-floating-promises
activeExecutionsInstance.stopExecution(execution.id);
});
@@ -135,16 +131,17 @@ export class ExecuteBatch extends Command {
process.exit(0);
}, 30000);
let executingWorkflows = activeExecutionsInstance.getActiveExecutions() as IExecutionsCurrentSummary[];
let executingWorkflows = activeExecutionsInstance.getActiveExecutions();
let count = 0;
while (executingWorkflows.length !== 0) {
if (count++ % 4 === 0) {
console.log(`Waiting for ${executingWorkflows.length} active executions to finish...`);
executingWorkflows.map(execution => {
executingWorkflows.map((execution) => {
console.log(` - Execution ID ${execution.id}, workflow ID: ${execution.workflowId}`);
});
}
// eslint-disable-next-line no-await-in-loop
await new Promise((resolve) => {
setTimeout(resolve, 500);
});
@@ -157,12 +154,13 @@ export class ExecuteBatch extends Command {
}
}
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
formatJsonOutput(data: object) {
return JSON.stringify(data, null, 2);
}
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
shouldBeConsideredAsWarning(errorMessage: string) {
const warningStrings = [
'refresh token is invalid',
'unable to connect to',
@@ -174,6 +172,7 @@ export class ExecuteBatch extends Command {
'request timed out',
];
// eslint-disable-next-line no-param-reassign
errorMessage = errorMessage.toLowerCase();
for (let i = 0; i < warningStrings.length; i++) {
@@ -185,18 +184,18 @@ export class ExecuteBatch extends Command {
return false;
}
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
async run() {
process.on('SIGTERM', ExecuteBatch.stopProcess);
process.on('SIGINT', ExecuteBatch.stopProcess);
const logger = getLogger();
LoggerProxy.init(logger);
// eslint-disable-next-line @typescript-eslint/no-shadow
const { flags } = this.parse(ExecuteBatch);
ExecuteBatch.debug = flags.debug === true;
ExecuteBatch.debug = flags.debug;
ExecuteBatch.concurrency = flags.concurrency || 1;
const ids: number[] = [];
@@ -241,7 +240,7 @@ export class ExecuteBatch extends Command {
if (flags.ids !== undefined) {
const paramIds = flags.ids.split(',');
const re = /\d+/;
const matchedIds = paramIds.filter(id => id.match(re)).map(id => parseInt(id.trim(), 10));
const matchedIds = paramIds.filter((id) => re.exec(id)).map((id) => parseInt(id.trim(), 10));
if (matchedIds.length === 0) {
console.log(`The parameter --ids must be a list of numeric IDs separated by a comma.`);
@@ -254,18 +253,17 @@ export class ExecuteBatch extends Command {
if (flags.skipList !== undefined) {
if (fs.existsSync(flags.skipList)) {
const contents = fs.readFileSync(flags.skipList, { encoding: 'utf-8' });
skipIds.push(...contents.split(',').map(id => parseInt(id.trim(), 10)));
skipIds.push(...contents.split(',').map((id) => parseInt(id.trim(), 10)));
} else {
console.log('Skip list file not found. Exiting.');
return;
}
}
if (flags.shallow === true) {
if (flags.shallow) {
ExecuteBatch.shallow = true;
}
// Start directly with the init of the database to improve startup time
const startDbInitPromise = Db.init();
@@ -281,7 +279,7 @@ export class ExecuteBatch extends Command {
let allWorkflows;
const query = Db.collections!.Workflow!.createQueryBuilder('workflows');
const query = Db.collections.Workflow!.createQueryBuilder('workflows');
if (ids.length > 0) {
query.andWhere(`workflows.id in (:...ids)`, { ids });
@@ -291,9 +289,10 @@ export class ExecuteBatch extends Command {
query.andWhere(`workflows.id not in (:...skipIds)`, { skipIds });
}
allWorkflows = await query.getMany() as IWorkflowDb[];
// eslint-disable-next-line prefer-const
allWorkflows = (await query.getMany()) as IWorkflowDb[];
if (ExecuteBatch.debug === true) {
if (ExecuteBatch.debug) {
process.stdout.write(`Found ${allWorkflows.length} workflows to execute.\n`);
}
@@ -318,12 +317,19 @@ export class ExecuteBatch extends Command {
let { retries } = flags;
while (retries > 0 && (results.summary.warningExecutions + results.summary.failedExecutions > 0) && ExecuteBatch.cancelled === false) {
const failedWorkflowIds = results.summary.errors.map(execution => execution.workflowId);
failedWorkflowIds.push(...results.summary.warnings.map(execution => execution.workflowId));
while (
retries > 0 &&
results.summary.warningExecutions + results.summary.failedExecutions > 0 &&
!ExecuteBatch.cancelled
) {
const failedWorkflowIds = results.summary.errors.map((execution) => execution.workflowId);
failedWorkflowIds.push(...results.summary.warnings.map((execution) => execution.workflowId));
const newWorkflowList = allWorkflows.filter(workflow => failedWorkflowIds.includes(workflow.id));
const newWorkflowList = allWorkflows.filter((workflow) =>
failedWorkflowIds.includes(workflow.id),
);
// eslint-disable-next-line no-await-in-loop
const retryResults = await this.runTests(newWorkflowList);
this.mergeResults(results, retryResults);
@@ -343,12 +349,17 @@ export class ExecuteBatch extends Command {
console.log(`\t${nodeName}: ${nodeCount}`);
});
console.log('\nCheck the JSON file for more details.');
} else if (flags.shortOutput) {
console.log(
this.formatJsonOutput({
...results,
executions: results.executions.filter(
(execution) => execution.executionStatus !== 'success',
),
}),
);
} else {
if (flags.shortOutput === true) {
console.log(this.formatJsonOutput({ ...results, executions: results.executions.filter(execution => execution.executionStatus !== 'success') }));
} else {
console.log(this.formatJsonOutput(results));
}
console.log(this.formatJsonOutput(results));
}
await ExecuteBatch.stopProcess(true);
@@ -357,23 +368,26 @@ export class ExecuteBatch extends Command {
this.exit(1);
}
this.exit(0);
}
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
mergeResults(results: IResult, retryResults: IResult) {
if (retryResults.summary.successfulExecutions === 0) {
// Nothing to replace.
return;
}
// Find successful executions and replace them on previous result.
retryResults.executions.forEach(newExecution => {
retryResults.executions.forEach((newExecution) => {
if (newExecution.executionStatus === 'success') {
// Remove previous execution from list.
results.executions = results.executions.filter(previousExecutions => previousExecutions.workflowId !== newExecution.workflowId);
results.executions = results.executions.filter(
(previousExecutions) => previousExecutions.workflowId !== newExecution.workflowId,
);
const errorIndex = results.summary.errors.findIndex(summaryInformation => summaryInformation.workflowId === newExecution.workflowId);
const errorIndex = results.summary.errors.findIndex(
(summaryInformation) => summaryInformation.workflowId === newExecution.workflowId,
);
if (errorIndex !== -1) {
// This workflow errored previously. Decrement error count.
results.summary.failedExecutions--;
@@ -381,7 +395,9 @@ export class ExecuteBatch extends Command {
results.summary.errors.splice(errorIndex, 1);
}
const warningIndex = results.summary.warnings.findIndex(summaryInformation => summaryInformation.workflowId === newExecution.workflowId);
const warningIndex = results.summary.warnings.findIndex(
(summaryInformation) => summaryInformation.workflowId === newExecution.workflowId,
);
if (warningIndex !== -1) {
// This workflow errored previously. Decrement error count.
results.summary.warningExecutions--;
@@ -420,7 +436,7 @@ export class ExecuteBatch extends Command {
let workflow: IWorkflowDb | undefined;
while (allWorkflows.length > 0) {
workflow = allWorkflows.shift();
if (ExecuteBatch.cancelled === true) {
if (ExecuteBatch.cancelled) {
process.stdout.write(`Thread ${i + 1} resolving and quitting.`);
resolve(true);
break;
@@ -440,6 +456,7 @@ export class ExecuteBatch extends Command {
this.updateStatus();
}
// eslint-disable-next-line @typescript-eslint/no-loop-func
await this.startThread(workflow).then((executionResult) => {
if (ExecuteBatch.debug) {
ExecuteBatch.workflowExecutionsProgress[i].pop();
@@ -456,7 +473,7 @@ export class ExecuteBatch extends Command {
result.summary.successfulExecutions++;
const nodeNames = Object.keys(executionResult.coveredNodes);
nodeNames.map(nodeName => {
nodeNames.map((nodeName) => {
if (result.coveredNodes[nodeName] === undefined) {
result.coveredNodes[nodeName] = 0;
}
@@ -506,19 +523,18 @@ export class ExecuteBatch extends Command {
});
}
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
updateStatus() {
if (ExecuteBatch.cancelled === true) {
if (ExecuteBatch.cancelled) {
return;
}
if (process.stdout.isTTY === true) {
process.stdout.moveCursor(0, - (ExecuteBatch.concurrency));
if (process.stdout.isTTY) {
process.stdout.moveCursor(0, -ExecuteBatch.concurrency);
process.stdout.cursorTo(0);
process.stdout.clearLine(0);
}
ExecuteBatch.workflowExecutionsProgress.map((concurrentThread, index) => {
let message = `${index + 1}: `;
concurrentThread.map((executionItem, workflowIndex) => {
@@ -537,16 +553,19 @@ export class ExecuteBatch extends Command {
default:
break;
}
message += (workflowIndex > 0 ? ', ' : '') + `${openColor}${executionItem.workflowId}${closeColor}`;
message += `${workflowIndex > 0 ? ', ' : ''}${openColor}${
executionItem.workflowId
}${closeColor}`;
});
if (process.stdout.isTTY === true) {
if (process.stdout.isTTY) {
process.stdout.cursorTo(0);
process.stdout.clearLine(0);
}
process.stdout.write(message + '\n');
process.stdout.write(`${message}\n`);
});
}
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
initializeLogs() {
process.stdout.write('**********************************************\n');
process.stdout.write(' n8n test workflows\n');
@@ -560,7 +579,7 @@ export class ExecuteBatch extends Command {
}
}
startThread(workflowData: IWorkflowDb): Promise<IExecutionResult> {
async startThread(workflowData: IWorkflowDb): Promise<IExecutionResult> {
// This will be the object returned by the promise.
// It will be updated according to execution progress below.
const executionResult: IExecutionResult = {
@@ -572,10 +591,9 @@ export class ExecuteBatch extends Command {
coveredNodes: {},
};
const requiredNodeTypes = ['n8n-nodes-base.start'];
let startNode: INode | undefined = undefined;
let startNode: INode | undefined;
// eslint-disable-next-line no-restricted-syntax
for (const node of workflowData.nodes) {
if (requiredNodeTypes.includes(node.type)) {
startNode = node;
@@ -593,10 +611,10 @@ export class ExecuteBatch extends Command {
// properties from the JSON object (useful for optional properties that can
// cause the comparison to detect changes when not true).
const nodeEdgeCases = {} as INodeSpecialCases;
workflowData.nodes.forEach(node => {
workflowData.nodes.forEach((node) => {
executionResult.coveredNodes[node.type] = (executionResult.coveredNodes[node.type] || 0) + 1;
if (node.notes !== undefined && node.notes !== '') {
node.notes.split('\n').forEach(note => {
node.notes.split('\n').forEach((note) => {
const parts = note.split('=');
if (parts.length === 2) {
if (nodeEdgeCases[node.name] === undefined) {
@@ -605,9 +623,13 @@ export class ExecuteBatch extends Command {
if (parts[0] === 'CAP_RESULTS_LENGTH') {
nodeEdgeCases[node.name].capResults = parseInt(parts[1], 10);
} else if (parts[0] === 'IGNORED_PROPERTIES') {
nodeEdgeCases[node.name].ignoredProperties = parts[1].split(',').map(property => property.trim());
nodeEdgeCases[node.name].ignoredProperties = parts[1]
.split(',')
.map((property) => property.trim());
} else if (parts[0] === 'KEEP_ONLY_PROPERTIES') {
nodeEdgeCases[node.name].keepOnlyProperties = parts[1].split(',').map(property => property.trim());
nodeEdgeCases[node.name].keepOnlyProperties = parts[1]
.split(',')
.map((property) => property.trim());
}
}
});
@@ -633,13 +655,11 @@ export class ExecuteBatch extends Command {
resolve(executionResult);
}, ExecuteBatch.executionTimeout);
try {
const runData: IWorkflowExecutionDataProcess = {
executionMode: 'cli',
startNodes: [startNode!.name],
workflowData: workflowData!,
workflowData,
};
const workflowRunner = new WorkflowRunner();
@@ -647,7 +667,7 @@ export class ExecuteBatch extends Command {
const activeExecutions = ActiveExecutions.getInstance();
const data = await activeExecutions.getPostExecutePromise(executionId);
if (gotCancel || ExecuteBatch.cancelled === true) {
if (gotCancel || ExecuteBatch.cancelled) {
clearTimeout(timeoutTimer);
// The promise was settled already so we simply ignore.
return;
@@ -657,14 +677,18 @@ export class ExecuteBatch extends Command {
executionResult.error = 'Workflow did not return any data.';
executionResult.executionStatus = 'error';
} else {
executionResult.executionTime = (Date.parse(data.stoppedAt as unknown as string) - Date.parse(data.startedAt as unknown as string)) / 1000;
executionResult.finished = (data?.finished !== undefined) as boolean;
executionResult.executionTime =
(Date.parse(data.stoppedAt as unknown as string) -
Date.parse(data.startedAt as unknown as string)) /
1000;
executionResult.finished = data?.finished !== undefined;
if (data.data.resultData.error) {
executionResult.error =
data.data.resultData.error.hasOwnProperty('description') ?
// @ts-ignore
data.data.resultData.error.description : data.data.resultData.error.message;
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, no-prototype-builtins
executionResult.error = data.data.resultData.error.hasOwnProperty('description')
? // @ts-ignore
data.data.resultData.error.description
: data.data.resultData.error.message;
if (data.data.resultData.lastNodeExecuted !== undefined) {
executionResult.error += ` on node ${data.data.resultData.lastNodeExecuted}`;
}
@@ -674,7 +698,7 @@ export class ExecuteBatch extends Command {
executionResult.executionStatus = 'warning';
}
} else {
if (ExecuteBatch.shallow === true) {
if (ExecuteBatch.shallow) {
// What this does is guarantee that top-level attributes
// from the JSON are kept and the are the same type.
@@ -688,34 +712,48 @@ export class ExecuteBatch extends Command {
if (taskData.data === undefined) {
return;
}
Object.keys(taskData.data).map(connectionName => {
const connection = taskData.data![connectionName] as Array<INodeExecutionData[] | null>;
connection.map(executionDataArray => {
Object.keys(taskData.data).map((connectionName) => {
const connection = taskData.data![connectionName];
connection.map((executionDataArray) => {
if (executionDataArray === null) {
return;
}
if (nodeEdgeCases[nodeName] !== undefined && nodeEdgeCases[nodeName].capResults !== undefined) {
if (
nodeEdgeCases[nodeName] !== undefined &&
nodeEdgeCases[nodeName].capResults !== undefined
) {
executionDataArray.splice(nodeEdgeCases[nodeName].capResults!);
}
executionDataArray.map(executionData => {
executionDataArray.map((executionData) => {
if (executionData.json === undefined) {
return;
}
if (nodeEdgeCases[nodeName] !== undefined && nodeEdgeCases[nodeName].ignoredProperties !== undefined) {
nodeEdgeCases[nodeName].ignoredProperties!.forEach(ignoredProperty => delete executionData.json[ignoredProperty]);
if (
nodeEdgeCases[nodeName] !== undefined &&
nodeEdgeCases[nodeName].ignoredProperties !== undefined
) {
nodeEdgeCases[nodeName].ignoredProperties!.forEach(
(ignoredProperty) => delete executionData.json[ignoredProperty],
);
}
let keepOnlyFields = [] as string[];
if (nodeEdgeCases[nodeName] !== undefined && nodeEdgeCases[nodeName].keepOnlyProperties !== undefined) {
if (
nodeEdgeCases[nodeName] !== undefined &&
nodeEdgeCases[nodeName].keepOnlyProperties !== undefined
) {
keepOnlyFields = nodeEdgeCases[nodeName].keepOnlyProperties!;
}
executionData.json = keepOnlyFields.length > 0 ? pick(executionData.json, keepOnlyFields) : executionData.json;
executionData.json =
keepOnlyFields.length > 0
? pick(executionData.json, keepOnlyFields)
: executionData.json;
const jsonProperties = executionData.json;
const nodeOutputAttributes = Object.keys(jsonProperties);
nodeOutputAttributes.map(attributeName => {
nodeOutputAttributes.map((attributeName) => {
if (Array.isArray(jsonProperties[attributeName])) {
jsonProperties[attributeName] = ['json array'];
} else if (typeof jsonProperties[attributeName] === 'object') {
@@ -724,7 +762,6 @@ export class ExecuteBatch extends Command {
});
});
});
});
});
});
@@ -732,14 +769,14 @@ export class ExecuteBatch extends Command {
// If not using shallow comparison then we only treat nodeEdgeCases.
const specialCases = Object.keys(nodeEdgeCases);
specialCases.forEach(nodeName => {
specialCases.forEach((nodeName) => {
data.data.resultData.runData[nodeName].map((taskData: ITaskData) => {
if (taskData.data === undefined) {
return;
}
Object.keys(taskData.data).map(connectionName => {
const connection = taskData.data![connectionName] as Array<INodeExecutionData[] | null>;
connection.map(executionDataArray => {
Object.keys(taskData.data).map((connectionName) => {
const connection = taskData.data![connectionName];
connection.map((executionDataArray) => {
if (executionDataArray === null) {
return;
}
@@ -749,15 +786,16 @@ export class ExecuteBatch extends Command {
}
if (nodeEdgeCases[nodeName].ignoredProperties !== undefined) {
executionDataArray.map(executionData => {
executionDataArray.map((executionData) => {
if (executionData.json === undefined) {
return;
}
nodeEdgeCases[nodeName].ignoredProperties!.forEach(ignoredProperty => delete executionData.json[ignoredProperty]);
nodeEdgeCases[nodeName].ignoredProperties!.forEach(
(ignoredProperty) => delete executionData.json[ignoredProperty],
);
});
}
});
});
});
});
@@ -767,9 +805,12 @@ export class ExecuteBatch extends Command {
if (ExecuteBatch.compare === undefined) {
executionResult.executionStatus = 'success';
} else {
const fileName = (ExecuteBatch.compare.endsWith(sep) ? ExecuteBatch.compare : ExecuteBatch.compare + sep) + `${workflowData.id}-snapshot.json`;
if (fs.existsSync(fileName) === true) {
const fileName = `${
ExecuteBatch.compare.endsWith(sep)
? ExecuteBatch.compare
: ExecuteBatch.compare + sep
}${workflowData.id}-snapshot.json`;
if (fs.existsSync(fileName)) {
const contents = fs.readFileSync(fileName, { encoding: 'utf-8' });
const changes = diff(JSON.parse(contents), data, { keysOnly: true });
@@ -790,7 +831,11 @@ export class ExecuteBatch extends Command {
// Save snapshots only after comparing - this is to make sure we're updating
// After comparing to existing verion.
if (ExecuteBatch.snapshot !== undefined) {
const fileName = (ExecuteBatch.snapshot.endsWith(sep) ? ExecuteBatch.snapshot : ExecuteBatch.snapshot + sep) + `${workflowData.id}-snapshot.json`;
const fileName = `${
ExecuteBatch.snapshot.endsWith(sep)
? ExecuteBatch.snapshot
: ExecuteBatch.snapshot + sep
}${workflowData.id}-snapshot.json`;
fs.writeFileSync(fileName, serializedData);
}
}
@@ -803,5 +848,4 @@ export class ExecuteBatch extends Command {
resolve(executionResult);
});
}
}

View File

@@ -1,32 +1,16 @@
import {
Command,
flags,
} from '@oclif/command';
/* eslint-disable @typescript-eslint/restrict-plus-operands */
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
/* eslint-disable no-console */
import { Command, flags } from '@oclif/command';
import {
Credentials,
UserSettings,
} from 'n8n-core';
import { Credentials, UserSettings } from 'n8n-core';
import {
IDataObject
} from 'n8n-workflow';
import {
Db,
ICredentialsDecryptedDb,
} from '../../src';
import {
getLogger,
} from '../../src/Logger';
import {
LoggerProxy,
} from 'n8n-workflow';
import { IDataObject, LoggerProxy } from 'n8n-workflow';
import * as fs from 'fs';
import * as path from 'path';
import { getLogger } from '../../src/Logger';
import { Db, ICredentialsDecryptedDb } from '../../src';
export class ExportCredentialsCommand extends Command {
static description = 'Export credentials';
@@ -45,7 +29,8 @@ export class ExportCredentialsCommand extends Command {
description: 'Export all credentials',
}),
backup: flags.boolean({
description: 'Sets --all --pretty --separate for simple backups. Only --output has to be set additionally.',
description:
'Sets --all --pretty --separate for simple backups. Only --output has to be set additionally.',
}),
id: flags.string({
description: 'The ID of the credential to export',
@@ -58,19 +43,23 @@ export class ExportCredentialsCommand extends Command {
description: 'Format the output in an easier to read fashion',
}),
separate: flags.boolean({
description: 'Exports one file per credential (useful for versioning). Must inform a directory via --output.',
description:
'Exports one file per credential (useful for versioning). Must inform a directory via --output.',
}),
decrypted: flags.boolean({
description: 'Exports data decrypted / in plain text. ALL SENSITIVE INFORMATION WILL BE VISIBLE IN THE FILES. Use to migrate from a installation to another that have a different secret key (in the config file).',
description:
'Exports data decrypted / in plain text. ALL SENSITIVE INFORMATION WILL BE VISIBLE IN THE FILES. Use to migrate from a installation to another that have a different secret key (in the config file).',
}),
};
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
async run() {
const logger = getLogger();
LoggerProxy.init(logger);
// eslint-disable-next-line @typescript-eslint/no-shadow
const { flags } = this.parse(ExportCredentialsCommand);
if (flags.backup) {
flags.all = true;
flags.pretty = true;
@@ -103,7 +92,9 @@ export class ExportCredentialsCommand extends Command {
fs.mkdirSync(flags.output, { recursive: true });
}
} catch (e) {
console.error('Aborting execution as a filesystem error has been encountered while creating the output directory. See log messages for details.');
console.error(
'Aborting execution as a filesystem error has been encountered while creating the output directory. See log messages for details.',
);
logger.error('\nFILESYSTEM ERROR');
logger.info('====================================');
logger.error(e.message);
@@ -127,6 +118,7 @@ export class ExportCredentialsCommand extends Command {
findQuery.id = flags.id;
}
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
const credentials = await Db.collections.Credentials!.find(findQuery);
if (flags.decrypted) {
@@ -148,17 +140,22 @@ export class ExportCredentialsCommand extends Command {
}
if (flags.separate) {
let fileContents: string, i: number;
let fileContents: string;
let i: number;
for (i = 0; i < credentials.length; i++) {
fileContents = JSON.stringify(credentials[i], null, flags.pretty ? 2 : undefined);
const filename = (flags.output!.endsWith(path.sep) ? flags.output! : flags.output + path.sep) + credentials[i].id + '.json';
const filename = `${
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
(flags.output!.endsWith(path.sep) ? flags.output! : flags.output + path.sep) +
credentials[i].id
}.json`;
fs.writeFileSync(filename, fileContents);
}
console.info(`Successfully exported ${i} credentials.`);
} else {
const fileContents = JSON.stringify(credentials, null, flags.pretty ? 2 : undefined);
if (flags.output) {
fs.writeFileSync(flags.output!, fileContents);
fs.writeFileSync(flags.output, fileContents);
console.info(`Successfully exported ${credentials.length} credentials.`);
} else {
console.info(fileContents);

View File

@@ -1,26 +1,13 @@
import {
Command,
flags,
} from '@oclif/command';
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
/* eslint-disable no-console */
import { Command, flags } from '@oclif/command';
import {
IDataObject
} from 'n8n-workflow';
import {
Db,
} from '../../src';
import {
getLogger,
} from '../../src/Logger';
import {
LoggerProxy,
} from 'n8n-workflow';
import { IDataObject, LoggerProxy } from 'n8n-workflow';
import * as fs from 'fs';
import * as path from 'path';
import { getLogger } from '../../src/Logger';
import { Db } from '../../src';
export class ExportWorkflowsCommand extends Command {
static description = 'Export workflows';
@@ -38,7 +25,8 @@ export class ExportWorkflowsCommand extends Command {
description: 'Export all workflows',
}),
backup: flags.boolean({
description: 'Sets --all --pretty --separate for simple backups. Only --output has to be set additionally.',
description:
'Sets --all --pretty --separate for simple backups. Only --output has to be set additionally.',
}),
id: flags.string({
description: 'The ID of the workflow to export',
@@ -51,14 +39,17 @@ export class ExportWorkflowsCommand extends Command {
description: 'Format the output in an easier to read fashion',
}),
separate: flags.boolean({
description: 'Exports one file per workflow (useful for versioning). Must inform a directory via --output.',
description:
'Exports one file per workflow (useful for versioning). Must inform a directory via --output.',
}),
};
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
async run() {
const logger = getLogger();
LoggerProxy.init(logger);
// eslint-disable-next-line @typescript-eslint/no-shadow
const { flags } = this.parse(ExportWorkflowsCommand);
if (flags.backup) {
@@ -93,7 +84,9 @@ export class ExportWorkflowsCommand extends Command {
fs.mkdirSync(flags.output, { recursive: true });
}
} catch (e) {
console.error('Aborting execution as a filesystem error has been encountered while creating the output directory. See log messages for details.');
console.error(
'Aborting execution as a filesystem error has been encountered while creating the output directory. See log messages for details.',
);
logger.error('\nFILESYSTEM ERROR');
logger.info('====================================');
logger.error(e.message);
@@ -117,6 +110,7 @@ export class ExportWorkflowsCommand extends Command {
findQuery.id = flags.id;
}
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
const workflows = await Db.collections.Workflow!.find(findQuery);
if (workflows.length === 0) {
@@ -124,18 +118,27 @@ export class ExportWorkflowsCommand extends Command {
}
if (flags.separate) {
let fileContents: string, i: number;
let fileContents: string;
let i: number;
for (i = 0; i < workflows.length; i++) {
fileContents = JSON.stringify(workflows[i], null, flags.pretty ? 2 : undefined);
const filename = (flags.output!.endsWith(path.sep) ? flags.output! : flags.output + path.sep) + workflows[i].id + '.json';
const filename = `${
// eslint-disable-next-line @typescript-eslint/restrict-plus-operands, @typescript-eslint/no-non-null-assertion
(flags.output!.endsWith(path.sep) ? flags.output! : flags.output + path.sep) +
workflows[i].id
}.json`;
fs.writeFileSync(filename, fileContents);
}
console.info(`Successfully exported ${i} workflows.`);
} else {
const fileContents = JSON.stringify(workflows, null, flags.pretty ? 2 : undefined);
if (flags.output) {
fs.writeFileSync(flags.output!, fileContents);
console.info(`Successfully exported ${workflows.length} ${workflows.length === 1 ? 'workflow.' : 'workflows.'}`);
fs.writeFileSync(flags.output, fileContents);
console.info(
`Successfully exported ${workflows.length} ${
workflows.length === 1 ? 'workflow.' : 'workflows.'
}`,
);
} else {
console.info(fileContents);
}

View File

@@ -1,28 +1,16 @@
import {
Command,
flags,
} from '@oclif/command';
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
/* eslint-disable no-console */
import { Command, flags } from '@oclif/command';
import {
Credentials,
UserSettings,
} from 'n8n-core';
import { Credentials, UserSettings } from 'n8n-core';
import {
Db,
} from '../../src';
import {
getLogger,
} from '../../src/Logger';
import {
LoggerProxy,
} from 'n8n-workflow';
import { LoggerProxy } from 'n8n-workflow';
import * as fs from 'fs';
import * as glob from 'fast-glob';
import * as path from 'path';
import { getLogger } from '../../src/Logger';
import { Db } from '../../src';
export class ImportCredentialsCommand extends Command {
static description = 'Import credentials';
@@ -43,10 +31,12 @@ export class ImportCredentialsCommand extends Command {
}),
};
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
async run() {
const logger = getLogger();
LoggerProxy.init(logger);
// eslint-disable-next-line @typescript-eslint/no-shadow
const { flags } = this.parse(ImportCredentialsCommand);
if (!flags.input) {
@@ -76,18 +66,25 @@ export class ImportCredentialsCommand extends Command {
}
if (flags.separate) {
const files = await glob((flags.input.endsWith(path.sep) ? flags.input : flags.input + path.sep) + '*.json');
const files = await glob(
`${flags.input.endsWith(path.sep) ? flags.input : flags.input + path.sep}*.json`,
);
for (i = 0; i < files.length; i++) {
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
const credential = JSON.parse(fs.readFileSync(files[i], { encoding: 'utf8' }));
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
if (typeof credential.data === 'object') {
// plain data / decrypted input. Should be encrypted first.
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
Credentials.prototype.setData.call(credential, credential.data, encryptionKey);
}
// eslint-disable-next-line no-await-in-loop, @typescript-eslint/no-non-null-assertion
await Db.collections.Credentials!.save(credential);
}
} else {
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
const fileContents = JSON.parse(fs.readFileSync(flags.input, { encoding: 'utf8' }));
if (!Array.isArray(fileContents)) {
@@ -97,8 +94,13 @@ export class ImportCredentialsCommand extends Command {
for (i = 0; i < fileContents.length; i++) {
if (typeof fileContents[i].data === 'object') {
// plain data / decrypted input. Should be encrypted first.
Credentials.prototype.setData.call(fileContents[i], fileContents[i].data, encryptionKey);
Credentials.prototype.setData.call(
fileContents[i],
fileContents[i].data,
encryptionKey,
);
}
// eslint-disable-next-line no-await-in-loop, @typescript-eslint/no-non-null-assertion
await Db.collections.Credentials!.save(fileContents[i]);
}
}

View File

@@ -1,26 +1,15 @@
import {
Command,
flags,
} from '@oclif/command';
/* eslint-disable no-console */
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
import { Command, flags } from '@oclif/command';
import {
Db,
} from '../../src';
import {
getLogger,
} from '../../src/Logger';
import {
LoggerProxy,
} from 'n8n-workflow';
import { LoggerProxy } from 'n8n-workflow';
import * as fs from 'fs';
import * as glob from 'fast-glob';
import * as path from 'path';
import {
UserSettings,
} from 'n8n-core';
import { UserSettings } from 'n8n-core';
import { getLogger } from '../../src/Logger';
import { Db } from '../../src';
export class ImportWorkflowsCommand extends Command {
static description = 'Import workflows';
@@ -41,10 +30,12 @@ export class ImportWorkflowsCommand extends Command {
}),
};
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
async run() {
const logger = getLogger();
LoggerProxy.init(logger);
// eslint-disable-next-line @typescript-eslint/no-shadow
const { flags } = this.parse(ImportWorkflowsCommand);
if (!flags.input) {
@@ -68,9 +59,12 @@ export class ImportWorkflowsCommand extends Command {
await UserSettings.prepareUserSettings();
let i;
if (flags.separate) {
const files = await glob((flags.input.endsWith(path.sep) ? flags.input : flags.input + path.sep) + '*.json');
const files = await glob(
`${flags.input.endsWith(path.sep) ? flags.input : flags.input + path.sep}*.json`,
);
for (i = 0; i < files.length; i++) {
const workflow = JSON.parse(fs.readFileSync(files[i], { encoding: 'utf8' }));
// eslint-disable-next-line no-await-in-loop, @typescript-eslint/no-non-null-assertion
await Db.collections.Workflow!.save(workflow);
}
} else {
@@ -81,6 +75,7 @@ export class ImportWorkflowsCommand extends Command {
}
for (i = 0; i < fileContents.length; i++) {
// eslint-disable-next-line no-await-in-loop, @typescript-eslint/no-non-null-assertion
await Db.collections.Workflow!.save(fileContents[i]);
}
}
@@ -89,6 +84,7 @@ export class ImportWorkflowsCommand extends Command {
process.exit(0);
} catch (error) {
console.error('An error occurred while exporting workflows. See log messages for details.');
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
logger.error(error.message);
this.exit(1);
}

View File

@@ -1,16 +1,10 @@
import {
Command,
flags,
} from '@oclif/command';
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
/* eslint-disable no-console */
import { Command, flags } from '@oclif/command';
import {
IDataObject
} from 'n8n-workflow';
import {
Db,
} from "../../src";
import { IDataObject } from 'n8n-workflow';
import { Db } from '../../src';
export class ListWorkflowCommand extends Command {
static description = '\nList workflows';
@@ -31,7 +25,9 @@ export class ListWorkflowCommand extends Command {
}),
};
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
async run() {
// eslint-disable-next-line @typescript-eslint/no-shadow
const { flags } = this.parse(ListWorkflowCommand);
if (flags.active !== undefined && !['true', 'false'].includes(flags.active)) {
@@ -46,14 +42,13 @@ export class ListWorkflowCommand extends Command {
findQuery.active = flags.active === 'true';
}
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
const workflows = await Db.collections.Workflow!.find(findQuery);
if (flags.onlyId) {
workflows.forEach(workflow => console.log(workflow.id));
workflows.forEach((workflow) => console.log(workflow.id));
} else {
workflows.forEach(workflow => console.log(workflow.id + "|" + workflow.name));
workflows.forEach((workflow) => console.log(`${workflow.id}|${workflow.name}`));
}
} catch (e) {
console.error('\nGOT ERROR');
console.log('====================================');

View File

@@ -1,12 +1,17 @@
/* eslint-disable @typescript-eslint/await-thenable */
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
/* eslint-disable @typescript-eslint/explicit-module-boundary-types */
/* eslint-disable @typescript-eslint/unbound-method */
/* eslint-disable no-console */
/* eslint-disable @typescript-eslint/no-unsafe-call */
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
import * as localtunnel from 'localtunnel';
import {
TUNNEL_SUBDOMAIN_ENV,
UserSettings,
} from 'n8n-core';
import { TUNNEL_SUBDOMAIN_ENV, UserSettings } from 'n8n-core';
import { Command, flags } from '@oclif/command';
const open = require('open');
// eslint-disable-next-line import/no-extraneous-dependencies
import * as Redis from 'ioredis';
import { IDataObject, LoggerProxy } from 'n8n-workflow';
import * as config from '../config';
import {
ActiveExecutions,
@@ -17,6 +22,7 @@ import {
Db,
ExternalHooks,
GenericHelpers,
// eslint-disable-next-line @typescript-eslint/no-unused-vars
IExecutionsCurrentSummary,
LoadNodesAndCredentials,
NodeTypes,
@@ -24,15 +30,11 @@ import {
TestWebhooks,
WaitTracker,
} from '../src';
import { IDataObject } from 'n8n-workflow';
import {
getLogger,
} from '../src/Logger';
import { getLogger } from '../src/Logger';
import {
LoggerProxy,
} from 'n8n-workflow';
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-var-requires
const open = require('open');
let activeWorkflowRunner: ActiveWorkflowRunner.ActiveWorkflowRunner | undefined;
let processExistCode = 0;
@@ -54,29 +56,32 @@ export class Start extends Command {
description: 'opens the UI automatically in browser',
}),
tunnel: flags.boolean({
description: 'runs the webhooks via a hooks.n8n.cloud tunnel server. Use only for testing and development!',
description:
'runs the webhooks via a hooks.n8n.cloud tunnel server. Use only for testing and development!',
}),
};
/**
* Opens the UI in browser
*/
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
static openBrowser() {
const editorUrl = GenericHelpers.getBaseUrl();
open(editorUrl, { wait: true })
.catch((error: Error) => {
console.log(`\nWas not able to open URL in browser. Please open manually by visiting:\n${editorUrl}\n`);
});
// eslint-disable-next-line @typescript-eslint/no-unused-vars
open(editorUrl, { wait: true }).catch((error: Error) => {
console.log(
`\nWas not able to open URL in browser. Please open manually by visiting:\n${editorUrl}\n`,
);
});
}
/**
* Stoppes the n8n in a graceful way.
* Make for example sure that all the webhooks from third party services
* get removed.
*/
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
static async stopProcess() {
getLogger().info('\nStopping n8n...');
@@ -90,10 +95,12 @@ export class Start extends Command {
process.exit(processExistCode);
}, 30000);
const skipWebhookDeregistration = config.get('endpoints.skipWebhoooksDeregistrationOnShutdown') as boolean;
const skipWebhookDeregistration = config.get(
'endpoints.skipWebhoooksDeregistrationOnShutdown',
) as boolean;
const removePromises = [];
if (activeWorkflowRunner !== undefined && skipWebhookDeregistration !== true) {
if (activeWorkflowRunner !== undefined && !skipWebhookDeregistration) {
removePromises.push(activeWorkflowRunner.removeAll());
}
@@ -105,22 +112,23 @@ export class Start extends Command {
// Wait for active workflow executions to finish
const activeExecutionsInstance = ActiveExecutions.getInstance();
let executingWorkflows = activeExecutionsInstance.getActiveExecutions() as IExecutionsCurrentSummary[];
let executingWorkflows = activeExecutionsInstance.getActiveExecutions();
let count = 0;
while (executingWorkflows.length !== 0) {
if (count++ % 4 === 0) {
console.log(`Waiting for ${executingWorkflows.length} active executions to finish...`);
executingWorkflows.map(execution => {
// eslint-disable-next-line array-callback-return
executingWorkflows.map((execution) => {
console.log(` - Execution ID ${execution.id}, workflow ID: ${execution.workflowId}`);
});
}
// eslint-disable-next-line no-await-in-loop
await new Promise((resolve) => {
setTimeout(resolve, 500);
});
executingWorkflows = activeExecutionsInstance.getActiveExecutions();
}
} catch (error) {
console.error('There was an error shutting down n8n.', error);
}
@@ -128,12 +136,12 @@ export class Start extends Command {
process.exit(processExistCode);
}
async run() {
// Make sure that n8n shuts down gracefully if possible
process.on('SIGTERM', Start.stopProcess);
process.on('SIGINT', Start.stopProcess);
// eslint-disable-next-line @typescript-eslint/no-shadow
const { flags } = this.parse(Start);
// Wrap that the process does not close but we can still use async
@@ -144,7 +152,9 @@ export class Start extends Command {
logger.info('Initializing n8n process');
// todo remove a few versions after release
logger.info('\nn8n now checks for new versions and security updates. You can turn this off using the environment variable N8N_VERSION_NOTIFICATIONS_ENABLED to "false"\nFor more information, please refer to https://docs.n8n.io/getting-started/installation/advanced/configuration.html\n');
logger.info(
'\nn8n now checks for new versions and security updates. You can turn this off using the environment variable N8N_VERSION_NOTIFICATIONS_ENABLED to "false"\nFor more information, please refer to https://docs.n8n.io/getting-started/installation/advanced/configuration.html\n',
);
// Start directly with the init of the database to improve startup time
const startDbInitPromise = Db.init().catch((error: Error) => {
@@ -186,9 +196,11 @@ export class Start extends Command {
const redisPort = config.get('queue.bull.redis.port');
const redisDB = config.get('queue.bull.redis.db');
const redisConnectionTimeoutLimit = config.get('queue.bull.redis.timeoutThreshold');
let lastTimer = 0, cumulativeTimeout = 0;
let lastTimer = 0;
let cumulativeTimeout = 0;
const settings = {
// eslint-disable-next-line @typescript-eslint/no-unused-vars
retryStrategy: (times: number): number | null => {
const now = Date.now();
if (now - lastTimer > 30000) {
@@ -199,7 +211,10 @@ export class Start extends Command {
cumulativeTimeout += now - lastTimer;
lastTimer = now;
if (cumulativeTimeout > redisConnectionTimeoutLimit) {
logger.error('Unable to connect to Redis after ' + redisConnectionTimeoutLimit + ". Exiting process.");
logger.error(
// eslint-disable-next-line @typescript-eslint/restrict-template-expressions
`Unable to connect to Redis after ${redisConnectionTimeoutLimit}. Exiting process.`,
);
process.exit(1);
}
}
@@ -235,20 +250,24 @@ export class Start extends Command {
});
}
const dbType = await GenericHelpers.getConfigValue('database.type') as DatabaseType;
const dbType = (await GenericHelpers.getConfigValue('database.type')) as DatabaseType;
if (dbType === 'sqlite') {
const shouldRunVacuum = config.get('database.sqlite.executeVacuumOnStartup') as number;
if (shouldRunVacuum) {
// eslint-disable-next-line @typescript-eslint/no-floating-promises, @typescript-eslint/no-non-null-assertion
await Db.collections.Execution!.query('VACUUM;');
}
}
if (flags.tunnel === true) {
if (flags.tunnel) {
this.log('\nWaiting for tunnel ...');
let tunnelSubdomain;
if (process.env[TUNNEL_SUBDOMAIN_ENV] !== undefined && process.env[TUNNEL_SUBDOMAIN_ENV] !== '') {
if (
process.env[TUNNEL_SUBDOMAIN_ENV] !== undefined &&
process.env[TUNNEL_SUBDOMAIN_ENV] !== ''
) {
tunnelSubdomain = process.env[TUNNEL_SUBDOMAIN_ENV];
} else if (userSettings.tunnelSubdomain !== undefined) {
tunnelSubdomain = userSettings.tunnelSubdomain;
@@ -257,9 +276,13 @@ export class Start extends Command {
if (tunnelSubdomain === undefined) {
// When no tunnel subdomain did exist yet create a new random one
const availableCharacters = 'abcdefghijklmnopqrstuvwxyz0123456789';
userSettings.tunnelSubdomain = Array.from({ length: 24 }).map(() => {
return availableCharacters.charAt(Math.floor(Math.random() * availableCharacters.length));
}).join('');
userSettings.tunnelSubdomain = Array.from({ length: 24 })
.map(() => {
return availableCharacters.charAt(
Math.floor(Math.random() * availableCharacters.length),
);
})
.join('');
await UserSettings.writeUserSettings(userSettings);
}
@@ -269,14 +292,16 @@ export class Start extends Command {
subdomain: tunnelSubdomain,
};
const port = config.get('port') as number;
const port = config.get('port');
// @ts-ignore
const webhookTunnel = await localtunnel(port, tunnelSettings);
process.env.WEBHOOK_URL = webhookTunnel.url + '/';
process.env.WEBHOOK_URL = `${webhookTunnel.url}/`;
this.log(`Tunnel URL: ${process.env.WEBHOOK_URL}\n`);
this.log('IMPORTANT! Do not share with anybody as it would give people access to your n8n instance!');
this.log(
'IMPORTANT! Do not share with anybody as it would give people access to your n8n instance!',
);
}
await Server.start();
@@ -285,6 +310,7 @@ export class Start extends Command {
activeWorkflowRunner = ActiveWorkflowRunner.getInstance();
await activeWorkflowRunner.init();
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const waitTracker = WaitTracker();
const editorUrl = GenericHelpers.getBaseUrl();
@@ -297,7 +323,7 @@ export class Start extends Command {
process.stdin.setEncoding('utf8');
let inputText = '';
if (flags.open === true) {
if (flags.open) {
Start.openBrowser();
}
this.log(`\nPress "o" to open in Browser.`);
@@ -307,15 +333,18 @@ export class Start extends Command {
inputText = '';
} else if (key.charCodeAt(0) === 3) {
// Ctrl + c got pressed
// eslint-disable-next-line @typescript-eslint/no-floating-promises
Start.stopProcess();
} else {
// When anything else got pressed, record it and send it on enter into the child process
// eslint-disable-next-line no-lonely-if
if (key.charCodeAt(0) === 13) {
// send to child process and print in terminal
process.stdout.write('\n');
inputText = '';
} else {
// record it and write into terminal
// eslint-disable-next-line @typescript-eslint/no-unused-vars
inputText += key;
process.stdout.write(key);
}
@@ -323,6 +352,7 @@ export class Start extends Command {
});
}
} catch (error) {
// eslint-disable-next-line @typescript-eslint/restrict-template-expressions
this.error(`There was an error: ${error.message}`);
processExistCode = 1;

View File

@@ -1,26 +1,16 @@
import {
Command, flags,
} from '@oclif/command';
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
/* eslint-disable no-console */
import { Command, flags } from '@oclif/command';
import {
IDataObject
} from 'n8n-workflow';
import { IDataObject, LoggerProxy } from 'n8n-workflow';
import {
Db,
GenericHelpers,
} from '../../src';
// eslint-disable-next-line @typescript-eslint/no-unused-vars
import { Db, GenericHelpers } from '../../src';
import {
getLogger,
} from '../../src/Logger';
import {
LoggerProxy,
} from 'n8n-workflow';
import { getLogger } from '../../src/Logger';
export class UpdateWorkflowCommand extends Command {
static description = '\Update workflows';
static description = 'Update workflows';
static examples = [
`$ n8n update:workflow --all --active=false`,
@@ -40,10 +30,12 @@ export class UpdateWorkflowCommand extends Command {
}),
};
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
async run() {
const logger = getLogger();
LoggerProxy.init(logger);
// eslint-disable-next-line @typescript-eslint/no-shadow
const { flags } = this.parse(UpdateWorkflowCommand);
if (!flags.all && !flags.id) {
@@ -52,7 +44,9 @@ export class UpdateWorkflowCommand extends Command {
}
if (flags.all && flags.id) {
console.info(`Either something else on top should be "--all" or "--id" can be set never both!`);
console.info(
`Either something else on top should be "--all" or "--id" can be set never both!`,
);
return;
}
@@ -60,13 +54,12 @@ export class UpdateWorkflowCommand extends Command {
if (flags.active === undefined) {
console.info(`No update flag like "--active=true" has been set!`);
return;
} else {
if (!['false', 'true'].includes(flags.active)) {
console.info(`Valid values for flag "--active" are only "false" or "true"!`);
return;
}
updateQuery.active = flags.active === 'true';
}
if (!['false', 'true'].includes(flags.active)) {
console.info(`Valid values for flag "--active" are only "false" or "true"!`);
return;
}
updateQuery.active = flags.active === 'true';
try {
await Db.init();
@@ -80,6 +73,7 @@ export class UpdateWorkflowCommand extends Command {
findQuery.active = true;
}
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
await Db.collections.Workflow!.update(findQuery, updateQuery);
console.info('Done');
} catch (e) {

View File

@@ -1,9 +1,14 @@
import {
UserSettings,
} from 'n8n-core';
/* eslint-disable no-console */
/* eslint-disable @typescript-eslint/no-unsafe-call */
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
/* eslint-disable @typescript-eslint/unbound-method */
import { UserSettings } from 'n8n-core';
import { Command, flags } from '@oclif/command';
// eslint-disable-next-line import/no-extraneous-dependencies
import * as Redis from 'ioredis';
import { IDataObject, LoggerProxy } from 'n8n-workflow';
import * as config from '../config';
import {
ActiveExecutions,
@@ -15,29 +20,20 @@ import {
GenericHelpers,
LoadNodesAndCredentials,
NodeTypes,
// eslint-disable-next-line @typescript-eslint/no-unused-vars
TestWebhooks,
WebhookServer,
} from '../src';
import { IDataObject } from 'n8n-workflow';
import {
getLogger,
} from '../src/Logger';
import {
LoggerProxy,
} from 'n8n-workflow';
import { getLogger } from '../src/Logger';
let activeWorkflowRunner: ActiveWorkflowRunner.ActiveWorkflowRunner | undefined;
let processExistCode = 0;
export class Webhook extends Command {
static description = 'Starts n8n webhook process. Intercepts only production URLs.';
static examples = [
`$ n8n webhook`,
];
static examples = [`$ n8n webhook`];
static flags = {
help: flags.help({ char: 'h' }),
@@ -48,6 +44,7 @@ export class Webhook extends Command {
* Make for example sure that all the webhooks from third party services
* get removed.
*/
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
static async stopProcess() {
LoggerProxy.info(`\nStopping n8n...`);
@@ -68,14 +65,16 @@ export class Webhook extends Command {
let count = 0;
while (executingWorkflows.length !== 0) {
if (count++ % 4 === 0) {
LoggerProxy.info(`Waiting for ${executingWorkflows.length} active executions to finish...`);
LoggerProxy.info(
`Waiting for ${executingWorkflows.length} active executions to finish...`,
);
}
// eslint-disable-next-line no-await-in-loop
await new Promise((resolve) => {
setTimeout(resolve, 500);
});
executingWorkflows = activeExecutionsInstance.getActiveExecutions();
}
} catch (error) {
LoggerProxy.error('There was an error shutting down n8n.', error);
}
@@ -83,7 +82,7 @@ export class Webhook extends Command {
process.exit(processExistCode);
}
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
async run() {
const logger = getLogger();
LoggerProxy.init(logger);
@@ -92,6 +91,7 @@ export class Webhook extends Command {
process.on('SIGTERM', Webhook.stopProcess);
process.on('SIGINT', Webhook.stopProcess);
// eslint-disable-next-line @typescript-eslint/no-unused-vars, @typescript-eslint/no-shadow
const { flags } = this.parse(Webhook);
// Wrap that the process does not close but we can still use async
@@ -114,7 +114,8 @@ export class Webhook extends Command {
try {
// Start directly with the init of the database to improve startup time
const startDbInitPromise = Db.init().catch(error => {
const startDbInitPromise = Db.init().catch((error) => {
// eslint-disable-next-line @typescript-eslint/restrict-template-expressions, @typescript-eslint/no-unsafe-member-access
logger.error(`There was an error initializing DB: "${error.message}"`);
processExistCode = 1;
@@ -124,6 +125,7 @@ export class Webhook extends Command {
});
// Make sure the settings exist
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const userSettings = await UserSettings.prepareUserSettings();
// Load all node and credential types
@@ -153,9 +155,11 @@ export class Webhook extends Command {
const redisPort = config.get('queue.bull.redis.port');
const redisDB = config.get('queue.bull.redis.db');
const redisConnectionTimeoutLimit = config.get('queue.bull.redis.timeoutThreshold');
let lastTimer = 0, cumulativeTimeout = 0;
let lastTimer = 0;
let cumulativeTimeout = 0;
const settings = {
// eslint-disable-next-line @typescript-eslint/no-unused-vars
retryStrategy: (times: number): number | null => {
const now = Date.now();
if (now - lastTimer > 30000) {
@@ -166,7 +170,10 @@ export class Webhook extends Command {
cumulativeTimeout += now - lastTimer;
lastTimer = now;
if (cumulativeTimeout > redisConnectionTimeoutLimit) {
logger.error('Unable to connect to Redis after ' + redisConnectionTimeoutLimit + ". Exiting process.");
logger.error(
// eslint-disable-next-line @typescript-eslint/restrict-template-expressions
`Unable to connect to Redis after ${redisConnectionTimeoutLimit}. Exiting process.`,
);
process.exit(1);
}
}
@@ -208,11 +215,12 @@ export class Webhook extends Command {
activeWorkflowRunner = ActiveWorkflowRunner.getInstance();
await activeWorkflowRunner.initWebhooks();
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const editorUrl = GenericHelpers.getBaseUrl();
console.info('Webhook listener waiting for requests.');
} catch (error) {
console.error('Exiting due to error. See log message for details.');
// eslint-disable-next-line @typescript-eslint/restrict-template-expressions
logger.error(`Webhook process cannot continue. "${error.message}"`);
processExistCode = 1;

View File

@@ -1,10 +1,16 @@
/* eslint-disable no-console */
/* eslint-disable @typescript-eslint/no-shadow */
/* eslint-disable @typescript-eslint/explicit-module-boundary-types */
/* eslint-disable @typescript-eslint/unbound-method */
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
/* eslint-disable @typescript-eslint/no-non-null-assertion */
/* eslint-disable @typescript-eslint/restrict-template-expressions */
/* eslint-disable @typescript-eslint/no-unused-vars */
// eslint-disable-next-line import/no-extraneous-dependencies
import * as PCancelable from 'p-cancelable';
import { Command, flags } from '@oclif/command';
import {
UserSettings,
WorkflowExecute,
} from 'n8n-core';
import { UserSettings, WorkflowExecute } from 'n8n-core';
import {
IDataObject,
@@ -13,12 +19,12 @@ import {
IWorkflowExecuteHooks,
Workflow,
WorkflowHooks,
LoggerProxy,
} from 'n8n-workflow';
import {
FindOneOptions,
} from 'typeorm';
import { FindOneOptions } from 'typeorm';
import * as Bull from 'bull';
import {
ActiveExecutions,
CredentialsOverwrites,
@@ -37,24 +43,15 @@ import {
WorkflowExecuteAdditionalData,
} from '../src';
import {
getLogger,
} from '../src/Logger';
import {
LoggerProxy,
} from 'n8n-workflow';
import { getLogger } from '../src/Logger';
import * as config from '../config';
import * as Bull from 'bull';
import * as Queue from '../src/Queue';
export class Worker extends Command {
static description = '\nStarts a n8n worker';
static examples = [
`$ n8n worker --concurrency=5`,
];
static examples = [`$ n8n worker --concurrency=5`];
static flags = {
help: flags.help({ char: 'h' }),
@@ -82,6 +79,7 @@ export class Worker extends Command {
LoggerProxy.info(`Stopping n8n...`);
// Stop accepting new jobs
// eslint-disable-next-line @typescript-eslint/no-floating-promises
Worker.jobQueue.pause(true);
try {
@@ -103,13 +101,17 @@ export class Worker extends Command {
while (Object.keys(Worker.runningJobs).length !== 0) {
if (count++ % 4 === 0) {
const waitLeft = Math.ceil((stopTime - new Date().getTime()) / 1000);
LoggerProxy.info(`Waiting for ${Object.keys(Worker.runningJobs).length} active executions to finish... (wait ${waitLeft} more seconds)`);
LoggerProxy.info(
`Waiting for ${
Object.keys(Worker.runningJobs).length
} active executions to finish... (wait ${waitLeft} more seconds)`,
);
}
// eslint-disable-next-line no-await-in-loop
await new Promise((resolve) => {
setTimeout(resolve, 500);
});
}
} catch (error) {
LoggerProxy.error('There was an error shutting down n8n.', error);
}
@@ -119,25 +121,38 @@ export class Worker extends Command {
async runJob(job: Bull.Job, nodeTypes: INodeTypes): Promise<IBullJobResponse> {
const jobData = job.data as IBullJobData;
const executionDb = await Db.collections.Execution!.findOne(jobData.executionId) as IExecutionFlattedDb;
const currentExecutionDb = ResponseHelper.unflattenExecutionData(executionDb) as IExecutionResponse;
LoggerProxy.info(`Start job: ${job.id} (Workflow ID: ${currentExecutionDb.workflowData.id} | Execution: ${jobData.executionId})`);
const executionDb = (await Db.collections.Execution!.findOne(
jobData.executionId,
)) as IExecutionFlattedDb;
const currentExecutionDb = ResponseHelper.unflattenExecutionData(executionDb);
LoggerProxy.info(
`Start job: ${job.id} (Workflow ID: ${currentExecutionDb.workflowData.id} | Execution: ${jobData.executionId})`,
);
let staticData = currentExecutionDb.workflowData!.staticData;
if (jobData.loadStaticData === true) {
let { staticData } = currentExecutionDb.workflowData;
if (jobData.loadStaticData) {
const findOptions = {
select: ['id', 'staticData'],
} as FindOneOptions;
const workflowData = await Db.collections!.Workflow!.findOne(currentExecutionDb.workflowData.id, findOptions);
const workflowData = await Db.collections.Workflow!.findOne(
currentExecutionDb.workflowData.id,
findOptions,
);
if (workflowData === undefined) {
throw new Error(`The workflow with the ID "${currentExecutionDb.workflowData.id}" could not be found`);
throw new Error(
`The workflow with the ID "${currentExecutionDb.workflowData.id}" could not be found`,
);
}
staticData = workflowData.staticData;
}
let workflowTimeout = config.get('executions.timeout') as number; // initialize with default
if (currentExecutionDb.workflowData.settings && currentExecutionDb.workflowData.settings.executionTimeout) {
workflowTimeout = currentExecutionDb.workflowData.settings!.executionTimeout as number; // preference on workflow setting
if (
// eslint-disable-next-line @typescript-eslint/prefer-optional-chain
currentExecutionDb.workflowData.settings &&
currentExecutionDb.workflowData.settings.executionTimeout
) {
workflowTimeout = currentExecutionDb.workflowData.settings.executionTimeout as number; // preference on workflow setting
}
let executionTimeoutTimestamp: number | undefined;
@@ -146,16 +161,37 @@ export class Worker extends Command {
executionTimeoutTimestamp = Date.now() + workflowTimeout * 1000;
}
const workflow = new Workflow({ id: currentExecutionDb.workflowData.id as string, name: currentExecutionDb.workflowData.name, nodes: currentExecutionDb.workflowData!.nodes, connections: currentExecutionDb.workflowData!.connections, active: currentExecutionDb.workflowData!.active, nodeTypes, staticData, settings: currentExecutionDb.workflowData!.settings });
const workflow = new Workflow({
id: currentExecutionDb.workflowData.id as string,
name: currentExecutionDb.workflowData.name,
nodes: currentExecutionDb.workflowData.nodes,
connections: currentExecutionDb.workflowData.connections,
active: currentExecutionDb.workflowData.active,
nodeTypes,
staticData,
settings: currentExecutionDb.workflowData.settings,
});
const additionalData = await WorkflowExecuteAdditionalData.getBase(undefined, executionTimeoutTimestamp);
additionalData.hooks = WorkflowExecuteAdditionalData.getWorkflowHooksWorkerExecuter(currentExecutionDb.mode, job.data.executionId, currentExecutionDb.workflowData, { retryOf: currentExecutionDb.retryOf as string });
const additionalData = await WorkflowExecuteAdditionalData.getBase(
undefined,
executionTimeoutTimestamp,
);
additionalData.hooks = WorkflowExecuteAdditionalData.getWorkflowHooksWorkerExecuter(
currentExecutionDb.mode,
job.data.executionId,
currentExecutionDb.workflowData,
{ retryOf: currentExecutionDb.retryOf as string },
);
additionalData.executionId = jobData.executionId;
let workflowExecute: WorkflowExecute;
let workflowRun: PCancelable<IRun>;
if (currentExecutionDb.data !== undefined) {
workflowExecute = new WorkflowExecute(additionalData, currentExecutionDb.mode, currentExecutionDb.data);
workflowExecute = new WorkflowExecute(
additionalData,
currentExecutionDb.mode,
currentExecutionDb.data,
);
workflowRun = workflowExecute.processRunExecutionData(workflow);
} else {
// Execute all nodes
@@ -180,6 +216,7 @@ export class Worker extends Command {
const logger = getLogger();
LoggerProxy.init(logger);
// eslint-disable-next-line no-console
console.info('Starting n8n worker...');
// Make sure that n8n shuts down gracefully if possible
@@ -192,7 +229,7 @@ export class Worker extends Command {
const { flags } = this.parse(Worker);
// Start directly with the init of the database to improve startup time
const startDbInitPromise = Db.init().catch(error => {
const startDbInitPromise = Db.init().catch((error) => {
logger.error(`There was an error initializing DB: "${error.message}"`);
Worker.processExistCode = 1;
@@ -225,10 +262,12 @@ export class Worker extends Command {
// Wait till the database is ready
await startDbInitPromise;
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
const redisConnectionTimeoutLimit = config.get('queue.bull.redis.timeoutThreshold');
Worker.jobQueue = Queue.getInstance().getBullObjectInstance();
Worker.jobQueue.process(flags.concurrency, (job) => this.runJob(job, nodeTypes));
// eslint-disable-next-line @typescript-eslint/no-floating-promises
Worker.jobQueue.process(flags.concurrency, async (job) => this.runJob(job, nodeTypes));
const versions = await GenericHelpers.getVersions();
@@ -251,9 +290,10 @@ export class Worker extends Command {
}
});
let lastTimer = 0, cumulativeTimeout = 0;
let lastTimer = 0;
let cumulativeTimeout = 0;
Worker.jobQueue.on('error', (error: Error) => {
if (error.toString().includes('ECONNREFUSED') === true) {
if (error.toString().includes('ECONNREFUSED')) {
const now = Date.now();
if (now - lastTimer > 30000) {
// Means we had no timeout at all or last timeout was temporary and we recovered
@@ -263,12 +303,14 @@ export class Worker extends Command {
cumulativeTimeout += now - lastTimer;
lastTimer = now;
if (cumulativeTimeout > redisConnectionTimeoutLimit) {
logger.error('Unable to connect to Redis after ' + redisConnectionTimeoutLimit + ". Exiting process.");
logger.error(
`Unable to connect to Redis after ${redisConnectionTimeoutLimit}. Exiting process.`,
);
process.exit(1);
}
}
logger.warn('Redis unavailable - trying to reconnect...');
} else if (error.toString().includes('Error initializing Lua scripts') === true) {
} else if (error.toString().includes('Error initializing Lua scripts')) {
// This is a non-recoverable error
// Happens when worker starts and Redis is unavailable
// Even if Redis comes back online, worker will be zombie
@@ -287,6 +329,5 @@ export class Worker extends Command {
process.exit(1);
}
})();
}
}