diff --git a/packages/cli/test/integration/publicApi/credentials.test.ts b/packages/cli/test/integration/publicApi/credentials.test.ts index aa611d0d78..bc5c414142 100644 --- a/packages/cli/test/integration/publicApi/credentials.test.ts +++ b/packages/cli/test/integration/publicApi/credentials.test.ts @@ -252,19 +252,17 @@ describe('GET /credentials/schema/:credentialType', () => { }); test('should retrieve credential type', async () => { - const response = await authOwnerAgent.get('/credentials/schema/githubApi'); + const response = await authOwnerAgent.get('/credentials/schema/ftp'); const { additionalProperties, type, properties, required } = response.body; expect(additionalProperties).toBe(false); expect(type).toBe('object'); - expect(properties.server).toBeDefined(); - expect(properties.server.type).toBe('string'); - expect(properties.user.type).toBeDefined(); - expect(properties.user.type).toBe('string'); - expect(properties.accessToken.type).toBeDefined(); - expect(properties.accessToken.type).toBe('string'); - expect(required).toEqual(expect.arrayContaining(['server', 'user', 'accessToken'])); + expect(properties.host.type).toBe('string'); + expect(properties.port.type).toBe('number'); + expect(properties.username.type).toBe('string'); + expect(properties.password.type).toBe('string'); + expect(required).toEqual(expect.arrayContaining(['host', 'port'])); expect(response.statusCode).toBe(200); }); }); @@ -301,9 +299,9 @@ const INVALID_PAYLOADS = [ }, { name: randomName(), - type: 'githubApi', + type: 'ftp', data: { - server: randomName(), + username: randomName(), }, }, {}, diff --git a/packages/cli/test/integration/shared/utils/index.ts b/packages/cli/test/integration/shared/utils/index.ts index 6033422db2..660b4ada5c 100644 --- a/packages/cli/test/integration/shared/utils/index.ts +++ b/packages/cli/test/integration/shared/utils/index.ts @@ -1,20 +1,13 @@ import { Container } from 'typedi'; import { randomBytes } from 'crypto'; import { existsSync } from 'fs'; -import { CronJob } from 'cron'; -import set from 'lodash/set'; import { BinaryDataManager, UserSettings } from 'n8n-core'; -import type { - ICredentialType, - IExecuteFunctions, - INode, - INodeExecutionData, - INodeParameters, - ITriggerFunctions, - TriggerTime, -} from 'n8n-workflow'; -import { deepCopy } from 'n8n-workflow'; -import { NodeHelpers, toCronExpression } from 'n8n-workflow'; +import type { INode } from 'n8n-workflow'; +import { GithubApi } from 'n8n-nodes-base/credentials/GithubApi.credentials'; +import { Ftp } from 'n8n-nodes-base/credentials/Ftp.credentials'; +import { Cron } from 'n8n-nodes-base/nodes/Cron/Cron.node'; +import { Set } from 'n8n-nodes-base/nodes/Set/Set.node'; +import { Start } from 'n8n-nodes-base/nodes/Start/Start.node'; import type request from 'supertest'; import { v4 as uuid } from 'uuid'; @@ -42,45 +35,17 @@ export async function initActiveWorkflowRunner(): Promise return workflowRunner; } -export function gitHubCredentialType(): ICredentialType { - return { - name: 'githubApi', - displayName: 'Github API', - documentationUrl: 'github', - properties: [ - { - displayName: 'Github Server', - name: 'server', - type: 'string', - default: 'https://api.github.com', - required: true, - description: 'The server to connect to. Only has to be set if Github Enterprise is used.', - }, - { - displayName: 'User', - name: 'user', - type: 'string', - required: true, - default: '', - }, - { - displayName: 'Access Token', - name: 'accessToken', - type: 'string', - required: true, - default: '', - }, - ], - }; -} - /** * Initialize node types. */ export async function initCredentialsTypes(): Promise { Container.get(LoadNodesAndCredentials).loaded.credentials = { githubApi: { - type: gitHubCredentialType(), + type: new GithubApi(), + sourcePath: '', + }, + ftp: { + type: new Ftp(), sourcePath: '', }, }; @@ -92,294 +57,16 @@ export async function initCredentialsTypes(): Promise { export async function initNodeTypes() { Container.get(LoadNodesAndCredentials).loaded.nodes = { 'n8n-nodes-base.start': { + type: new Start(), sourcePath: '', - type: { - description: { - displayName: 'Start', - name: 'start', - group: ['input'], - version: 1, - description: 'Starts the workflow execution from this node', - defaults: { - name: 'Start', - color: '#553399', - }, - inputs: [], - outputs: ['main'], - properties: [], - }, - async execute(this: IExecuteFunctions) { - return [this.getInputData()]; - }, - }, }, 'n8n-nodes-base.cron': { + type: new Cron(), sourcePath: '', - type: { - description: { - displayName: 'Cron', - name: 'cron', - icon: 'fa:calendar', - group: ['trigger', 'schedule'], - version: 1, - description: 'Triggers the workflow at a specific time', - eventTriggerDescription: '', - activationMessage: - 'Your cron trigger will now trigger executions on the schedule you have defined.', - defaults: { - name: 'Cron', - color: '#00FF00', - }, - inputs: [], - outputs: ['main'], - properties: [ - { - displayName: 'Trigger Times', - name: 'triggerTimes', - type: 'fixedCollection', - typeOptions: { - multipleValues: true, - multipleValueButtonText: 'Add Time', - }, - default: {}, - description: 'Triggers for the workflow', - placeholder: 'Add Cron Time', - options: NodeHelpers.cronNodeOptions, - }, - ], - }, - async trigger(this: ITriggerFunctions) { - const triggerTimes = this.getNodeParameter('triggerTimes') as unknown as { - item: TriggerTime[]; - }; - - // Get all the trigger times - const cronTimes = (triggerTimes.item || []).map(toCronExpression); - - // The trigger function to execute when the cron-time got reached - // or when manually triggered - const executeTrigger = () => { - this.emit([this.helpers.returnJsonArray([{}])]); - }; - - const timezone = this.getTimezone(); - - // Start the cron-jobs - const cronJobs = cronTimes.map( - (cronTime) => new CronJob(cronTime, executeTrigger, undefined, true, timezone), - ); - - // Stop the cron-jobs - async function closeFunction() { - for (const cronJob of cronJobs) { - cronJob.stop(); - } - } - - async function manualTriggerFunction() { - executeTrigger(); - } - - return { - closeFunction, - manualTriggerFunction, - }; - }, - }, }, 'n8n-nodes-base.set': { + type: new Set(), sourcePath: '', - type: { - description: { - displayName: 'Set', - name: 'set', - icon: 'fa:pen', - group: ['input'], - version: 1, - description: 'Sets values on items and optionally remove other values', - defaults: { - name: 'Set', - color: '#0000FF', - }, - inputs: ['main'], - outputs: ['main'], - properties: [ - { - displayName: 'Keep Only Set', - name: 'keepOnlySet', - type: 'boolean', - default: false, - description: - 'If only the values set on this node should be kept and all others removed.', - }, - { - displayName: 'Values to Set', - name: 'values', - placeholder: 'Add Value', - type: 'fixedCollection', - typeOptions: { - multipleValues: true, - sortable: true, - }, - description: 'The value to set.', - default: {}, - options: [ - { - name: 'boolean', - displayName: 'Boolean', - values: [ - { - displayName: 'Name', - name: 'name', - type: 'string', - default: 'propertyName', - description: - 'Name of the property to write data to. Supports dot-notation. Example: "data.person[0].name"', - }, - { - displayName: 'Value', - name: 'value', - type: 'boolean', - default: false, - description: 'The boolean value to write in the property.', - }, - ], - }, - { - name: 'number', - displayName: 'Number', - values: [ - { - displayName: 'Name', - name: 'name', - type: 'string', - default: 'propertyName', - description: - 'Name of the property to write data to. Supports dot-notation. Example: "data.person[0].name"', - }, - { - displayName: 'Value', - name: 'value', - type: 'number', - default: 0, - description: 'The number value to write in the property.', - }, - ], - }, - { - name: 'string', - displayName: 'String', - values: [ - { - displayName: 'Name', - name: 'name', - type: 'string', - default: 'propertyName', - description: - 'Name of the property to write data to. Supports dot-notation. Example: "data.person[0].name"', - }, - { - displayName: 'Value', - name: 'value', - type: 'string', - default: '', - description: 'The string value to write in the property.', - }, - ], - }, - ], - }, - - { - displayName: 'Options', - name: 'options', - type: 'collection', - placeholder: 'Add Option', - default: {}, - options: [ - { - displayName: 'Dot Notation', - name: 'dotNotation', - type: 'boolean', - default: true, - description: `

By default, dot-notation is used in property names. This means that "a.b" will set the property "b" underneath "a" so { "a": { "b": value} }.

If that is not intended this can be deactivated, it will then set { "a.b": value } instead.

- `, - }, - ], - }, - ], - }, - async execute(this: IExecuteFunctions) { - const items = this.getInputData(); - - if (items.length === 0) { - items.push({ json: {} }); - } - - const returnData: INodeExecutionData[] = []; - - let item: INodeExecutionData; - let keepOnlySet: boolean; - for (let itemIndex = 0; itemIndex < items.length; itemIndex++) { - keepOnlySet = this.getNodeParameter('keepOnlySet', itemIndex, false) as boolean; - item = items[itemIndex]; - const options = this.getNodeParameter('options', itemIndex, {}); - - const newItem: INodeExecutionData = { - json: {}, - }; - - if (!keepOnlySet) { - if (item.binary !== undefined) { - // Create a shallow copy of the binary data so that the old - // data references which do not get changed still stay behind - // but the incoming data does not get changed. - newItem.binary = {}; - Object.assign(newItem.binary, item.binary); - } - - newItem.json = deepCopy(item.json); - } - - // Add boolean values - (this.getNodeParameter('values.boolean', itemIndex, []) as INodeParameters[]).forEach( - (setItem) => { - if (options.dotNotation === false) { - newItem.json[setItem.name as string] = !!setItem.value; - } else { - set(newItem.json, setItem.name as string, !!setItem.value); - } - }, - ); - - // Add number values - (this.getNodeParameter('values.number', itemIndex, []) as INodeParameters[]).forEach( - (setItem) => { - if (options.dotNotation === false) { - newItem.json[setItem.name as string] = setItem.value; - } else { - set(newItem.json, setItem.name as string, setItem.value); - } - }, - ); - - // Add string values - (this.getNodeParameter('values.string', itemIndex, []) as INodeParameters[]).forEach( - (setItem) => { - if (options.dotNotation === false) { - newItem.json[setItem.name as string] = setItem.value; - } else { - set(newItem.json, setItem.name as string, setItem.value); - } - }, - ); - - returnData.push(newItem); - } - - return [returnData]; - }, - }, }, }; } diff --git a/packages/core/package.json b/packages/core/package.json index a0886df901..bf5664ea8a 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -43,6 +43,9 @@ "@types/request-promise-native": "~1.0.15", "@types/uuid": "^8.3.2" }, + "peerDependencies": { + "n8n-nodes-base": "workspace:*" + }, "dependencies": { "axios": "^0.21.1", "@n8n/client-oauth2": "workspace:*", diff --git a/packages/core/test/helpers/constants.ts b/packages/core/test/helpers/constants.ts index a566ba8a72..a917fd00d2 100644 --- a/packages/core/test/helpers/constants.ts +++ b/packages/core/test/helpers/constants.ts @@ -1,467 +1,35 @@ -import set from 'lodash/set'; - import type { IExecuteFunctions, INodeExecutionData, - INodeParameters, INodeTypeData, - NodeParameterValue, WorkflowTestData, } from 'n8n-workflow'; -import { deepCopy } from 'n8n-workflow'; +import { If } from 'n8n-nodes-base/nodes/If/If.node'; +import { Merge } from 'n8n-nodes-base/dist/nodes/Merge/Merge.node'; +import { NoOp } from 'n8n-nodes-base/nodes/NoOp/NoOp.node'; +import { Set } from 'n8n-nodes-base/nodes/Set/Set.node'; +import { Start } from 'n8n-nodes-base/nodes/Start/Start.node'; export const predefinedNodesTypes: INodeTypeData = { 'n8n-nodes-base.if': { + type: new If(), sourcePath: '', - type: { - description: { - displayName: 'If', - name: 'if', - group: ['transform'], - version: 1, - description: 'Splits a stream depending on defined compare operations.', - defaults: { - name: 'IF', - color: '#408000', - }, - inputs: ['main'], - outputs: ['main', 'main'], - properties: [ - { - displayName: 'Conditions', - name: 'conditions', - placeholder: 'Add Condition', - type: 'fixedCollection', - typeOptions: { - multipleValues: true, - }, - description: 'The type of values to compare.', - default: {}, - options: [ - { - name: 'boolean', - displayName: 'Boolean', - values: [ - { - displayName: 'Value 1', - name: 'value1', - type: 'boolean', - default: false, - description: 'The value to compare with the second one.', - }, - { - displayName: 'Operation', - name: 'operation', - type: 'options', - options: [ - { - name: 'Equal', - value: 'equal', - }, - { - name: 'Not Equal', - value: 'notEqual', - }, - ], - default: 'equal', - description: 'Operation to decide where the the data should be mapped to.', - }, - { - displayName: 'Value 2', - name: 'value2', - type: 'boolean', - default: false, - description: 'The value to compare with the first one.', - }, - ], - }, - { - name: 'number', - displayName: 'Number', - values: [ - { - displayName: 'Value 1', - name: 'value1', - type: 'number', - default: 0, - description: 'The value to compare with the second one.', - }, - { - displayName: 'Operation', - name: 'operation', - type: 'options', - options: [ - { - name: 'Smaller', - value: 'smaller', - }, - { - name: 'Smaller Equal', - value: 'smallerEqual', - }, - { - name: 'Equal', - value: 'equal', - }, - { - name: 'Not Equal', - value: 'notEqual', - }, - { - name: 'Larger', - value: 'larger', - }, - { - name: 'Larger Equal', - value: 'largerEqual', - }, - { - name: 'Is Empty', - value: 'isEmpty', - }, - ], - default: 'smaller', - description: 'Operation to decide where the the data should be mapped to.', - }, - { - displayName: 'Value 2', - name: 'value2', - type: 'number', - displayOptions: { - hide: { - operation: ['isEmpty'], - }, - }, - default: 0, - description: 'The value to compare with the first one.', - }, - ], - }, - { - name: 'string', - displayName: 'String', - values: [ - { - displayName: 'Value 1', - name: 'value1', - type: 'string', - default: '', - description: 'The value to compare with the second one.', - }, - { - displayName: 'Operation', - name: 'operation', - type: 'options', - options: [ - { - name: 'Contains', - value: 'contains', - }, - { - name: 'Ends With', - value: 'endsWith', - }, - { - name: 'Equal', - value: 'equal', - }, - { - name: 'Not Contains', - value: 'notContains', - }, - { - name: 'Not Equal', - value: 'notEqual', - }, - { - name: 'Regex', - value: 'regex', - }, - { - name: 'Starts With', - value: 'startsWith', - }, - { - name: 'Is Empty', - value: 'isEmpty', - }, - ], - default: 'equal', - description: 'Operation to decide where the the data should be mapped to.', - }, - { - displayName: 'Value 2', - name: 'value2', - type: 'string', - displayOptions: { - hide: { - operation: ['isEmpty', 'regex'], - }, - }, - default: '', - description: 'The value to compare with the first one.', - }, - { - displayName: 'Regex', - name: 'value2', - type: 'string', - displayOptions: { - show: { - operation: ['regex'], - }, - }, - default: '', - placeholder: '/text/i', - description: 'The regex which has to match.', - }, - ], - }, - ], - }, - { - displayName: 'Combine', - name: 'combineOperation', - type: 'options', - options: [ - { - name: 'ALL', - description: 'Only if all conditions are met it goes into "true" branch.', - value: 'all', - }, - { - name: 'ANY', - description: 'If any of the conditions is met it goes into "true" branch.', - value: 'any', - }, - ], - default: 'all', - description: - 'If multiple rules got set this settings decides if it is true as soon as ANY condition matches or only if ALL get meet.', - }, - ], - }, - async execute(this: IExecuteFunctions) { - const returnDataTrue: INodeExecutionData[] = []; - const returnDataFalse: INodeExecutionData[] = []; - - const items = this.getInputData(); - - let item: INodeExecutionData; - let combineOperation: string; - - // The compare operations - const compareOperationFunctions: { - [key: string]: (value1: NodeParameterValue, value2: NodeParameterValue) => boolean; - } = { - contains: (value1: NodeParameterValue, value2: NodeParameterValue) => - (value1 || '').toString().includes((value2 || '').toString()), - notContains: (value1: NodeParameterValue, value2: NodeParameterValue) => - !(value1 || '').toString().includes((value2 || '').toString()), - endsWith: (value1: NodeParameterValue, value2: NodeParameterValue) => - (value1 as string).endsWith(value2 as string), - equal: (value1: NodeParameterValue, value2: NodeParameterValue) => value1 === value2, - notEqual: (value1: NodeParameterValue, value2: NodeParameterValue) => value1 !== value2, - larger: (value1: NodeParameterValue, value2: NodeParameterValue) => - (value1 || 0) > (value2 || 0), - largerEqual: (value1: NodeParameterValue, value2: NodeParameterValue) => - (value1 || 0) >= (value2 || 0), - smaller: (value1: NodeParameterValue, value2: NodeParameterValue) => - (value1 || 0) < (value2 || 0), - smallerEqual: (value1: NodeParameterValue, value2: NodeParameterValue) => - (value1 || 0) <= (value2 || 0), - startsWith: (value1: NodeParameterValue, value2: NodeParameterValue) => - (value1 as string).startsWith(value2 as string), - isEmpty: (value1: NodeParameterValue) => [undefined, null, ''].includes(value1 as string), - regex: (value1: NodeParameterValue, value2: NodeParameterValue) => { - const regexMatch = (value2 || '').toString().match(new RegExp('^/(.*?)/([gimusy]*)$')); - - let regex: RegExp; - if (!regexMatch) { - regex = new RegExp((value2 || '').toString()); - } else if (regexMatch.length === 1) { - regex = new RegExp(regexMatch[1]); - } else { - regex = new RegExp(regexMatch[1], regexMatch[2]); - } - - return !!(value1 || '').toString().match(regex); - }, - }; - - // The different dataTypes to check the values in - const dataTypes = ['boolean', 'number', 'string']; - - // Iterate over all items to check which ones should be output as via output "true" and - // which ones via output "false" - let dataType: string; - let compareOperationResult: boolean; - itemLoop: for (let itemIndex = 0; itemIndex < items.length; itemIndex++) { - item = items[itemIndex]; - - let compareData: INodeParameters; - - combineOperation = this.getNodeParameter('combineOperation', itemIndex) as string; - - // Check all the values of the different dataTypes - for (dataType of dataTypes) { - // Check all the values of the current dataType - for (compareData of this.getNodeParameter( - `conditions.${dataType}`, - itemIndex, - [], - ) as INodeParameters[]) { - // Check if the values passes - compareOperationResult = compareOperationFunctions[compareData.operation as string]( - compareData.value1 as NodeParameterValue, - compareData.value2 as NodeParameterValue, - ); - - if (compareOperationResult && combineOperation === 'any') { - // If it passes and the operation is "any" we do not have to check any - // other ones as it should pass anyway. So go on with the next item. - returnDataTrue.push(item); - continue itemLoop; - } else if (!compareOperationResult && combineOperation === 'all') { - // If it fails and the operation is "all" we do not have to check any - // other ones as it should be not pass anyway. So go on with the next item. - returnDataFalse.push(item); - continue itemLoop; - } - } - } - - if (combineOperation === 'all') { - // If the operation is "all" it means the item did match all conditions - // so it passes. - returnDataTrue.push(item); - } else { - // If the operation is "any" it means the the item did not match any condition. - returnDataFalse.push(item); - } - } - - return [returnDataTrue, returnDataFalse]; - }, - }, }, 'n8n-nodes-base.merge': { + type: new Merge(), sourcePath: '', - type: { - description: { - displayName: 'Merge', - name: 'merge', - icon: 'fa:clone', - group: ['transform'], - version: [1, 2], - requiredInputs: '={{ $version === 2 ? 1 : undefined }}', - description: 'Merges data of multiple streams once data of both is available', - defaults: { - name: 'Merge', - color: '#00cc22', - }, - inputs: ['main', 'main'], - outputs: ['main'], - properties: [ - { - displayName: 'Mode', - name: 'mode', - type: 'options', - options: [ - { - name: 'Append', - value: 'append', - description: - 'Combines data of both inputs. The output will contain items of input 1 and input 2.', - }, - { - name: 'Pass-through', - value: 'passThrough', - description: - 'Passes through data of one input. The output will contain only items of the defined input.', - }, - { - name: 'Wait', - value: 'wait', - description: - 'Waits till data of both inputs is available and will then output a single empty item.', - }, - ], - default: 'append', - description: - 'How data should be merged. If it should simply
be appended or merged depending on a property.', - }, - { - displayName: 'Output Data', - name: 'output', - type: 'options', - displayOptions: { - show: { - mode: ['passThrough'], - }, - }, - options: [ - { - name: 'Input 1', - value: 'input1', - }, - { - name: 'Input 2', - value: 'input2', - }, - ], - default: 'input1', - description: 'Defines of which input the data should be used as output of node.', - }, - ], - }, - async execute(this: IExecuteFunctions) { - const returnData: INodeExecutionData[] = []; - - const mode = this.getNodeParameter('mode', 0) as string; - - if (mode === 'append') { - // Simply appends the data - for (let i = 0; i < 2; i++) { - returnData.push.apply(returnData, this.getInputData(i)); - } - } else if (mode === 'passThrough') { - const output = this.getNodeParameter('output', 0) as string; - - if (output === 'input1') { - returnData.push.apply(returnData, this.getInputData(0)); - } else { - returnData.push.apply(returnData, this.getInputData(1)); - } - } else if (mode === 'wait') { - returnData.push({ json: {} }); - } - - return [returnData]; - }, - }, }, 'n8n-nodes-base.noOp': { + type: new NoOp(), + sourcePath: '', + }, + 'n8n-nodes-base.set': { + type: new Set(), + sourcePath: '', + }, + 'n8n-nodes-base.start': { + type: new Start(), sourcePath: '', - type: { - description: { - displayName: 'No Operation, do nothing', - name: 'noOp', - icon: 'fa:arrow-right', - group: ['organization'], - version: 1, - description: 'No Operation', - defaults: { - name: 'NoOp', - color: '#b0b0b0', - }, - inputs: ['main'], - outputs: ['main'], - properties: [], - }, - async execute(this: IExecuteFunctions) { - return [this.getInputData()]; - }, - }, }, 'n8n-nodes-base.versionTest': { sourcePath: '', @@ -522,220 +90,6 @@ export const predefinedNodesTypes: INodeTypeData = { }, }, }, - 'n8n-nodes-base.set': { - sourcePath: '', - type: { - description: { - displayName: 'Set', - name: 'set', - group: ['input'], - version: 1, - description: 'Sets a value', - defaults: { - name: 'Set', - color: '#0000FF', - }, - inputs: ['main'], - outputs: ['main'], - properties: [ - { - displayName: 'Keep Only Set', - name: 'keepOnlySet', - type: 'boolean', - default: false, - description: - 'If only the values set on this node should be
kept and all others removed.', - }, - { - displayName: 'Values to Set', - name: 'values', - placeholder: 'Add Value', - type: 'fixedCollection', - typeOptions: { - multipleValues: true, - }, - description: 'The value to set.', - default: {}, - options: [ - { - name: 'boolean', - displayName: 'Boolean', - values: [ - { - displayName: 'Name', - name: 'name', - type: 'string', - default: 'propertyName', - description: - 'Name of the property to write data to.
Supports dot-notation.
Example: "data.person[0].name"', - }, - { - displayName: 'Value', - name: 'value', - type: 'boolean', - default: false, - description: 'The boolean value to write in the property.', - }, - ], - }, - { - name: 'number', - displayName: 'Number', - values: [ - { - displayName: 'Name', - name: 'name', - type: 'string', - default: 'propertyName', - description: - 'Name of the property to write data to.
Supports dot-notation.
Example: "data.person[0].name"', - }, - { - displayName: 'Value', - name: 'value', - type: 'number', - default: 0, - description: 'The number value to write in the property.', - }, - ], - }, - { - name: 'string', - displayName: 'String', - values: [ - { - displayName: 'Name', - name: 'name', - type: 'string', - default: 'propertyName', - description: - 'Name of the property to write data to.
Supports dot-notation.
Example: "data.person[0].name"', - }, - { - displayName: 'Value', - name: 'value', - type: 'string', - default: '', - description: 'The string value to write in the property.', - }, - ], - }, - ], - }, - - { - displayName: 'Options', - name: 'options', - type: 'collection', - placeholder: 'Add Option', - default: {}, - options: [ - { - displayName: 'Dot Notation', - name: 'dotNotation', - type: 'boolean', - default: true, - description: - '

By default, dot-notation is used in property names. This means that "a.b" will set the property "b" underneath "a" so { "a": { "b": value} }.

If that is not intended this can be deactivated, it will then set { "a.b": value } instead.

', - }, - ], - }, - ], - }, - async execute(this: IExecuteFunctions) { - const items = this.getInputData(); - - if (items.length === 0) { - items.push({ json: {} }); - } - - const returnData: INodeExecutionData[] = []; - - let item: INodeExecutionData; - let keepOnlySet: boolean; - for (let itemIndex = 0; itemIndex < items.length; itemIndex++) { - keepOnlySet = this.getNodeParameter('keepOnlySet', itemIndex, false) as boolean; - item = items[itemIndex]; - const options = this.getNodeParameter('options', itemIndex, {}); - - const newItem: INodeExecutionData = { - json: {}, - }; - - if (!keepOnlySet) { - if (item.binary !== undefined) { - // Create a shallow copy of the binary data so that the old - // data references which do not get changed still stay behind - // but the incoming data does not get changed. - newItem.binary = {}; - Object.assign(newItem.binary, item.binary); - } - - newItem.json = deepCopy(item.json); - } - - // Add boolean values - (this.getNodeParameter('values.boolean', itemIndex, []) as INodeParameters[]).forEach( - (setItem) => { - if (options.dotNotation === false) { - newItem.json[setItem.name as string] = !!setItem.value; - } else { - set(newItem.json, setItem.name as string, !!setItem.value); - } - }, - ); - - // Add number values - (this.getNodeParameter('values.number', itemIndex, []) as INodeParameters[]).forEach( - (setItem) => { - if (options.dotNotation === false) { - newItem.json[setItem.name as string] = setItem.value; - } else { - set(newItem.json, setItem.name as string, setItem.value); - } - }, - ); - - // Add string values - (this.getNodeParameter('values.string', itemIndex, []) as INodeParameters[]).forEach( - (setItem) => { - if (options.dotNotation === false) { - newItem.json[setItem.name as string] = setItem.value; - } else { - set(newItem.json, setItem.name as string, setItem.value); - } - }, - ); - - returnData.push(newItem); - } - - return [returnData]; - }, - }, - }, - 'n8n-nodes-base.start': { - sourcePath: '', - type: { - description: { - displayName: 'Start', - name: 'start', - group: ['input'], - version: 1, - description: 'Starts the workflow execution from this node', - defaults: { - name: 'Start', - color: '#553399', - }, - inputs: [], - outputs: ['main'], - properties: [], - }, - async execute(this: IExecuteFunctions): Promise { - return [this.getInputData()]; - }, - }, - }, }; export const legacyWorkflowExecuteTests: WorkflowTestData[] = [ diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 22df3e77bb..7b4b32a11d 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -615,6 +615,9 @@ importers: mime-types: specifier: ^2.1.27 version: 2.1.35 + n8n-nodes-base: + specifier: workspace:* + version: link:../nodes-base n8n-workflow: specifier: workspace:* version: link:../workflow