diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md
index 98d3702d9c..9a150b04b1 100644
--- a/.github/ISSUE_TEMPLATE/bug_report.md
+++ b/.github/ISSUE_TEMPLATE/bug_report.md
@@ -22,8 +22,10 @@ A clear and concise description of what you expected to happen.
**Environment (please complete the following information):**
- OS: [e.g. Ubuntu Linux 18.04]
- - n8n Version [e.g. 0.26.0]
- - Node.js Version [e.g. 10.16.0]
+ - n8n Version [e.g. 0.119.0]
+ - Node.js Version [e.g. 14.16.0]
+ - Database system [e.g. SQLite; n8n uses SQLite as default otherwise changed]
+ - Operation mode [e.g. own; operation modes are `own`, `main` and `queue`. Default is `own`]
**Additional context**
Add any other context about the problem here.
diff --git a/.gitignore b/.gitignore
index 0441d445b4..d457b50091 100644
--- a/.gitignore
+++ b/.gitignore
@@ -13,3 +13,4 @@ _START_PACKAGE
.vscode
.idea
.prettierrc.js
+vetur.config.js
diff --git a/README.md b/README.md
index 6e1e7e1766..1fd4f1128c 100644
--- a/README.md
+++ b/README.md
@@ -63,9 +63,7 @@ If you have problems or questions go to our forum, we will then try to help you
## Jobs
If you are interested in working for n8n and so shape the future of the project
-check out our job posts:
-
-[https://n8n.join.com](https://n8n.join.com)
+check out our [job posts](https://apply.workable.com/n8n/)
diff --git a/docker/images/n8n/README.md b/docker/images/n8n/README.md
index ef0d7123e7..cb484addb0 100644
--- a/docker/images/n8n/README.md
+++ b/docker/images/n8n/README.md
@@ -49,6 +49,7 @@ Additional information and example workflows on the n8n.io website: [https://n8n
docker run -it --rm \
--name n8n \
-p 5678:5678 \
+ -v ~/.n8n:/home/node/.n8n \
n8nio/n8n
```
@@ -262,9 +263,7 @@ If you have problems or questions go to our forum, we will then try to help you
## Jobs
If you are interested in working for n8n and so shape the future of the project
-check out our job posts:
-
-[https://n8n.join.com](https://n8n.join.com)
+check out our [job posts](https://apply.workable.com/n8n/)
diff --git a/packages/cli/README.md b/packages/cli/README.md
index e7f951bbbe..d7aad52434 100644
--- a/packages/cli/README.md
+++ b/packages/cli/README.md
@@ -2,7 +2,7 @@

-n8n is a free and open [fair-code](http://faircode.io) distributed node based Workflow Automation Tool. It can be self-hosted, easily extended, and so also used with internal tools.
+n8n is a free and open [fair-code](http://faircode.io) distributed node-based Workflow Automation Tool. You can self-host n8n, easily extend it, and even use it with internal tools.
@@ -11,100 +11,145 @@ n8n is a free and open [fair-code](http://faircode.io) distributed node based Wo
- [Demo](#demo)
+- [Getting Started](#getting-started)
+ - [Use npx](#use-npx)
+ - [Run with Docker](#run-with-docker)
+ - [Install with npm](#install-with-npm)
+ - [Sign-up on n8n.cloud](#sign-up-on-n8n.cloud)
- [Available integrations](#available-integrations)
- [Documentation](#documentation)
- [Create Custom Nodes](#create-custom-nodes)
-- [Hosted n8n](#hosted-n8n)
+- [Contributing](#contributing)
- [What does n8n mean and how do you pronounce it](#what-does-n8n-mean-and-how-do-you-pronounce-it)
- [Support](#support)
- [Jobs](#jobs)
- [Upgrading](#upgrading)
- [License](#license)
-- [Development](#development)
## Demo
-[:tv: A short demo (< 3 min)](https://www.youtube.com/watch?v=3w7xIMKLVAg)
-which shows how to create a simple workflow which automatically sends a new
-Slack notification every time a Github repository received or lost a star.
+πΊ Here's a [short demo (<3 min)](https://www.youtube.com/watch?v=3w7xIMKLVAg) that shows how to create a simple workflow to automatically sends a notification on Slack every time a GitHub repository gets starred or un-starred.
+## Getting Started
+
+There are a couple of ways to get started with n8n.
+
+### Use npx
+
+To spin up n8n using npx, you can run:
+
+```bash
+npx n8n
+```
+
+It will download everything that is needed to start n8n.
+
+You can then access n8n by opening:
+[http://localhost:5678](http://localhost:5678)
+
+**Note:** The minimum required version for Node.js is v14.15. Make sure to update Node.js to v14.15 or above.
+
+### Run with Docker
+
+To play around with n8n, you can also start it using Docker:
+
+```bash
+docker run -it --rm \
+ --name n8n \
+ -p 5678:5678 \
+ n8nio/n8n
+```
+
+Be aware that all the data will be lost once the Docker container gets removed. To persist the data mount the `~/.n8n` folder:
+
+```bash
+docker run -it --rm \
+ --name n8n \
+ -p 5678:5678 \
+ -v ~/.n8n:/home/node/.n8n \
+ n8nio/n8n
+```
+
+n8n also offers a Docker image for Raspberry Pi: `n8nio/n8n:latest-rpi`.
+
+Refer to the [documentation](https://github.com/n8n-io/n8n/blob/master/docker/images/n8n/README.md) for more information on the Docker setup.
+
+### Install with npm
+
+To install n8n globally using npm:
+
+```bash
+npm install n8n -g
+```
+
+After the installation, start n8n running the following command:
+
+```bash
+n8n
+# or
+n8n start
+```
+
+### Sign-up on n8n.cloud
+
+Sign-up for an [n8n.cloud](https://www.n8n.cloud/) account.
+
+While n8n.cloud and n8n are the same in terms of features, n8n.cloud provides certain conveniences such as:
+- Not having to set up and maintain your n8n instance
+- Managed OAuth for authentication
+- Easily upgrading to the newer n8n versions
## Available integrations
-n8n has 200+ different nodes to automate workflows. The list can be found on: [https://n8n.io/nodes](https://n8n.io/nodes)
-
+n8n has 280+ different nodes that allow you to connect various services and build your automation workflows. You can find the list of all the integrations at [https://n8n.io/integrations](https://n8n.io/integrations)
## Documentation
-The official n8n documentation can be found under: [https://docs.n8n.io](https://docs.n8n.io)
+To learn more about n8n, refer to the official documentation here: [https://docs.n8n.io](https://docs.n8n.io)
-Additional information and example workflows on the n8n.io website: [https://n8n.io](https://n8n.io)
+You can find additional information and example workflows on the [n8n.io](https://n8n.io) website.
## Create Custom Nodes
-It is very easy to create own nodes for n8n. More information about that can
-be found in the documentation of "n8n-node-dev" which is a small CLI which
-helps with n8n-node-development.
+You can create custom nodes for n8n. Follow the instructions mentioned in the documentation to create your node: [Creating nodes](https://docs.n8n.io/nodes/creating-nodes/create-node.html)
-[To n8n-node-dev](https://github.com/n8n-io/n8n/tree/master/packages/node-dev)
+## Contributing
-Additional information can be found on the [ documentation page](https://docs.n8n.io/#/create-node).
+π Did you find a bug?
+β¨ Do you want to contribute a feature?
-## Hosted n8n
+The [CONTRIBUTING guide](https://github.com/n8n-io/n8n/blob/master/CONTRIBUTING.md) will help you set up your development environment.
-If you are interested in a hosted version of n8n on our infrastructure please contact us via:
-[hosting@n8n.io](mailto:hosting@n8n.io)
+You can find more information on how you can contribute to the project on our documentation: [How can I contribute?](https://docs.n8n.io/reference/contributing.html)
+## What does n8n mean, and how do you pronounce it?
+**Short answer:** n8n is an abbreviation for "nodemation", and it is pronounced as n-eight-n.
-## What does n8n mean and how do you pronounce it?
-
-**Short answer:** It means "nodemation" and it is pronounced as n-eight-n.
-
-**Long answer:** I get that question quite often (more often than I expected)
-so I decided it is probably best to answer it here. While looking for a
-good name for the project with a free domain I realized very quickly that all the
-good ones I could think of were already taken. So, in the end, I chose
-nodemation. "node-" in the sense that it uses a Node-View and that it uses
-Node.js and "-mation" for "automation" which is what the project is supposed to help with.
-However, I did not like how long the name was and I could not imagine writing
-something that long every time in the CLI. That is when I then ended up on
-"n8n". Sure does not work perfectly but does neither for Kubernetes (k8s) and
-did not hear anybody complain there. So I guess it should be ok.
+**Long answer:** In n8n, you build your automation ("-mation") workflows by connecting different nodes in the Editor UI. The project is also built using Node.js. As a consequence, the project was named nodemation.
+However, the name was long, and it wouldn't be a good idea to use such a long name in the CLI. Hence, nodemation got abbreviated as "n8n" (there are eight characters between the first and the last n!).
## Support
-If you have problems or questions go to our forum, we will then try to help you asap:
-
-[https://community.n8n.io](https://community.n8n.io)
-
-
+If you run into issues or have any questions reach out to us via our community forum: [https://community.n8n.io](https://community.n8n.io).
## Jobs
-If you are interested in working for n8n and so shape the future of the project
-check out our job posts:
-
-[https://n8n.join.com](https://n8n.join.com)
+If you are interested in working at n8n and building the project, check out the [job openings](https://apply.workable.com/n8n/).
## Upgrading
-Before you upgrade to the latest version make sure to check here if there are any breaking changes which concern you:
-[Breaking Changes](https://github.com/n8n-io/n8n/blob/master/packages/cli/BREAKING-CHANGES.md)
+Before you upgrade to the latest version, make sure to check the changelogs: [Changelog](https://docs.n8n.io/reference/changelog.html)
+You can also find breaking changes here: [Breaking Changes](./BREAKING-CHANGES.md)
## License
-n8n is [fair-code](http://faircode.io) distributed under [**Apache 2.0 with Commons Clause**](https://github.com/n8n-io/n8n/blob/master/packages/cli/LICENSE.md) license
+n8n is [fair-code](http://faircode.io) distributed under [**Apache 2.0 with Commons Clause**](https://github.com/n8n-io/n8n/blob/master/packages/cli/LICENSE.md) license.
-Additional information about license can be found in the [FAQ](https://docs.n8n.io/#/faq?id=license)
-
-
-## Development
-
-Have you found a bug :bug: ? Or maybe you have a nice feature :sparkles: to contribute ? The [CONTRIBUTING guide](https://github.com/n8n-io/n8n/blob/master/CONTRIBUTING.md) will help you get your development environment ready in minutes.
+Additional information on the license can be found in the [FAQ](https://docs.n8n.io/reference/faq.html#license)
diff --git a/packages/cli/package.json b/packages/cli/package.json
index aa48f92da2..252994f485 100644
--- a/packages/cli/package.json
+++ b/packages/cli/package.json
@@ -1,6 +1,6 @@
{
"name": "n8n",
- "version": "0.118.0",
+ "version": "0.121.0",
"description": "n8n Workflow Automation Tool",
"license": "SEE LICENSE IN LICENSE.md",
"homepage": "https://n8n.io",
@@ -55,6 +55,7 @@
"devDependencies": {
"@oclif/dev-cli": "^1.22.2",
"@types/basic-auth": "^1.1.2",
+ "@types/bcryptjs": "^2.4.2",
"@types/bull": "^3.3.10",
"@types/compression": "1.0.1",
"@types/connect-history-api-fallback": "^1.3.1",
@@ -79,11 +80,11 @@
"typescript": "~3.9.7"
},
"dependencies": {
- "@node-rs/bcrypt": "^1.2.0",
"@oclif/command": "^1.5.18",
"@oclif/errors": "^1.2.2",
"@types/jsonwebtoken": "^8.3.4",
"basic-auth": "^2.0.1",
+ "bcryptjs": "^2.4.3",
"body-parser": "^1.18.3",
"body-parser-xml": "^1.1.0",
"bull": "^3.19.0",
@@ -104,10 +105,10 @@
"localtunnel": "^2.0.0",
"lodash.get": "^4.4.2",
"mysql2": "~2.2.0",
- "n8n-core": "~0.69.0",
- "n8n-editor-ui": "~0.88.0",
- "n8n-nodes-base": "~0.115.0",
- "n8n-workflow": "~0.57.0",
+ "n8n-core": "~0.72.0",
+ "n8n-editor-ui": "~0.91.0",
+ "n8n-nodes-base": "~0.118.0",
+ "n8n-workflow": "~0.59.0",
"oauth-1.0a": "^2.2.6",
"open": "^7.0.0",
"pg": "^8.3.0",
diff --git a/packages/cli/src/ActiveWorkflowRunner.ts b/packages/cli/src/ActiveWorkflowRunner.ts
index 9f9b9f3580..60b10b5b08 100644
--- a/packages/cli/src/ActiveWorkflowRunner.ts
+++ b/packages/cli/src/ActiveWorkflowRunner.ts
@@ -35,7 +35,7 @@ import {
} from 'n8n-workflow';
import * as express from 'express';
-import {
+import {
LoggerProxy as Logger,
} from 'n8n-workflow';
@@ -67,15 +67,15 @@ export class ActiveWorkflowRunner {
for (const workflowData of workflowsData) {
console.log(` - ${workflowData.name}`);
- Logger.debug(`Initializing active workflow "${workflowData.name}" (startup)`, {workflowName: workflowData.name, workflowId: workflowData.id});
+ Logger.debug(`Initializing active workflow "${workflowData.name}" (startup)`, { workflowName: workflowData.name, workflowId: workflowData.id });
try {
await this.add(workflowData.id.toString(), 'init', workflowData);
- Logger.verbose(`Successfully started workflow "${workflowData.name}"`, {workflowName: workflowData.name, workflowId: workflowData.id});
+ Logger.verbose(`Successfully started workflow "${workflowData.name}"`, { workflowName: workflowData.name, workflowId: workflowData.id });
console.log(` => Started`);
} catch (error) {
console.log(` => ERROR: Workflow could not be activated:`);
console.log(` ${error.message}`);
- Logger.error(`Unable to initialize workflow "${workflowData.name}" (startup)`, {workflowName: workflowData.name, workflowId: workflowData.id});
+ Logger.error(`Unable to initialize workflow "${workflowData.name}" (startup)`, { workflowName: workflowData.name, workflowId: workflowData.id });
}
}
Logger.verbose('Finished initializing active workflows (startup)');
@@ -188,7 +188,7 @@ export class ActiveWorkflowRunner {
}
const nodeTypes = NodeTypes();
- const workflow = new Workflow({ id: webhook.workflowId.toString(), name: workflowData.name, nodes: workflowData.nodes, connections: workflowData.connections, active: workflowData.active, nodeTypes, staticData: workflowData.staticData, settings: workflowData.settings});
+ const workflow = new Workflow({ id: webhook.workflowId.toString(), name: workflowData.name, nodes: workflowData.nodes, connections: workflowData.connections, active: workflowData.active, nodeTypes, staticData: workflowData.staticData, settings: workflowData.settings });
const credentials = await WorkflowCredentials([workflow.getNode(webhook.node as string) as INode]);
@@ -225,8 +225,8 @@ export class ActiveWorkflowRunner {
* @returns {Promise}
* @memberof ActiveWorkflowRunner
*/
- async getWebhookMethods(path: string) : Promise {
- const webhooks = await Db.collections.Webhook?.find({ webhookPath: path}) as IWebhookDb[];
+ async getWebhookMethods(path: string): Promise {
+ const webhooks = await Db.collections.Webhook?.find({ webhookPath: path }) as IWebhookDb[];
// Gather all request methods in string array
const webhookMethods: string[] = webhooks.map(webhook => webhook.method);
@@ -463,7 +463,7 @@ export class ActiveWorkflowRunner {
* @returns {IGetExecuteTriggerFunctions}
* @memberof ActiveWorkflowRunner
*/
- getExecuteTriggerFunctions(workflowData: IWorkflowDb, additionalData: IWorkflowExecuteAdditionalDataWorkflow, mode: WorkflowExecuteMode, activation: WorkflowActivateMode): IGetExecuteTriggerFunctions{
+ getExecuteTriggerFunctions(workflowData: IWorkflowDb, additionalData: IWorkflowExecuteAdditionalDataWorkflow, mode: WorkflowExecuteMode, activation: WorkflowActivateMode): IGetExecuteTriggerFunctions {
return ((workflow: Workflow, node: INode) => {
const returnFunctions = NodeExecuteFunctions.getExecuteTriggerFunctions(workflow, node, additionalData, mode, activation);
returnFunctions.emit = (data: INodeExecutionData[][]): void => {
@@ -517,8 +517,8 @@ export class ActiveWorkflowRunner {
if (workflowInstance.getTriggerNodes().length !== 0
|| workflowInstance.getPollNodes().length !== 0) {
- await this.activeWorkflows.add(workflowId, workflowInstance, additionalData, mode, activation, getTriggerFunctions, getPollFunctions);
- Logger.info(`Successfully activated workflow "${workflowData.name}"`);
+ await this.activeWorkflows.add(workflowId, workflowInstance, additionalData, mode, activation, getTriggerFunctions, getPollFunctions);
+ Logger.verbose(`Successfully activated workflow "${workflowData.name}"`, { workflowId, workflowName: workflowData.name });
}
if (this.activationErrors[workflowId] !== undefined) {
@@ -569,7 +569,8 @@ export class ActiveWorkflowRunner {
// if it's active in memory then it's a trigger
// so remove from list of actives workflows
if (this.activeWorkflows.isActive(workflowId)) {
- this.activeWorkflows.remove(workflowId);
+ await this.activeWorkflows.remove(workflowId);
+ Logger.verbose(`Successfully deactivated workflow "${workflowId}"`, { workflowId });
}
return;
diff --git a/packages/cli/src/GenericHelpers.ts b/packages/cli/src/GenericHelpers.ts
index 8dba633baa..80b107f2be 100644
--- a/packages/cli/src/GenericHelpers.ts
+++ b/packages/cli/src/GenericHelpers.ts
@@ -11,22 +11,6 @@ import { IPackageVersions } from './';
let versionCache: IPackageVersions | undefined;
-/**
- * Displays a message to the user
- *
- * @export
- * @param {string} message The message to display
- * @param {string} [level='log']
- */
-export function logOutput(message: string, level = 'log'): void {
- if (level === 'log') {
- console.log(message);
- } else if (level === 'error') {
- console.error(message);
- }
-}
-
-
/**
* Returns the base URL n8n is reachable from
*
diff --git a/packages/cli/src/LoadNodesAndCredentials.ts b/packages/cli/src/LoadNodesAndCredentials.ts
index a69c6be33f..2b1263e6d0 100644
--- a/packages/cli/src/LoadNodesAndCredentials.ts
+++ b/packages/cli/src/LoadNodesAndCredentials.ts
@@ -4,11 +4,18 @@ import {
} from 'n8n-core';
import {
ICredentialType,
+ ILogger,
INodeType,
INodeTypeData,
+ LoggerProxy,
} from 'n8n-workflow';
import * as config from '../config';
+
+import {
+ getLogger,
+} from '../src/Logger';
+
import {
access as fsAccess,
readdir as fsReaddir,
@@ -31,7 +38,12 @@ class LoadNodesAndCredentialsClass {
nodeModulesPath = '';
+ logger: ILogger;
+
async init() {
+ this.logger = getLogger();
+ LoggerProxy.init(this.logger);
+
// Get the path to the node-modules folder to be later able
// to load the credentials and nodes
const checkPaths = [
@@ -171,6 +183,10 @@ class LoadNodesAndCredentialsClass {
tempNode.description.icon = 'file:' + path.join(path.dirname(filePath), tempNode.description.icon.substr(5));
}
+ if (tempNode.executeSingle) {
+ this.logger.warn(`"executeSingle" will get deprecated soon. Please update the code of node "${packageName}.${nodeName}" to use "execute" instead!`, { filePath });
+ }
+
if (this.includeNodes !== undefined && !this.includeNodes.includes(fullNodeName)) {
return;
}
diff --git a/packages/cli/src/Server.ts b/packages/cli/src/Server.ts
index cbe43697f3..ba4eb543d0 100644
--- a/packages/cli/src/Server.ts
+++ b/packages/cli/src/Server.ts
@@ -8,7 +8,6 @@ import {
resolve as pathResolve,
} from 'path';
import {
- getConnection,
getConnectionManager,
In,
} from 'typeorm';
@@ -22,7 +21,9 @@ import { RequestOptions } from 'oauth-1.0a';
import * as csrf from 'csrf';
import * as requestPromise from 'request-promise-native';
import { createHmac } from 'crypto';
-import { compare } from '@node-rs/bcrypt';
+// IMPORTANT! Do not switch to anther bcrypt library unless really necessary and
+// tested with all possible systems like Windows, Alpine on ARM, FreeBSD, ...
+import { compare } from 'bcryptjs';
import * as promClient from 'prom-client';
import {
@@ -572,6 +573,7 @@ class App {
const newWorkflowData = req.body as IWorkflowBase;
const id = req.params.id;
+ newWorkflowData.id = id;
await this.externalHooks.run('workflow.update', [newWorkflowData]);
@@ -716,6 +718,7 @@ class App {
// get generated dynamically
this.app.get(`/${this.restEndpoint}/node-parameter-options`, ResponseHelper.send(async (req: express.Request, res: express.Response): Promise => {
const nodeType = req.query.nodeType as string;
+ const path = req.query.path as string;
let credentials: INodeCredentials | undefined = undefined;
const currentNodeParameters = JSON.parse('' + req.query.currentNodeParameters) as INodeParameters;
if (req.query.credentials !== undefined) {
@@ -725,7 +728,7 @@ class App {
const nodeTypes = NodeTypes();
- const loadDataInstance = new LoadNodeParameterOptions(nodeType, nodeTypes, JSON.parse('' + req.query.currentNodeParameters), credentials!);
+ const loadDataInstance = new LoadNodeParameterOptions(nodeType, nodeTypes, path, JSON.parse('' + req.query.currentNodeParameters), credentials!);
const workflowData = loadDataInstance.getWorkflowData() as IWorkflowBase;
const workflowCredentials = await WorkflowCredentials(workflowData.nodes);
@@ -1732,6 +1735,7 @@ class App {
}
);
}
+ returnData.sort((a, b) => parseInt(b.id, 10) - parseInt(a.id, 10));
return returnData;
}
diff --git a/packages/cli/src/WorkflowRunnerProcess.ts b/packages/cli/src/WorkflowRunnerProcess.ts
index 14ae4a1abf..4a09c76a89 100644
--- a/packages/cli/src/WorkflowRunnerProcess.ts
+++ b/packages/cli/src/WorkflowRunnerProcess.ts
@@ -194,9 +194,9 @@ export class WorkflowRunnerProcess {
* @param {any[]} parameters
* @memberof WorkflowRunnerProcess
*/
- sendHookToParentProcess(hook: string, parameters: any[]) { // tslint:disable-line:no-any
+ async sendHookToParentProcess(hook: string, parameters: any[]) { // tslint:disable-line:no-any
try {
- sendToParentProcess('processHook', {
+ await sendToParentProcess('processHook', {
hook,
parameters,
});
@@ -217,22 +217,22 @@ export class WorkflowRunnerProcess {
const hookFunctions: IWorkflowExecuteHooks = {
nodeExecuteBefore: [
async (nodeName: string): Promise => {
- this.sendHookToParentProcess('nodeExecuteBefore', [nodeName]);
+ await this.sendHookToParentProcess('nodeExecuteBefore', [nodeName]);
},
],
nodeExecuteAfter: [
async (nodeName: string, data: ITaskData): Promise => {
- this.sendHookToParentProcess('nodeExecuteAfter', [nodeName, data]);
+ await this.sendHookToParentProcess('nodeExecuteAfter', [nodeName, data]);
},
],
workflowExecuteBefore: [
async (): Promise => {
- this.sendHookToParentProcess('workflowExecuteBefore', []);
+ await this.sendHookToParentProcess('workflowExecuteBefore', []);
},
],
workflowExecuteAfter: [
async (fullRunData: IRun, newStaticData?: IDataObject): Promise => {
- this.sendHookToParentProcess('workflowExecuteAfter', [fullRunData, newStaticData]);
+ await this.sendHookToParentProcess('workflowExecuteAfter', [fullRunData, newStaticData]);
},
],
};
diff --git a/packages/cli/src/databases/mysqldb/migrations/1620729500000-ChangeCredentialDataSize.ts b/packages/cli/src/databases/mysqldb/migrations/1620729500000-ChangeCredentialDataSize.ts
new file mode 100644
index 0000000000..ea1bd42049
--- /dev/null
+++ b/packages/cli/src/databases/mysqldb/migrations/1620729500000-ChangeCredentialDataSize.ts
@@ -0,0 +1,18 @@
+import { MigrationInterface, QueryRunner } from 'typeorm';
+import * as config from '../../../../config';
+
+export class ChangeCredentialDataSize1620729500000 implements MigrationInterface {
+ name = 'ChangeCredentialDataSize1620729500000';
+
+ async up(queryRunner: QueryRunner): Promise {
+ const tablePrefix = config.get('database.tablePrefix');
+
+ await queryRunner.query('ALTER TABLE `' + tablePrefix + 'credentials_entity` MODIFY COLUMN `type` varchar(128) NOT NULL');
+ }
+
+ async down(queryRunner: QueryRunner): Promise {
+ const tablePrefix = config.get('database.tablePrefix');
+
+ await queryRunner.query('ALTER TABLE `' + tablePrefix + 'credentials_entity` MODIFY COLUMN `type` varchar(32) NOT NULL');
+ }
+}
diff --git a/packages/cli/src/databases/mysqldb/migrations/index.ts b/packages/cli/src/databases/mysqldb/migrations/index.ts
index 08ca45edbf..f5c110fec2 100644
--- a/packages/cli/src/databases/mysqldb/migrations/index.ts
+++ b/packages/cli/src/databases/mysqldb/migrations/index.ts
@@ -4,6 +4,7 @@ import { CreateIndexStoppedAt1594902918301 } from './1594902918301-CreateIndexSt
import { AddWebhookId1611149998770 } from './1611149998770-AddWebhookId';
import { MakeStoppedAtNullable1607431743767 } from './1607431743767-MakeStoppedAtNullable';
import { ChangeDataSize1615306975123 } from './1615306975123-ChangeDataSize';
+import { ChangeCredentialDataSize1620729500000 } from './1620729500000-ChangeCredentialDataSize';
export const mysqlMigrations = [
InitialMigration1588157391238,
@@ -12,4 +13,5 @@ export const mysqlMigrations = [
AddWebhookId1611149998770,
MakeStoppedAtNullable1607431743767,
ChangeDataSize1615306975123,
+ ChangeCredentialDataSize1620729500000,
];
diff --git a/packages/core/package.json b/packages/core/package.json
index 69a76944f1..26f3b8b9d2 100644
--- a/packages/core/package.json
+++ b/packages/core/package.json
@@ -1,6 +1,6 @@
{
"name": "n8n-core",
- "version": "0.69.0",
+ "version": "0.72.0",
"description": "Core functionality of n8n",
"license": "SEE LICENSE IN LICENSE.md",
"homepage": "https://n8n.io",
@@ -47,7 +47,7 @@
"file-type": "^14.6.2",
"lodash.get": "^4.4.2",
"mime-types": "^2.1.27",
- "n8n-workflow": "~0.57.0",
+ "n8n-workflow": "~0.59.0",
"oauth-1.0a": "^2.2.6",
"p-cancelable": "^2.0.0",
"request": "^2.88.2",
diff --git a/packages/core/src/LoadNodeParameterOptions.ts b/packages/core/src/LoadNodeParameterOptions.ts
index 6a9b457503..d112f63bfd 100644
--- a/packages/core/src/LoadNodeParameterOptions.ts
+++ b/packages/core/src/LoadNodeParameterOptions.ts
@@ -18,10 +18,12 @@ const TEMP_WORKFLOW_NAME = 'Temp-Workflow';
export class LoadNodeParameterOptions {
+ path: string;
workflow: Workflow;
- constructor(nodeTypeName: string, nodeTypes: INodeTypes, currentNodeParameters: INodeParameters, credentials?: INodeCredentials) {
+ constructor(nodeTypeName: string, nodeTypes: INodeTypes, path: string, currentNodeParameters: INodeParameters, credentials?: INodeCredentials) {
+ this.path = path;
const nodeType = nodeTypes.getByName(nodeTypeName);
if (nodeType === undefined) {
@@ -89,7 +91,7 @@ export class LoadNodeParameterOptions {
throw new Error(`The node-type "${node!.type}" does not have the method "${methodName}" defined!`);
}
- const thisArgs = NodeExecuteFunctions.getLoadOptionsFunctions(this.workflow, node!, additionalData);
+ const thisArgs = NodeExecuteFunctions.getLoadOptionsFunctions(this.workflow, node!, this.path, additionalData);
return nodeType!.methods.loadOptions[methodName].call(thisArgs);
}
diff --git a/packages/core/src/NodeExecuteFunctions.ts b/packages/core/src/NodeExecuteFunctions.ts
index aeeb39c810..36004fd10c 100644
--- a/packages/core/src/NodeExecuteFunctions.ts
+++ b/packages/core/src/NodeExecuteFunctions.ts
@@ -691,7 +691,7 @@ export function getExecuteFunctions(workflow: Workflow, runExecutionData: IRunEx
return continueOnFail(node);
},
evaluateExpression: (expression: string, itemIndex: number) => {
- return workflow.expression.resolveSimpleParameterValue('=' + expression, runExecutionData, runIndex, itemIndex, node.name, connectionInputData, mode);
+ return workflow.expression.resolveSimpleParameterValue('=' + expression, {}, runExecutionData, runIndex, itemIndex, node.name, connectionInputData, mode);
},
async executeWorkflow(workflowInfo: IExecuteWorkflowInfo, inputData?: INodeExecutionData[]): Promise { // tslint:disable-line:no-any
return additionalData.executeWorkflow(workflowInfo, additionalData, inputData);
@@ -742,7 +742,7 @@ export function getExecuteFunctions(workflow: Workflow, runExecutionData: IRunEx
return getWorkflowMetadata(workflow);
},
getWorkflowDataProxy: (itemIndex: number): IWorkflowDataProxyData => {
- const dataProxy = new WorkflowDataProxy(workflow, runExecutionData, runIndex, itemIndex, node.name, connectionInputData, mode);
+ const dataProxy = new WorkflowDataProxy(workflow, runExecutionData, runIndex, itemIndex, node.name, connectionInputData, {}, mode);
return dataProxy.getDataProxy();
},
getWorkflowStaticData(type: string): IDataObject {
@@ -789,7 +789,7 @@ export function getExecuteSingleFunctions(workflow: Workflow, runExecutionData:
},
evaluateExpression: (expression: string, evaluateItemIndex: number | undefined) => {
evaluateItemIndex = evaluateItemIndex === undefined ? itemIndex : evaluateItemIndex;
- return workflow.expression.resolveSimpleParameterValue('=' + expression, runExecutionData, runIndex, evaluateItemIndex, node.name, connectionInputData, mode);
+ return workflow.expression.resolveSimpleParameterValue('=' + expression, {}, runExecutionData, runIndex, evaluateItemIndex, node.name, connectionInputData, mode);
},
getContext(type: string): IContextObject {
return NodeHelpers.getContext(runExecutionData, type, node);
@@ -841,7 +841,7 @@ export function getExecuteSingleFunctions(workflow: Workflow, runExecutionData:
return getWorkflowMetadata(workflow);
},
getWorkflowDataProxy: (): IWorkflowDataProxyData => {
- const dataProxy = new WorkflowDataProxy(workflow, runExecutionData, runIndex, itemIndex, node.name, connectionInputData, mode);
+ const dataProxy = new WorkflowDataProxy(workflow, runExecutionData, runIndex, itemIndex, node.name, connectionInputData, {}, mode);
return dataProxy.getDataProxy();
},
getWorkflowStaticData(type: string): IDataObject {
@@ -871,18 +871,20 @@ export function getExecuteSingleFunctions(workflow: Workflow, runExecutionData:
* @param {IWorkflowExecuteAdditionalData} additionalData
* @returns {ILoadOptionsFunctions}
*/
-export function getLoadOptionsFunctions(workflow: Workflow, node: INode, additionalData: IWorkflowExecuteAdditionalData): ILoadOptionsFunctions {
- return ((workflow: Workflow, node: INode) => {
+export function getLoadOptionsFunctions(workflow: Workflow, node: INode, path: string, additionalData: IWorkflowExecuteAdditionalData): ILoadOptionsFunctions {
+ return ((workflow: Workflow, node: INode, path: string) => {
const that = {
getCredentials(type: string): ICredentialDataDecryptedObject | undefined {
return getCredentials(workflow, node, type, additionalData, 'internal');
},
- getCurrentNodeParameter: (parameterName: string): NodeParameterValue | INodeParameters | NodeParameterValue[] | INodeParameters[] | object | undefined => {
+ getCurrentNodeParameter: (parameterPath: string): NodeParameterValue | INodeParameters | NodeParameterValue[] | INodeParameters[] | object | undefined => {
const nodeParameters = additionalData.currentNodeParameters;
- if (nodeParameters && nodeParameters[parameterName]) {
- return nodeParameters[parameterName];
+
+ if (parameterPath.charAt(0) === '&') {
+ parameterPath = `${path.split('.').slice(1, -1).join('.')}.${parameterPath.slice(1)}`;
}
- return undefined;
+
+ return get(nodeParameters, parameterPath);
},
getCurrentNodeParameters: (): INodeParameters | undefined => {
return additionalData.currentNodeParameters;
@@ -915,7 +917,7 @@ export function getLoadOptionsFunctions(workflow: Workflow, node: INode, additio
},
};
return that;
- })(workflow, node);
+ })(workflow, node, path);
}
diff --git a/packages/editor-ui/package.json b/packages/editor-ui/package.json
index ccf591fe6b..b86a1f9a4e 100644
--- a/packages/editor-ui/package.json
+++ b/packages/editor-ui/package.json
@@ -1,6 +1,6 @@
{
"name": "n8n-editor-ui",
- "version": "0.88.0",
+ "version": "0.91.0",
"description": "Workflow Editor UI for n8n",
"license": "SEE LICENSE IN LICENSE.md",
"homepage": "https://n8n.io",
@@ -65,7 +65,7 @@
"lodash.debounce": "^4.0.8",
"lodash.get": "^4.4.2",
"lodash.set": "^4.3.2",
- "n8n-workflow": "~0.57.0",
+ "n8n-workflow": "~0.59.0",
"node-sass": "^4.12.0",
"normalize-wheel": "^1.0.1",
"prismjs": "^1.17.1",
diff --git a/packages/editor-ui/src/Interface.ts b/packages/editor-ui/src/Interface.ts
index 1ac72fd90a..4fdbd770f8 100644
--- a/packages/editor-ui/src/Interface.ts
+++ b/packages/editor-ui/src/Interface.ts
@@ -131,7 +131,7 @@ export interface IRestApi {
getSettings(): Promise;
getNodeTypes(): Promise;
getNodesInformation(nodeList: string[]): Promise;
- getNodeParameterOptions(nodeType: string, methodName: string, currentNodeParameters: INodeParameters, credentials?: INodeCredentials): Promise;
+ getNodeParameterOptions(nodeType: string, path: string, methodName: string, currentNodeParameters: INodeParameters, credentials?: INodeCredentials): Promise;
removeTestWebhook(workflowId: string): Promise;
runWorkflow(runData: IStartRunData): Promise;
createNewWorkflow(sendData: IWorkflowData): Promise;
@@ -444,4 +444,4 @@ export interface ILinkMenuItemProperties {
icon: string;
href: string;
newWindow?: boolean;
-}
\ No newline at end of file
+}
diff --git a/packages/editor-ui/src/components/ExecutionsList.vue b/packages/editor-ui/src/components/ExecutionsList.vue
index d576c6898b..e0e129a737 100644
--- a/packages/editor-ui/src/components/ExecutionsList.vue
+++ b/packages/editor-ui/src/components/ExecutionsList.vue
@@ -438,7 +438,8 @@ export default mixins(
this.$store.commit('setActiveExecutions', results[1]);
- const alreadyPresentExecutionIds = this.finishedExecutions.map(exec => exec.id);
+ // execution IDs are typed as string, int conversion is necessary so we can order.
+ const alreadyPresentExecutionIds = this.finishedExecutions.map(exec => parseInt(exec.id, 10));
let lastId = 0;
const gaps = [] as number[];
for(let i = results[0].results.length - 1; i >= 0; i--) {
@@ -459,7 +460,7 @@ export default mixins(
// Check new results from end to start
// Add new items accordingly.
- const executionIndex = alreadyPresentExecutionIds.indexOf(currentItem.id);
+ const executionIndex = alreadyPresentExecutionIds.indexOf(currentId);
if (executionIndex !== -1) {
// Execution that we received is already present.
@@ -477,7 +478,7 @@ export default mixins(
// Find the correct position to place this newcomer
let j;
for (j = this.finishedExecutions.length - 1; j >= 0; j--) {
- if (currentItem.id < this.finishedExecutions[j].id) {
+ if (currentId < parseInt(this.finishedExecutions[j].id, 10)) {
this.finishedExecutions.splice(j + 1, 0, currentItem);
break;
}
diff --git a/packages/editor-ui/src/components/ExpressionEdit.vue b/packages/editor-ui/src/components/ExpressionEdit.vue
index ada71f6869..c073ce7793 100644
--- a/packages/editor-ui/src/components/ExpressionEdit.vue
+++ b/packages/editor-ui/src/components/ExpressionEdit.vue
@@ -30,7 +30,7 @@
Result
-
+
@@ -52,7 +52,11 @@ import {
Workflow,
} from 'n8n-workflow';
-export default Vue.extend({
+import { externalHooks } from '@/components/mixins/externalHooks';
+
+import mixins from 'vue-typed-mixins';
+
+export default mixins(externalHooks).extend({
name: 'ExpressionEdit',
props: [
'dialogVisible',
@@ -81,7 +85,16 @@ export default Vue.extend({
},
itemSelected (eventData: IVariableItemSelected) {
+ // User inserted item from Expression Editor variable selector
(this.$refs.inputFieldExpression as any).itemSelected(eventData); // tslint:disable-line:no-any
+
+ this.$externalHooks().run('expressionEdit.itemSelected', { parameter: this.parameter, value: this.value, selectedItem: eventData });
+ },
+ },
+ watch: {
+ dialogVisible (newValue) {
+ const resolvedExpressionValue = this.$refs.expressionResult && (this.$refs.expressionResult as any).getValue() || undefined; // tslint:disable-line:no-any
+ this.$externalHooks().run('expressionEdit.dialogVisibleChanged', { dialogVisible: newValue, parameter: this.parameter, value: this.value, resolvedExpressionValue });
},
},
});
diff --git a/packages/editor-ui/src/components/FixedCollectionParameter.vue b/packages/editor-ui/src/components/FixedCollectionParameter.vue
index 9588e51a80..2d10f5b3ad 100644
--- a/packages/editor-ui/src/components/FixedCollectionParameter.vue
+++ b/packages/editor-ui/src/components/FixedCollectionParameter.vue
@@ -177,7 +177,11 @@ export default mixins(genericHelpers)
} else if (optionParameter.typeOptions !== undefined && optionParameter.typeOptions.multipleValues === true) {
// Multiple values are allowed so append option to array
newParameterValue[optionParameter.name] = get(this.nodeValues, `${this.path}.${optionParameter.name}`, []);
- (newParameterValue[optionParameter.name] as INodeParameters[]).push(JSON.parse(JSON.stringify(optionParameter.default)));
+ if (Array.isArray(optionParameter.default)) {
+ (newParameterValue[optionParameter.name] as INodeParameters[]).push(...JSON.parse(JSON.stringify(optionParameter.default)));
+ } else if (optionParameter.default !== '' && typeof optionParameter.default !== 'object') {
+ (newParameterValue[optionParameter.name] as INodeParameters[]).push(JSON.parse(JSON.stringify(optionParameter.default)));
+ }
} else {
// Add a new option
newParameterValue[optionParameter.name] = JSON.parse(JSON.stringify(optionParameter.default));
diff --git a/packages/editor-ui/src/components/NodeSettings.vue b/packages/editor-ui/src/components/NodeSettings.vue
index 07f8ac7b2d..303b1fcb26 100644
--- a/packages/editor-ui/src/components/NodeSettings.vue
+++ b/packages/editor-ui/src/components/NodeSettings.vue
@@ -37,9 +37,6 @@