From d073204bb0a7b62959e58b91ef49bcb5d6321b33 Mon Sep 17 00:00:00 2001 From: Jan Oberhauser Date: Thu, 31 Oct 2019 17:24:56 +0100 Subject: [PATCH 01/25] :sparkles: Add MoveBinaryData-Node to move data between binary an JSON --- packages/editor-ui/src/main.ts | 2 + .../nodes-base/nodes/MoveBinaryData.node.ts | 320 ++++++++++++++++++ packages/nodes-base/package.json | 1 + 3 files changed, 323 insertions(+) create mode 100644 packages/nodes-base/nodes/MoveBinaryData.node.ts diff --git a/packages/editor-ui/src/main.ts b/packages/editor-ui/src/main.ts index 1784368ded..9cfa172e33 100644 --- a/packages/editor-ui/src/main.ts +++ b/packages/editor-ui/src/main.ts @@ -40,6 +40,7 @@ import { faEye, faExclamationTriangle, faExternalLinkAlt, + faExchangeAlt, faFile, faFileCode, faFileDownload, @@ -111,6 +112,7 @@ library.add(faEnvelope); library.add(faEye); library.add(faExclamationTriangle); library.add(faExternalLinkAlt); +library.add(faExchangeAlt); library.add(faFile); library.add(faFileCode); library.add(faFileDownload); diff --git a/packages/nodes-base/nodes/MoveBinaryData.node.ts b/packages/nodes-base/nodes/MoveBinaryData.node.ts new file mode 100644 index 0000000000..3fb9c936d8 --- /dev/null +++ b/packages/nodes-base/nodes/MoveBinaryData.node.ts @@ -0,0 +1,320 @@ +import { + get, + set, + unset, +} from 'lodash'; + +import { IExecuteFunctions } from 'n8n-core'; +import { + IDataObject, + INodeExecutionData, + INodeType, + INodeTypeDescription, +} from 'n8n-workflow'; + + +export class MoveBinaryData implements INodeType { + description: INodeTypeDescription = { + displayName: 'Move Binary Data', + name: 'moveBinaryData', + icon: 'fa:exchange-alt', + group: ['transform'], + version: 1, + subtitle: '={{$parameter["mode"]==="binaryToJson" ? "Binary to JSON" : "JSON to Binary"}}', + description: 'Move data between binary and JSON properties.', + defaults: { + name: 'Move Binary Data', + color: '#7722CC', + }, + inputs: ['main'], + outputs: ['main'], + properties: [ + { + displayName: 'Mode', + name: 'mode', + type: 'options', + options: [ + { + name: 'Binary to JSON', + value: 'binaryToJson', + description: 'Move data from Binary to JSON', + }, + { + name: 'JSON to Binary', + value: 'jsonToBinary', + description: 'Move data from JSON to Binary.', + }, + ], + default: 'binaryToJson', + description: 'From and to where data should be moved.', + }, + + + // ---------------------------------- + // binaryToJson + // ---------------------------------- + { + displayName: 'Set all Data', + name: 'setAllData', + type: 'boolean', + displayOptions: { + show: { + mode: [ + 'binaryToJson', + ], + }, + }, + default: true, + description: 'If all JSON data should be replaced with the data retrieved
from binary key. Else the data will be written to a single key.', + }, + { + displayName: 'Source Key', + name: 'sourceKey', + type: 'string', + displayOptions: { + show: { + mode: [ + 'binaryToJson', + ], + }, + }, + default: 'data', + required: true, + placeholder: 'data', + description: 'The name of the binary key to get data from.
It is also possible to define deep keys by using dot-notation like for example:
"level1.level2.currentKey"', + }, + { + displayName: 'Destination Key', + name: 'destinationKey', + type: 'string', + displayOptions: { + show: { + mode: [ + 'binaryToJson', + ], + setAllData: [ + false, + ], + }, + }, + default: 'data', + required: true, + placeholder: '', + description: 'The name the JSON key to copy data to. It is also possible
to define deep keys by using dot-notation like for example:
"level1.level2.newKey"', + }, + + // ---------------------------------- + // jsonToBinary + // ---------------------------------- + { + displayName: 'Convert all Data', + name: 'convertAllData', + type: 'boolean', + displayOptions: { + show: { + mode: [ + 'jsonToBinary', + ], + }, + }, + default: true, + description: 'If all JSON data should be converted to binary.
Else only the data of one key will be converted.', + }, + { + displayName: 'Source Key', + name: 'sourceKey', + type: 'string', + displayOptions: { + show: { + convertAllData: [ + false, + ], + mode: [ + 'jsonToBinary', + ], + }, + }, + default: 'data', + required: true, + placeholder: 'data', + description: 'The name of the JSON key to get data from. It is also possible
to define deep keys by using dot-notation like for example:
"level1.level2.currentKey"', + }, + { + displayName: 'Destination Key', + name: 'destinationKey', + type: 'string', + displayOptions: { + show: { + mode: [ + 'jsonToBinary', + ], + }, + }, + default: 'data', + required: true, + placeholder: 'data', + description: 'The name the binary key to copy data to. It is also possible
to define deep keys by using dot-notation like for example:
"level1.level2.newKey"', + }, + + { + displayName: 'Options', + name: 'options', + type: 'collection', + placeholder: 'Add Option', + default: {}, + options: [ + { + displayName: 'Keep Source', + name: 'keepSource', + type: 'boolean', + default: false, + description: 'If the source key should be kept. By default does it get deleted.', + }, + { + displayName: 'Mime Type', + name: 'mimeType', + type: 'string', + displayOptions: { + show: { + '/mode': [ + 'jsonToBinary', + ], + }, + }, + default: 'application/json', + placeholder: 'application/json', + description: 'The mime-type to set. By default will the mime-type for JSON be set.', + }, + { + displayName: 'Use Raw Data', + name: 'useRawData', + type: 'boolean', + displayOptions: { + show: { + '/mode': [ + 'jsonToBinary', + ], + }, + }, + default: false, + description: 'Use data as is and do not JSON.stringify it.', + }, + ], + } + ], + }; + + async execute(this: IExecuteFunctions): Promise { + + const items = this.getInputData(); + + const mode = this.getNodeParameter('mode', 0) as string; + + const returnData: INodeExecutionData[] = []; + + let item: INodeExecutionData; + let newItem: INodeExecutionData; + let options: IDataObject; + for (let itemIndex = 0; itemIndex < items.length; itemIndex++) { + item = items[itemIndex]; + options = this.getNodeParameter('options', 0, {}) as IDataObject; + + // Copy the whole JSON data as data on any level can be renamed + newItem = { + json: {}, + }; + + if (mode === 'binaryToJson') { + const setAllData = this.getNodeParameter('setAllData', itemIndex) as boolean; + const sourceKey = this.getNodeParameter('sourceKey', itemIndex) as string; + + const value = get(item.binary, sourceKey); + + if (value === undefined) { + // No data found so skip + continue; + } + + const convertedValue = JSON.parse(new Buffer(value.data, 'base64').toString('ascii')); + + if (setAllData === true) { + // Set the full data + newItem.json = convertedValue; + } else { + // Does get added to existing data so copy it first + newItem.json = JSON.parse(JSON.stringify(item.json)); + + const destinationKey = this.getNodeParameter('destinationKey', itemIndex, '') as string; + set(newItem.json, destinationKey, convertedValue); + } + + if (options.keepSource === true) { + // Binary data does not get touched so simply reference it + newItem.binary = item.binary; + } else { + // Binary data will change so copy it + newItem.binary = JSON.parse(JSON.stringify(item.binary)); + unset(newItem.binary, sourceKey); + } + + } else if (mode === 'jsonToBinary') { + const convertAllData = this.getNodeParameter('convertAllData', itemIndex) as boolean; + const destinationKey = this.getNodeParameter('destinationKey', itemIndex) as string; + + let value: IDataObject | string = item.json; + if (convertAllData === false) { + const sourceKey = this.getNodeParameter('sourceKey', itemIndex) as string; + value = get(item.json, sourceKey) as IDataObject; + } + + if (value === undefined) { + // No data found so skip + continue; + } + + if (item.binary !== undefined) { + // Item already has binary data so copy it + newItem.binary = JSON.parse(JSON.stringify(item.binary)); + } else { + // Item does not have binary data yet so initialize empty + newItem.binary = {}; + } + + if (options.useRawData !== true) { + value = JSON.stringify(value); + } + + const convertedValue = { + data: new Buffer(value as string).toString('base64'), + mimeType: options.mimeType || 'application/json', + }; + set(newItem.binary!, destinationKey, convertedValue); + + if (options.keepSource === true) { + // JSON data does not get touched so simply reference it + newItem.json = item.json; + } else { + // JSON data will change so copy it + + if (convertAllData === true) { + // Data should not be kept and all data got converted. So simply set new as empty + newItem.json = {}; + } else { + // Data should not be kept and only one key has to get removed. So copy all + // data and then remove the not needed one + newItem.json = JSON.parse(JSON.stringify(item.json)); + const sourceKey = this.getNodeParameter('sourceKey', itemIndex) as string; + + unset(newItem.json, sourceKey); + } + } + } else { + throw new Error(`The operation "${mode}" is not known!`); + } + + returnData.push(newItem); + } + + return [returnData]; + } +} diff --git a/packages/nodes-base/package.json b/packages/nodes-base/package.json index 4adb05ac46..3ab0854948 100644 --- a/packages/nodes-base/package.json +++ b/packages/nodes-base/package.json @@ -89,6 +89,7 @@ "dist/nodes/Mailgun/Mailgun.node.js", "dist/nodes/Mattermost/Mattermost.node.js", "dist/nodes/Merge.node.js", + "dist/nodes/MoveBinaryData.node", "dist/nodes/MongoDb/MongoDb.node.js", "dist/nodes/NextCloud/NextCloud.node.js", "dist/nodes/NoOp.node.js", From 6a88484d467c71b38b87a70fa809713a2a6cb7e9 Mon Sep 17 00:00:00 2001 From: Jan Oberhauser Date: Thu, 31 Oct 2019 17:26:05 +0100 Subject: [PATCH 02/25] :zap: Change default parameter value --- packages/nodes-base/nodes/GraphQL/GraphQL.node.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/nodes-base/nodes/GraphQL/GraphQL.node.ts b/packages/nodes-base/nodes/GraphQL/GraphQL.node.ts index 85ea50402c..7fb9b6c0b1 100644 --- a/packages/nodes-base/nodes/GraphQL/GraphQL.node.ts +++ b/packages/nodes-base/nodes/GraphQL/GraphQL.node.ts @@ -144,7 +144,7 @@ export class GraphQL implements INodeType { displayName: 'Response Data Property Name', name: 'dataPropertyName', type: 'string', - default: 'response', + default: 'data', required: true, displayOptions: { show: { From 78af0362312b4113ba65e93cde363b2c66cbeef6 Mon Sep 17 00:00:00 2001 From: Jan Oberhauser Date: Thu, 31 Oct 2019 17:43:30 +0100 Subject: [PATCH 03/25] :zap: Close Postgres-Databank connection once node is done --- packages/nodes-base/nodes/Postgres/Postgres.node.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/packages/nodes-base/nodes/Postgres/Postgres.node.ts b/packages/nodes-base/nodes/Postgres/Postgres.node.ts index e4c5ca8252..eed3b50cf3 100644 --- a/packages/nodes-base/nodes/Postgres/Postgres.node.ts +++ b/packages/nodes-base/nodes/Postgres/Postgres.node.ts @@ -285,6 +285,9 @@ export class Postgres implements INodeType { throw new Error(`The operation "${operation}" is not supported!`); } + // Close the connection + await pgp.end(); + return this.prepareOutputData(returnItems); } } From 09b339f01988dde6be314a0ba98c83c75a03053d Mon Sep 17 00:00:00 2001 From: Jan Oberhauser Date: Thu, 31 Oct 2019 20:16:20 +0100 Subject: [PATCH 04/25] :zap: Make it possible to return all matches in with GoogleSheet Lookup --- packages/nodes-base/nodes/Google/GoogleSheet.ts | 12 +++++++++--- .../nodes-base/nodes/Google/GoogleSheets.node.ts | 16 +++++++++++++++- 2 files changed, 24 insertions(+), 4 deletions(-) diff --git a/packages/nodes-base/nodes/Google/GoogleSheet.ts b/packages/nodes-base/nodes/Google/GoogleSheet.ts index 678ffdbbd2..58392be54f 100644 --- a/packages/nodes-base/nodes/Google/GoogleSheet.ts +++ b/packages/nodes-base/nodes/Google/GoogleSheet.ts @@ -316,10 +316,11 @@ export class GoogleSheet { * @param {number} keyRowIndex Index of the row which contains the keys * @param {number} dataStartRowIndex Index of the first row which contains data * @param {ILookupValues[]} lookupValues The lookup values which decide what data to return + * @param {boolean} [returnAllMatches] Returns all the found matches instead of only the first one * @returns {Promise} * @memberof GoogleSheet */ - async lookupValues(inputData: string[][], keyRowIndex: number, dataStartRowIndex: number, lookupValues: ILookupValues[]): Promise { + async lookupValues(inputData: string[][], keyRowIndex: number, dataStartRowIndex: number, lookupValues: ILookupValues[], returnAllMatches?: boolean): Promise { const keys: string[] = []; if (keyRowIndex < 0 || dataStartRowIndex < keyRowIndex || keyRowIndex >= inputData.length) { @@ -351,13 +352,18 @@ export class GoogleSheet { for (rowIndex = dataStartRowIndex; rowIndex < inputData.length; rowIndex++) { if (inputData[rowIndex][returnColumnIndex].toString() === lookupValue.lookupValue.toString()) { returnData.push(inputData[rowIndex]); - continue lookupLoop; + + if (returnAllMatches !== true) { + continue lookupLoop; + } } } // If value could not be found add an empty one that the order of // the returned items stays the same - returnData.push([]); + if (returnAllMatches !== true) { + returnData.push([]); + } } return this.structureData(returnData, 1, keys, true); diff --git a/packages/nodes-base/nodes/Google/GoogleSheets.node.ts b/packages/nodes-base/nodes/Google/GoogleSheets.node.ts index c6f0ca0d55..da7d070214 100644 --- a/packages/nodes-base/nodes/Google/GoogleSheets.node.ts +++ b/packages/nodes-base/nodes/Google/GoogleSheets.node.ts @@ -267,6 +267,20 @@ export class GoogleSheets implements INodeType { placeholder: 'Add Option', default: {}, options: [ + { + displayName: 'Return All Matches', + name: 'returnAllMatches', + type: 'boolean', + default: false, + displayOptions: { + show: { + '/operation': [ + 'lookup', + ], + }, + }, + description: 'By default only the first result gets returned. If options gets set all found matches get returned.', + }, { displayName: 'Value Input Mode', name: 'valueInputMode', @@ -436,7 +450,7 @@ export class GoogleSheets implements INodeType { }); } - const returnData = await sheet.lookupValues(sheetData, keyRow, dataStartRow, lookupValues); + const returnData = await sheet.lookupValues(sheetData, keyRow, dataStartRow, lookupValues, options.returnAllMatches as boolean | undefined); return [this.helpers.returnJsonArray(returnData)]; } else if (operation === 'read') { From 95aa0590b205eaf0fdce20151843ac75d5a0bc61 Mon Sep 17 00:00:00 2001 From: Jan Oberhauser Date: Thu, 31 Oct 2019 20:19:55 +0100 Subject: [PATCH 05/25] :bookmark: Release n8n-nodes-base@0.26.0 --- packages/nodes-base/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/nodes-base/package.json b/packages/nodes-base/package.json index 3ab0854948..571fa15e09 100644 --- a/packages/nodes-base/package.json +++ b/packages/nodes-base/package.json @@ -1,6 +1,6 @@ { "name": "n8n-nodes-base", - "version": "0.25.0", + "version": "0.26.0", "description": "Base nodes of n8n", "license": "SEE LICENSE IN LICENSE.md", "homepage": "https://n8n.io", From 729cdedcabb0723c2fab706ca063b6a95d0012aa Mon Sep 17 00:00:00 2001 From: Jan Oberhauser Date: Thu, 31 Oct 2019 20:22:09 +0100 Subject: [PATCH 06/25] :bookmark: Release n8n-editor-ui@0.24.0 --- packages/editor-ui/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/editor-ui/package.json b/packages/editor-ui/package.json index 7047295fba..911257526e 100644 --- a/packages/editor-ui/package.json +++ b/packages/editor-ui/package.json @@ -1,6 +1,6 @@ { "name": "n8n-editor-ui", - "version": "0.23.0", + "version": "0.24.0", "description": "Workflow Editor UI for n8n", "license": "SEE LICENSE IN LICENSE.md", "homepage": "https://n8n.io", From e2718e3cc391258a6b7e19856ab1c41ecfdf50b3 Mon Sep 17 00:00:00 2001 From: Jan Oberhauser Date: Thu, 31 Oct 2019 20:26:00 +0100 Subject: [PATCH 07/25] :arrow_up: Set n8n-editor-ui@0.24.0 and n8n-nodes-base@0.26.0 on n8n --- packages/cli/package.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/cli/package.json b/packages/cli/package.json index 59c5ea5435..20a611f673 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -90,8 +90,8 @@ "localtunnel": "^1.9.1", "mongodb": "^3.2.3", "n8n-core": "~0.14.0", - "n8n-editor-ui": "~0.23.0", - "n8n-nodes-base": "~0.25.0", + "n8n-editor-ui": "~0.24.0", + "n8n-nodes-base": "~0.26.0", "n8n-workflow": "~0.15.0", "open": "^6.1.0", "pg": "^7.11.0", From 7b0b71960d7caf39e2ca04da957a9deb10c8b2b8 Mon Sep 17 00:00:00 2001 From: Jan Oberhauser Date: Thu, 31 Oct 2019 20:28:09 +0100 Subject: [PATCH 08/25] :bookmark: Release n8n@0.31.0 --- packages/cli/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/cli/package.json b/packages/cli/package.json index 20a611f673..3a96c347f9 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -1,6 +1,6 @@ { "name": "n8n", - "version": "0.30.0", + "version": "0.31.0", "description": "n8n Workflow Automation Tool", "license": "SEE LICENSE IN LICENSE.md", "homepage": "https://n8n.io", From 547cd4a9ce3b0063886985935ce8e36a07d01469 Mon Sep 17 00:00:00 2001 From: Jan Oberhauser Date: Thu, 31 Oct 2019 22:33:30 +0100 Subject: [PATCH 09/25] :bug: Fix typo --- packages/nodes-base/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/nodes-base/package.json b/packages/nodes-base/package.json index 571fa15e09..95a76fddd8 100644 --- a/packages/nodes-base/package.json +++ b/packages/nodes-base/package.json @@ -89,7 +89,7 @@ "dist/nodes/Mailgun/Mailgun.node.js", "dist/nodes/Mattermost/Mattermost.node.js", "dist/nodes/Merge.node.js", - "dist/nodes/MoveBinaryData.node", + "dist/nodes/MoveBinaryData.node.js", "dist/nodes/MongoDb/MongoDb.node.js", "dist/nodes/NextCloud/NextCloud.node.js", "dist/nodes/NoOp.node.js", From 7ebe09cec16ca9c177f0146678904b9836d1d6a3 Mon Sep 17 00:00:00 2001 From: Jan Oberhauser Date: Thu, 31 Oct 2019 22:34:17 +0100 Subject: [PATCH 10/25] :bookmark: Release n8n-nodes-base@0.26.1 --- packages/nodes-base/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/nodes-base/package.json b/packages/nodes-base/package.json index 95a76fddd8..841e206211 100644 --- a/packages/nodes-base/package.json +++ b/packages/nodes-base/package.json @@ -1,6 +1,6 @@ { "name": "n8n-nodes-base", - "version": "0.26.0", + "version": "0.26.1", "description": "Base nodes of n8n", "license": "SEE LICENSE IN LICENSE.md", "homepage": "https://n8n.io", From 2c45460d9098bc01ce257b2db609a7f122341657 Mon Sep 17 00:00:00 2001 From: Sebastian Date: Fri, 1 Nov 2019 10:50:27 +0100 Subject: [PATCH 11/25] Add encoding Option --- packages/nodes-base/nodes/MoveBinaryData.node.ts | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/packages/nodes-base/nodes/MoveBinaryData.node.ts b/packages/nodes-base/nodes/MoveBinaryData.node.ts index 3fb9c936d8..2974dbb351 100644 --- a/packages/nodes-base/nodes/MoveBinaryData.node.ts +++ b/packages/nodes-base/nodes/MoveBinaryData.node.ts @@ -199,6 +199,20 @@ export class MoveBinaryData implements INodeType { default: false, description: 'Use data as is and do not JSON.stringify it.', }, + { + displayName: 'Encoding', + name: 'encoding', + type: 'string', + displayOptions: { + show: { + '/mode': [ + 'binaryToJson', + ], + }, + }, + default: 'ascii', + description: 'Set the encoding of the data stream', + }, ], } ], @@ -235,7 +249,7 @@ export class MoveBinaryData implements INodeType { continue; } - const convertedValue = JSON.parse(new Buffer(value.data, 'base64').toString('ascii')); + const convertedValue = JSON.parse(new Buffer(value.data, 'base64').toString(options.encoding)); if (setAllData === true) { // Set the full data From b16b664afaaf9e4f4c9ff394610419eb8f12a882 Mon Sep 17 00:00:00 2001 From: Jan Oberhauser Date: Fri, 1 Nov 2019 11:07:17 +0100 Subject: [PATCH 12/25] :zap: Make JSON.parse of data in MoveBinaryData optional --- .../nodes-base/nodes/MoveBinaryData.node.ts | 25 +++++++++++++++++-- 1 file changed, 23 insertions(+), 2 deletions(-) diff --git a/packages/nodes-base/nodes/MoveBinaryData.node.ts b/packages/nodes-base/nodes/MoveBinaryData.node.ts index 3fb9c936d8..baaa71e454 100644 --- a/packages/nodes-base/nodes/MoveBinaryData.node.ts +++ b/packages/nodes-base/nodes/MoveBinaryData.node.ts @@ -170,6 +170,23 @@ export class MoveBinaryData implements INodeType { default: false, description: 'If the source key should be kept. By default does it get deleted.', }, + { + displayName: 'JSON Parse', + name: 'jsonParse', + type: 'boolean', + displayOptions: { + show: { + '/mode': [ + 'binaryToJson', + ], + '/setAllData': [ + false + ], + }, + }, + default: false, + description: 'Run JSON parse on the data to get propery object data.', + }, { displayName: 'Mime Type', name: 'mimeType', @@ -235,15 +252,19 @@ export class MoveBinaryData implements INodeType { continue; } - const convertedValue = JSON.parse(new Buffer(value.data, 'base64').toString('ascii')); + let convertedValue = new Buffer(value.data, 'base64').toString('utf8'); if (setAllData === true) { // Set the full data - newItem.json = convertedValue; + newItem.json = JSON.parse(convertedValue); } else { // Does get added to existing data so copy it first newItem.json = JSON.parse(JSON.stringify(item.json)); + if (options.jsonParse) { + convertedValue = JSON.parse(convertedValue); + } + const destinationKey = this.getNodeParameter('destinationKey', itemIndex, '') as string; set(newItem.json, destinationKey, convertedValue); } From 1d61efcd01bb0992d1ee96bf4d31ecd0f27e237e Mon Sep 17 00:00:00 2001 From: Jan Oberhauser Date: Fri, 1 Nov 2019 11:35:38 +0100 Subject: [PATCH 13/25] :bookmark: Release n8n-nodes-base@0.26.2 --- packages/nodes-base/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/nodes-base/package.json b/packages/nodes-base/package.json index 841e206211..aa2af1805f 100644 --- a/packages/nodes-base/package.json +++ b/packages/nodes-base/package.json @@ -1,6 +1,6 @@ { "name": "n8n-nodes-base", - "version": "0.26.1", + "version": "0.26.2", "description": "Base nodes of n8n", "license": "SEE LICENSE IN LICENSE.md", "homepage": "https://n8n.io", From 6f5418a9ad63979bac283a788b36fe92eaac318f Mon Sep 17 00:00:00 2001 From: Jan Oberhauser Date: Fri, 1 Nov 2019 11:36:57 +0100 Subject: [PATCH 14/25] :arrow_up: Set n8n-nodes-base@0.26.2 on n8n --- packages/cli/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/cli/package.json b/packages/cli/package.json index 3a96c347f9..ac7e939b22 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -91,7 +91,7 @@ "mongodb": "^3.2.3", "n8n-core": "~0.14.0", "n8n-editor-ui": "~0.24.0", - "n8n-nodes-base": "~0.26.0", + "n8n-nodes-base": "~0.26.2", "n8n-workflow": "~0.15.0", "open": "^6.1.0", "pg": "^7.11.0", From 1e11953c1c93417aa968e0fc22fa556bc253b9b1 Mon Sep 17 00:00:00 2001 From: Jan Oberhauser Date: Fri, 1 Nov 2019 11:37:45 +0100 Subject: [PATCH 15/25] :bookmark: Release n8n@0.31.1 --- packages/cli/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/cli/package.json b/packages/cli/package.json index ac7e939b22..8495547343 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -1,6 +1,6 @@ { "name": "n8n", - "version": "0.31.0", + "version": "0.31.1", "description": "n8n Workflow Automation Tool", "license": "SEE LICENSE IN LICENSE.md", "homepage": "https://n8n.io", From 644b5455c9b974fc9377202bdfd91e3992071f3e Mon Sep 17 00:00:00 2001 From: Leo Date: Sat, 2 Nov 2019 10:12:59 +0100 Subject: [PATCH 16/25] added ability to post raw/string/xml --- packages/nodes-base/nodes/HttpRequest.node.ts | 44 ++++++++++++++++--- 1 file changed, 38 insertions(+), 6 deletions(-) diff --git a/packages/nodes-base/nodes/HttpRequest.node.ts b/packages/nodes-base/nodes/HttpRequest.node.ts index 9ab1068e08..5d9367b948 100644 --- a/packages/nodes-base/nodes/HttpRequest.node.ts +++ b/packages/nodes-base/nodes/HttpRequest.node.ts @@ -196,11 +196,11 @@ export class HttpRequest implements INodeType { }, { - displayName: 'JSON Parameters', + displayName: 'JSON/RAW Parameters', name: 'jsonParameters', type: 'boolean', default: false, - description: 'If the query and/or body parameter should be set via the UI or raw as JSON', + description: 'If the query and/or body parameter should be set via the value-key pair UI or JSON/RAW', }, { @@ -228,6 +228,10 @@ export class HttpRequest implements INodeType { name: 'JSON', value: 'json' }, + { + name: 'RAW/Custom', + value: 'raw' + }, { name: 'Form-Data Multipart', value: 'multipart-form-data' @@ -240,6 +244,24 @@ export class HttpRequest implements INodeType { default: 'json', description: 'Content-Type to use to send body parameters.', }, + { + displayName: 'MIME Type', + name: 'bodyContentCustomMIMEType', + type: 'string', + default: '', + placeholder: 'text/xml', + description: 'Specify the mime type for raw/custom body type', + required: false, + displayOptions: { + show: { + '/requestMethod': [ + 'PATCH', + 'POST', + 'PUT', + ], + }, + }, + }, { displayName: 'Full Response', name: 'fullResponse', @@ -357,7 +379,7 @@ export class HttpRequest implements INodeType { }, }, default: '', - description: 'Body parameters as JSON.', + description: 'Body parameters as JSON or RAW.', }, { displayName: 'Body Parameters', @@ -554,13 +576,13 @@ export class HttpRequest implements INodeType { requestOptions[optionData.name] = tempValue; // @ts-ignore - if (typeof requestOptions[optionData.name] !== 'object') { - // If it is not an object it must be JSON so parse it + if (typeof requestOptions[optionData.name] !== 'object' && options.bodyContentType !== 'raw') { + // If it is not an object && bodyContentType is not 'raw' it must be JSON so parse it try { // @ts-ignore requestOptions[optionData.name] = JSON.parse(requestOptions[optionData.name]); } catch (e) { - throw new Error(`The data in "${optionData.displayName}" is no valid JSON.`); + throw new Error(`The data in "${optionData.displayName}" is no valid JSON. Set Body Content Type to "RAW/Custom" for XML or other types of payloads`); } } } @@ -592,6 +614,14 @@ export class HttpRequest implements INodeType { } } + // Add Content Type if any are set + if (options.bodyContentCustomMIMEType) { + if(requestOptions.headers === undefined) { + requestOptions.headers = {}; + } + requestOptions.headers['Content-Type'] = options.bodyContentCustomMIMEType; + } + // Add credentials if any are set if (httpBasicAuth !== undefined) { requestOptions.auth = { @@ -612,6 +642,8 @@ export class HttpRequest implements INodeType { if (responseFormat === 'file') { requestOptions.encoding = null; + } else if(options.bodyContentType === 'raw') { + requestOptions.json = false; } else { requestOptions.json = true; } From ab6cc43a4c1681c062abfe8f8e8105a669c25317 Mon Sep 17 00:00:00 2001 From: Jan Oberhauser Date: Sat, 2 Nov 2019 12:16:20 +0100 Subject: [PATCH 17/25] :shirt: Fix lint issues --- .../credentials/Amqp.credentials.ts | 3 -- packages/nodes-base/nodes/Amqp/Amqp.node.ts | 50 +++++++------------ .../nodes-base/nodes/Amqp/AmqpTrigger.node.ts | 41 +++++++-------- 3 files changed, 36 insertions(+), 58 deletions(-) diff --git a/packages/nodes-base/credentials/Amqp.credentials.ts b/packages/nodes-base/credentials/Amqp.credentials.ts index 640dedb18b..ff6b23ed1f 100644 --- a/packages/nodes-base/credentials/Amqp.credentials.ts +++ b/packages/nodes-base/credentials/Amqp.credentials.ts @@ -8,9 +8,6 @@ export class Amqp implements ICredentialType { name = 'amqp'; displayName = 'AMQP'; properties = [ - // The credentials to get from user and save encrypted. - // Properties can be defined exactly in the same way - // as node properties. { displayName: 'Hostname', name: 'hostname', diff --git a/packages/nodes-base/nodes/Amqp/Amqp.node.ts b/packages/nodes-base/nodes/Amqp/Amqp.node.ts index 9dcb3ca5ef..66ed1aa25d 100644 --- a/packages/nodes-base/nodes/Amqp/Amqp.node.ts +++ b/packages/nodes-base/nodes/Amqp/Amqp.node.ts @@ -1,11 +1,11 @@ +import { ContainerOptions, Delivery } from 'rhea'; + import { IExecuteSingleFunctions } from 'n8n-core'; import { - IDataObject, INodeExecutionData, INodeType, INodeTypeDescription, } from 'n8n-workflow'; -import { Delivery } from 'rhea'; export class Amqp implements INodeType { description: INodeTypeDescription = { @@ -26,23 +26,6 @@ export class Amqp implements INodeType { required: true, }], properties: [ - { - displayName: 'Host', - name: 'hostname', - type: 'string', - default: 'localhost', - description: 'hostname of the amqp server', - }, - { - displayName: 'Port', - name: 'port', - type: 'number', - typeOptions: { - minValue: 1, - }, - default: 5672, - description: 'TCP Port to connect to', - }, { displayName: 'Queue / Topic', name: 'sink', @@ -71,46 +54,47 @@ export class Amqp implements INodeType { } const sink = this.getNodeParameter('sink', '') as string; - let applicationProperties = this.getNodeParameter('headerParametersJson', {}) as string | object; + const applicationProperties = this.getNodeParameter('headerParametersJson', {}) as string | object; let headerProperties = applicationProperties; - if(typeof applicationProperties === 'string' && applicationProperties != '') { - headerProperties = JSON.parse(applicationProperties) + if(typeof applicationProperties === 'string' && applicationProperties !== '') { + headerProperties = JSON.parse(applicationProperties); } - if (sink == '') { + if (sink === '') { throw new Error('Queue or Topic required!'); } - let container = require('rhea'); + const container = require('rhea'); - let connectOptions = { + const connectOptions: ContainerOptions = { host: credentials.hostname, port: credentials.port, reconnect: true, // this id the default anyway reconnect_limit: 50, // try for max 50 times, based on a back-off algorithm - } + }; if (credentials.username || credentials.password) { container.options.username = credentials.username; container.options.password = credentials.password; } - let allSent = new Promise( function( resolve ) { - container.on('sendable', function (context: any) { + const allSent = new Promise(( resolve ) => { + container.on('sendable', (context: any) => { // tslint:disable-line:no-any - let message = { + const message = { application_properties: headerProperties, body: JSON.stringify(item) - } - let sendResult = context.sender.send(message); + }; + + const sendResult = context.sender.send(message); resolve(sendResult); }); }); - + container.connect(connectOptions).open_sender(sink); - let sendResult: Delivery = await allSent as Delivery; // sendResult has a a property that causes circular reference if returned + const sendResult: Delivery = await allSent as Delivery; // sendResult has a a property that causes circular reference if returned return { json: { id: sendResult.id } } as INodeExecutionData; } diff --git a/packages/nodes-base/nodes/Amqp/AmqpTrigger.node.ts b/packages/nodes-base/nodes/Amqp/AmqpTrigger.node.ts index c65f9996e5..91a4eff552 100644 --- a/packages/nodes-base/nodes/Amqp/AmqpTrigger.node.ts +++ b/packages/nodes-base/nodes/Amqp/AmqpTrigger.node.ts @@ -1,9 +1,10 @@ +import { ContainerOptions } from 'rhea'; + import { ITriggerFunctions } from 'n8n-core'; import { INodeType, INodeTypeDescription, ITriggerResponse, - } from 'n8n-workflow'; @@ -67,40 +68,40 @@ export class AmqpTrigger implements INodeType { const clientname = this.getNodeParameter('clientname', '') as string; const subscription = this.getNodeParameter('subscription', '') as string; - if (sink == '') { + if (sink === '') { throw new Error('Queue or Topic required!'); } - let durable: boolean = false; + let durable = false; if(subscription && clientname) { durable = true; } - let container = require('rhea'); - let connectOptions = { + const container = require('rhea'); + const connectOptions: ContainerOptions = { host: credentials.hostname, port: credentials.port, reconnect: true, // this id the default anyway reconnect_limit: 50, // try for max 50 times, based on a back-off algorithm container_id: (durable ? clientname : null) - } + }; if (credentials.username || credentials.password) { container.options.username = credentials.username; container.options.password = credentials.password; } - let lastMsgId: any = undefined; - let self = this; + let lastMsgId: number | undefined = undefined; + const self = this; - container.on('message', function (context: any) { - if (context.message.message_id && context.message.message_id == lastMsgId) { + container.on('message', (context: any) => { // tslint:disable-line:no-any + if (context.message.message_id && context.message.message_id === lastMsgId) { // ignore duplicate message check, don't think it's necessary, but it was in the rhea-lib example code lastMsgId = context.message.message_id; return; } self.emit([self.helpers.returnJsonArray([context.message])]); }); - - let connection = container.connect(connectOptions); + + const connection = container.connect(connectOptions); let clientOptions = undefined; if (durable) { clientOptions = { @@ -111,14 +112,14 @@ export class AmqpTrigger implements INodeType { expiry_policy: 'never' }, credit_window: 1 // prefetch 1 - } + }; } else { clientOptions = { source: { address: sink, }, credit_window: 1 // prefetch 1 - } + }; } connection.open_receiver(clientOptions); @@ -135,15 +136,11 @@ export class AmqpTrigger implements INodeType { // for AMQP it doesn't make much sense to wait here but // for a new user who doesn't know how this works, it's better to wait and show a respective info message async function manualTriggerFunction() { - - await new Promise( function( resolve ) { - let timeoutHandler = setTimeout(function() { - self.emit([self.helpers.returnJsonArray([{ - error: 'Aborted, no message received within 30secs. This 30sec timeout is only set for "manually triggered execution". Active Workflows will listen indefinitely.' - }])]); - resolve(true); + await new Promise(( resolve, reject ) => { + const timeoutHandler = setTimeout(() => { + reject(new Error('Aborted, no message received within 30secs. This 30sec timeout is only set for "manually triggered execution". Active Workflows will listen indefinitely.')); }, 30000); - container.on('message', function (context: any) { + container.on('message', (context: any) => { // tslint:disable-line:no-any clearTimeout(timeoutHandler); resolve(true); }); From 5804d908d524e800fee64d7038df714acb01dc6b Mon Sep 17 00:00:00 2001 From: Jan Oberhauser Date: Sat, 2 Nov 2019 21:20:42 +0100 Subject: [PATCH 18/25] :zap: Rename parameter to match others and add dot to descriptions --- packages/nodes-base/nodes/HttpRequest.node.ts | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/packages/nodes-base/nodes/HttpRequest.node.ts b/packages/nodes-base/nodes/HttpRequest.node.ts index 5d9367b948..0f6ab7499b 100644 --- a/packages/nodes-base/nodes/HttpRequest.node.ts +++ b/packages/nodes-base/nodes/HttpRequest.node.ts @@ -200,7 +200,7 @@ export class HttpRequest implements INodeType { name: 'jsonParameters', type: 'boolean', default: false, - description: 'If the query and/or body parameter should be set via the value-key pair UI or JSON/RAW', + description: 'If the query and/or body parameter should be set via the value-key pair UI or JSON/RAW.', }, { @@ -246,11 +246,11 @@ export class HttpRequest implements INodeType { }, { displayName: 'MIME Type', - name: 'bodyContentCustomMIMEType', + name: 'bodyContentCustomMimeType', type: 'string', default: '', placeholder: 'text/xml', - description: 'Specify the mime type for raw/custom body type', + description: 'Specify the mime type for raw/custom body type.', required: false, displayOptions: { show: { @@ -615,11 +615,11 @@ export class HttpRequest implements INodeType { } // Add Content Type if any are set - if (options.bodyContentCustomMIMEType) { + if (options.bodyContentCustomMimeType) { if(requestOptions.headers === undefined) { requestOptions.headers = {}; } - requestOptions.headers['Content-Type'] = options.bodyContentCustomMIMEType; + requestOptions.headers['Content-Type'] = options.bodyContentCustomMimeType; } // Add credentials if any are set From b5918c6bfec15fa6bddb2d0cb1636f8f2f5ba152 Mon Sep 17 00:00:00 2001 From: Jan Oberhauser Date: Sat, 2 Nov 2019 21:33:25 +0100 Subject: [PATCH 19/25] :zap: Make it possible to define custom file name for spreadsheet files --- .../nodes-base/nodes/SpreadsheetFile.node.ts | 33 +++++++++++++++++-- 1 file changed, 31 insertions(+), 2 deletions(-) diff --git a/packages/nodes-base/nodes/SpreadsheetFile.node.ts b/packages/nodes-base/nodes/SpreadsheetFile.node.ts index ac76a02e28..9f82a2b950 100644 --- a/packages/nodes-base/nodes/SpreadsheetFile.node.ts +++ b/packages/nodes-base/nodes/SpreadsheetFile.node.ts @@ -156,11 +156,34 @@ export class SpreadsheetFile implements INodeType { 'toFile', ], }, - }, placeholder: '', description: 'Name of the binary property in which to save
the binary data of the spreadsheet file.', }, + + { + displayName: 'Options', + name: 'options', + type: 'collection', + placeholder: 'Add Option', + displayOptions: { + show: { + operation: [ + 'toFile', + ], + }, + }, + default: {}, + options: [ + { + displayName: 'File Name', + name: 'fileName', + type: 'string', + default: '', + description: 'File name to set in binary data. By default will "spreadsheet." be used.', + }, + ], + }, ] }; @@ -214,6 +237,7 @@ export class SpreadsheetFile implements INodeType { // Write the workflow data to spreadsheet file const binaryPropertyName = this.getNodeParameter('binaryPropertyName', 0) as string; const fileFormat = this.getNodeParameter('fileFormat', 0) as string; + const options = this.getNodeParameter('options', 0, {}) as IDataObject; // Get the json data of the items and flatten it let item: INodeExecutionData; @@ -258,7 +282,12 @@ export class SpreadsheetFile implements INodeType { binary: {}, }; - newItem.binary![binaryPropertyName] = await this.helpers.prepareBinaryData(wbout, `spreadsheet.${fileFormat}`); + let fileName = `spreadsheet.${fileFormat}`; + if (options.fileName !== undefined) { + fileName = options.fileName as string; + } + + newItem.binary![binaryPropertyName] = await this.helpers.prepareBinaryData(wbout, fileName); const newItems = []; newItems.push(newItem); From d6134ec40530a4ee21e552e96d2328d48e61d459 Mon Sep 17 00:00:00 2001 From: Jan Oberhauser Date: Sat, 2 Nov 2019 21:41:13 +0100 Subject: [PATCH 20/25] :sparkles: Add Deal.duplicate functionality to Pipedrive-Node --- .../nodes/Pipedrive/Pipedrive.node.ts | 37 +++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/packages/nodes-base/nodes/Pipedrive/Pipedrive.node.ts b/packages/nodes-base/nodes/Pipedrive/Pipedrive.node.ts index 3f4d4da5fe..8ae9c42630 100644 --- a/packages/nodes-base/nodes/Pipedrive/Pipedrive.node.ts +++ b/packages/nodes-base/nodes/Pipedrive/Pipedrive.node.ts @@ -171,6 +171,11 @@ export class Pipedrive implements INodeType { value: 'delete', description: 'Delete a deal', }, + { + name: 'Duplicate', + value: 'duplicate', + description: 'Duplicate a deal', + }, { name: 'Get', value: 'get', @@ -947,6 +952,28 @@ export class Pipedrive implements INodeType { description: 'ID of the deal to delete.', }, + // ---------------------------------- + // deal:duplicate + // ---------------------------------- + { + displayName: 'Deal ID', + name: 'dealId', + type: 'number', + displayOptions: { + show: { + operation: [ + 'duplicate', + ], + resource: [ + 'deal', + ], + }, + }, + default: 0, + required: true, + description: 'ID of the deal to duplicate.', + }, + // ---------------------------------- // deal:get // ---------------------------------- @@ -2157,6 +2184,16 @@ export class Pipedrive implements INodeType { const dealId = this.getNodeParameter('dealId', i) as number; endpoint = `/deals/${dealId}`; + } else if (operation === 'duplicate') { + // ---------------------------------- + // deal:duplicate + // ---------------------------------- + + requestMethod = 'POST'; + + const dealId = this.getNodeParameter('dealId', i) as number; + endpoint = `/deals/${dealId}/duplicate`; + } else if (operation === 'get') { // ---------------------------------- // deal:get From ac88d25e1b5a5f100aa2711c9baaa20271bf93d3 Mon Sep 17 00:00:00 2001 From: Jan Oberhauser Date: Sat, 2 Nov 2019 22:29:52 +0100 Subject: [PATCH 21/25] :sparkles: Add clear functionality to Google Sheet-Node --- .../nodes-base/nodes/Google/GoogleSheet.ts | 21 ++++++++++++++ .../nodes/Google/GoogleSheets.node.ts | 28 +++++++++++++++++++ 2 files changed, 49 insertions(+) diff --git a/packages/nodes-base/nodes/Google/GoogleSheet.ts b/packages/nodes-base/nodes/Google/GoogleSheet.ts index 58392be54f..72391939af 100644 --- a/packages/nodes-base/nodes/Google/GoogleSheet.ts +++ b/packages/nodes-base/nodes/Google/GoogleSheet.ts @@ -45,6 +45,27 @@ export class GoogleSheet { } + /** + * Clears values from a sheet + * + * @param {string} range + * @returns {Promise} + * @memberof GoogleSheet + */ + async clearData(range: string): Promise { + const client = await this.getAuthenticationClient(); + + const response = await Sheets.spreadsheets.values.clear( + { + auth: client, + spreadsheetId: this.id, + range, + } + ); + + return response.data; + } + /** * Returns the cell values */ diff --git a/packages/nodes-base/nodes/Google/GoogleSheets.node.ts b/packages/nodes-base/nodes/Google/GoogleSheets.node.ts index da7d070214..028011a3ff 100644 --- a/packages/nodes-base/nodes/Google/GoogleSheets.node.ts +++ b/packages/nodes-base/nodes/Google/GoogleSheets.node.ts @@ -47,6 +47,11 @@ export class GoogleSheets implements INodeType { value: 'append', description: 'Appends the data to a Sheet', }, + { + name: 'Clear', + value: 'clear', + description: 'Clears data from a Sheet', + }, { name: 'Lookup', value: 'lookup', @@ -172,6 +177,7 @@ export class GoogleSheets implements INodeType { hide: { operation: [ 'append', + 'clear', ], rawData: [ true @@ -193,6 +199,9 @@ export class GoogleSheets implements INodeType { }, displayOptions: { hide: { + operation: [ + 'clear', + ], rawData: [ true ], @@ -266,6 +275,16 @@ export class GoogleSheets implements INodeType { type: 'collection', placeholder: 'Add Option', default: {}, + displayOptions: { + show: { + operation: [ + 'append', + 'lookup', + 'read', + 'update', + ], + }, + }, options: [ { displayName: 'Return All Matches', @@ -425,6 +444,15 @@ export class GoogleSheets implements INodeType { // TODO: Should add this data somewhere // TODO: Should have something like add metadata which does not get passed through + return this.prepareOutputData(items); + } else if (operation === 'clear') { + // ---------------------------------- + // clear + // ---------------------------------- + + await sheet.clearData(range); + + const items = this.getInputData(); return this.prepareOutputData(items); } else if (operation === 'lookup') { // ---------------------------------- From 33fd92f2fd8edb5b25448c1a99056efb3eb6b559 Mon Sep 17 00:00:00 2001 From: Jan Oberhauser Date: Sat, 2 Nov 2019 22:31:41 +0100 Subject: [PATCH 22/25] :bookmark: Release n8n-nodes-base@0.27.0 --- packages/nodes-base/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/nodes-base/package.json b/packages/nodes-base/package.json index 2e36690692..e85d775461 100644 --- a/packages/nodes-base/package.json +++ b/packages/nodes-base/package.json @@ -1,6 +1,6 @@ { "name": "n8n-nodes-base", - "version": "0.26.2", + "version": "0.27.0", "description": "Base nodes of n8n", "license": "SEE LICENSE IN LICENSE.md", "homepage": "https://n8n.io", From 54744cadb4f8e567618c03f89fa43f3c1944525a Mon Sep 17 00:00:00 2001 From: Jan Oberhauser Date: Sat, 2 Nov 2019 22:32:49 +0100 Subject: [PATCH 23/25] :arrow_up: Set n8n-nodes-base@0.27.0 on n8n --- packages/cli/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/cli/package.json b/packages/cli/package.json index 8495547343..ce377406aa 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -91,7 +91,7 @@ "mongodb": "^3.2.3", "n8n-core": "~0.14.0", "n8n-editor-ui": "~0.24.0", - "n8n-nodes-base": "~0.26.2", + "n8n-nodes-base": "~0.27.0", "n8n-workflow": "~0.15.0", "open": "^6.1.0", "pg": "^7.11.0", From 111d0e277bc06afa9bbac55bc7378f18cafc537e Mon Sep 17 00:00:00 2001 From: Jan Oberhauser Date: Sat, 2 Nov 2019 22:33:25 +0100 Subject: [PATCH 24/25] :bookmark: Release n8n@0.32.0 --- packages/cli/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/cli/package.json b/packages/cli/package.json index ce377406aa..d384877f7f 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -1,6 +1,6 @@ { "name": "n8n", - "version": "0.31.1", + "version": "0.32.0", "description": "n8n Workflow Automation Tool", "license": "SEE LICENSE IN LICENSE.md", "homepage": "https://n8n.io", From 8d88c64b9d8079c7ab2df6820840610e57963064 Mon Sep 17 00:00:00 2001 From: Jan Oberhauser Date: Sat, 2 Nov 2019 22:35:29 +0100 Subject: [PATCH 25/25] :bookmark: Release n8n-nodes-base@0.27.1 --- packages/nodes-base/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/nodes-base/package.json b/packages/nodes-base/package.json index e85d775461..6986537668 100644 --- a/packages/nodes-base/package.json +++ b/packages/nodes-base/package.json @@ -1,6 +1,6 @@ { "name": "n8n-nodes-base", - "version": "0.27.0", + "version": "0.27.1", "description": "Base nodes of n8n", "license": "SEE LICENSE IN LICENSE.md", "homepage": "https://n8n.io",