refactor: Use actual nodes/credentials in cli and core tests (no-changelog) (#7115)

This commit is contained in:
कारतोफ्फेलस्क्रिप्ट™ 2023-09-06 10:23:47 +02:00 committed by GitHub
parent a910757cc5
commit 2f647974f4
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 44 additions and 999 deletions

View file

@ -252,19 +252,17 @@ describe('GET /credentials/schema/:credentialType', () => {
}); });
test('should retrieve credential type', async () => { 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; const { additionalProperties, type, properties, required } = response.body;
expect(additionalProperties).toBe(false); expect(additionalProperties).toBe(false);
expect(type).toBe('object'); expect(type).toBe('object');
expect(properties.server).toBeDefined(); expect(properties.host.type).toBe('string');
expect(properties.server.type).toBe('string'); expect(properties.port.type).toBe('number');
expect(properties.user.type).toBeDefined(); expect(properties.username.type).toBe('string');
expect(properties.user.type).toBe('string'); expect(properties.password.type).toBe('string');
expect(properties.accessToken.type).toBeDefined(); expect(required).toEqual(expect.arrayContaining(['host', 'port']));
expect(properties.accessToken.type).toBe('string');
expect(required).toEqual(expect.arrayContaining(['server', 'user', 'accessToken']));
expect(response.statusCode).toBe(200); expect(response.statusCode).toBe(200);
}); });
}); });
@ -301,9 +299,9 @@ const INVALID_PAYLOADS = [
}, },
{ {
name: randomName(), name: randomName(),
type: 'githubApi', type: 'ftp',
data: { data: {
server: randomName(), username: randomName(),
}, },
}, },
{}, {},

View file

@ -1,20 +1,13 @@
import { Container } from 'typedi'; import { Container } from 'typedi';
import { randomBytes } from 'crypto'; import { randomBytes } from 'crypto';
import { existsSync } from 'fs'; import { existsSync } from 'fs';
import { CronJob } from 'cron';
import set from 'lodash/set';
import { BinaryDataManager, UserSettings } from 'n8n-core'; import { BinaryDataManager, UserSettings } from 'n8n-core';
import type { import type { INode } from 'n8n-workflow';
ICredentialType, import { GithubApi } from 'n8n-nodes-base/credentials/GithubApi.credentials';
IExecuteFunctions, import { Ftp } from 'n8n-nodes-base/credentials/Ftp.credentials';
INode, import { Cron } from 'n8n-nodes-base/nodes/Cron/Cron.node';
INodeExecutionData, import { Set } from 'n8n-nodes-base/nodes/Set/Set.node';
INodeParameters, import { Start } from 'n8n-nodes-base/nodes/Start/Start.node';
ITriggerFunctions,
TriggerTime,
} from 'n8n-workflow';
import { deepCopy } from 'n8n-workflow';
import { NodeHelpers, toCronExpression } from 'n8n-workflow';
import type request from 'supertest'; import type request from 'supertest';
import { v4 as uuid } from 'uuid'; import { v4 as uuid } from 'uuid';
@ -42,45 +35,17 @@ export async function initActiveWorkflowRunner(): Promise<ActiveWorkflowRunner>
return workflowRunner; 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. * Initialize node types.
*/ */
export async function initCredentialsTypes(): Promise<void> { export async function initCredentialsTypes(): Promise<void> {
Container.get(LoadNodesAndCredentials).loaded.credentials = { Container.get(LoadNodesAndCredentials).loaded.credentials = {
githubApi: { githubApi: {
type: gitHubCredentialType(), type: new GithubApi(),
sourcePath: '',
},
ftp: {
type: new Ftp(),
sourcePath: '', sourcePath: '',
}, },
}; };
@ -92,294 +57,16 @@ export async function initCredentialsTypes(): Promise<void> {
export async function initNodeTypes() { export async function initNodeTypes() {
Container.get(LoadNodesAndCredentials).loaded.nodes = { Container.get(LoadNodesAndCredentials).loaded.nodes = {
'n8n-nodes-base.start': { 'n8n-nodes-base.start': {
type: new Start(),
sourcePath: '', 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': { 'n8n-nodes-base.cron': {
type: new Cron(),
sourcePath: '', 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': { 'n8n-nodes-base.set': {
type: new Set(),
sourcePath: '', 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: `<p>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} }.<p></p>If that is not intended this can be deactivated, it will then set { "a.b": value } instead.</p>
`,
},
],
},
],
},
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];
},
},
}, },
}; };
} }

View file

@ -43,6 +43,9 @@
"@types/request-promise-native": "~1.0.15", "@types/request-promise-native": "~1.0.15",
"@types/uuid": "^8.3.2" "@types/uuid": "^8.3.2"
}, },
"peerDependencies": {
"n8n-nodes-base": "workspace:*"
},
"dependencies": { "dependencies": {
"axios": "^0.21.1", "axios": "^0.21.1",
"@n8n/client-oauth2": "workspace:*", "@n8n/client-oauth2": "workspace:*",

View file

@ -1,467 +1,35 @@
import set from 'lodash/set';
import type { import type {
IExecuteFunctions, IExecuteFunctions,
INodeExecutionData, INodeExecutionData,
INodeParameters,
INodeTypeData, INodeTypeData,
NodeParameterValue,
WorkflowTestData, WorkflowTestData,
} from 'n8n-workflow'; } 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 = { export const predefinedNodesTypes: INodeTypeData = {
'n8n-nodes-base.if': { 'n8n-nodes-base.if': {
type: new If(),
sourcePath: '', 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': { 'n8n-nodes-base.merge': {
type: new Merge(),
sourcePath: '', 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<br />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': { 'n8n-nodes-base.noOp': {
type: new NoOp(),
sourcePath: '', 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.set': {
type: new Set(),
sourcePath: '',
}, },
'n8n-nodes-base.start': {
type: new Start(),
sourcePath: '',
}, },
'n8n-nodes-base.versionTest': { 'n8n-nodes-base.versionTest': {
sourcePath: '', 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<br />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.<br />Supports dot-notation.<br />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.<br />Supports dot-notation.<br />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.<br />Supports dot-notation.<br />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:
'<p>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} }.</p><p>If that is not intended this can be deactivated, it will then set { "a.b": value } instead.</p>',
},
],
},
],
},
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<INodeExecutionData[][]> {
return [this.getInputData()];
},
},
},
}; };
export const legacyWorkflowExecuteTests: WorkflowTestData[] = [ export const legacyWorkflowExecuteTests: WorkflowTestData[] = [

View file

@ -615,6 +615,9 @@ importers:
mime-types: mime-types:
specifier: ^2.1.27 specifier: ^2.1.27
version: 2.1.35 version: 2.1.35
n8n-nodes-base:
specifier: workspace:*
version: link:../nodes-base
n8n-workflow: n8n-workflow:
specifier: workspace:* specifier: workspace:*
version: link:../workflow version: link:../workflow