mirror of
https://github.com/n8n-io/n8n.git
synced 2026-05-12 16:10:30 +02:00
207 lines
6.0 KiB
TypeScript
207 lines
6.0 KiB
TypeScript
/* eslint-disable n8n-nodes-base/node-execute-block-wrong-error-thrown */
|
|
import { NodesConfig } from '@n8n/config';
|
|
import { Container } from '@n8n/di';
|
|
import {
|
|
NodeConnectionTypes,
|
|
UnexpectedError,
|
|
UserError,
|
|
type CodeExecutionMode,
|
|
type CodeNodeEditorLanguage,
|
|
type IExecuteFunctions,
|
|
type INodeType,
|
|
type INodeTypeDescription,
|
|
} from 'n8n-workflow';
|
|
|
|
type CodeNodeLanguageOption = CodeNodeEditorLanguage | 'pythonNative';
|
|
|
|
import { javascriptCodeDescription } from './descriptions/JavascriptCodeDescription';
|
|
import { pythonCodeDescription } from './descriptions/PythonCodeDescription';
|
|
import { JsTaskRunnerSandbox } from './JsTaskRunnerSandbox';
|
|
import { PythonRunnerUnavailableError } from './python-runner-unavailable.error';
|
|
import { PythonTaskRunnerSandbox } from './PythonTaskRunnerSandbox';
|
|
|
|
class PythonDisabledError extends UserError {
|
|
constructor() {
|
|
super(
|
|
'This instance disallows Python execution because it has the environment variable `N8N_PYTHON_ENABLED` set to `false`. To restore Python execution, remove this environment variable or set it to `true` and restart the instance.',
|
|
);
|
|
}
|
|
}
|
|
|
|
export class Code implements INodeType {
|
|
description: INodeTypeDescription = {
|
|
displayName: 'Code',
|
|
name: 'code',
|
|
icon: 'node:code',
|
|
iconColor: 'amber',
|
|
group: ['transform'],
|
|
version: [1, 2],
|
|
defaultVersion: 2,
|
|
description: 'Run custom JavaScript or Python code',
|
|
defaults: {
|
|
name: 'Code',
|
|
},
|
|
inputs: [NodeConnectionTypes.Main],
|
|
outputs: [NodeConnectionTypes.Main],
|
|
builderHint: {
|
|
searchHint:
|
|
'Use Code node as a LAST RESORT — it runs in a sandboxed environment and is slower than native nodes. Code node is ONLY appropriate for complex multi-step algorithms that cannot be expressed in single expressions, or operations requiring complex data structures.',
|
|
relatedNodes: [
|
|
{
|
|
nodeType: 'n8n-nodes-base.set',
|
|
relationHint:
|
|
'Use this instead for data manipulation: add/modify/rename fields, set values, map data',
|
|
},
|
|
{
|
|
nodeType: 'n8n-nodes-base.filter',
|
|
relationHint: 'Use this instead for filtering items by condition',
|
|
},
|
|
{
|
|
nodeType: 'n8n-nodes-base.if',
|
|
relationHint: 'Use this instead for routing by condition',
|
|
},
|
|
{
|
|
nodeType: 'n8n-nodes-base.switch',
|
|
relationHint: 'Use this instead for multi-way routing by condition',
|
|
},
|
|
{
|
|
nodeType: 'n8n-nodes-base.splitOut',
|
|
relationHint: 'Use this instead for splitting arrays into separate items',
|
|
},
|
|
{
|
|
nodeType: 'n8n-nodes-base.aggregate',
|
|
relationHint: 'Use this instead for combining multiple items into one',
|
|
},
|
|
{
|
|
nodeType: 'n8n-nodes-base.summarize',
|
|
relationHint: 'Use this instead for summarizing or pivoting data',
|
|
},
|
|
{
|
|
nodeType: 'n8n-nodes-base.removeDuplicates',
|
|
relationHint: 'Use this instead for removing duplicates',
|
|
},
|
|
{
|
|
nodeType: 'n8n-nodes-base.limit',
|
|
relationHint: 'Use this instead to reduce the number of items returned',
|
|
},
|
|
{
|
|
nodeType: 'n8n-nodes-base.merge',
|
|
relationHint: 'Use this instead for merging data from multiple branches',
|
|
},
|
|
{
|
|
nodeType: 'n8n-nodes-base.dateTime',
|
|
relationHint: 'Use this instead for date time operations',
|
|
},
|
|
{
|
|
nodeType: 'n8n-nodes-base.html',
|
|
relationHint: 'Use this instead for creating html pages',
|
|
},
|
|
],
|
|
},
|
|
parameterPane: 'wide',
|
|
properties: [
|
|
{
|
|
displayName: 'Mode',
|
|
name: 'mode',
|
|
type: 'options',
|
|
noDataExpression: true,
|
|
options: [
|
|
{
|
|
name: 'Run Once for All Items',
|
|
value: 'runOnceForAllItems',
|
|
description: 'Run this code only once, no matter how many input items there are',
|
|
},
|
|
{
|
|
name: 'Run Once for Each Item',
|
|
value: 'runOnceForEachItem',
|
|
description: 'Run this code as many times as there are input items',
|
|
},
|
|
],
|
|
default: 'runOnceForAllItems',
|
|
},
|
|
{
|
|
displayName: 'Language',
|
|
name: 'language',
|
|
type: 'options',
|
|
noDataExpression: true,
|
|
displayOptions: {
|
|
show: {
|
|
'@version': [2],
|
|
},
|
|
},
|
|
options: [
|
|
{
|
|
name: 'JavaScript',
|
|
value: 'javaScript',
|
|
action: 'Code in JavaScript',
|
|
},
|
|
{
|
|
name: 'Python',
|
|
value: 'pythonNative',
|
|
action: 'Code in Python',
|
|
},
|
|
],
|
|
default: 'javaScript',
|
|
},
|
|
{
|
|
displayName: 'Language',
|
|
name: 'language',
|
|
type: 'hidden',
|
|
displayOptions: {
|
|
show: {
|
|
'@version': [1],
|
|
},
|
|
},
|
|
default: 'javaScript',
|
|
},
|
|
|
|
...javascriptCodeDescription,
|
|
...pythonCodeDescription,
|
|
],
|
|
};
|
|
|
|
async execute(this: IExecuteFunctions) {
|
|
const node = this.getNode();
|
|
const language: CodeNodeLanguageOption =
|
|
node.typeVersion === 2
|
|
? (this.getNodeParameter('language', 0) as CodeNodeLanguageOption)
|
|
: 'javaScript';
|
|
|
|
const isJsLang = language === 'javaScript';
|
|
const isPyLang = language === 'python' || language === 'pythonNative'; // keep legacy `python` for backwards compatibility
|
|
if (isPyLang && !Container.get(NodesConfig).pythonEnabled) {
|
|
throw new PythonDisabledError();
|
|
}
|
|
|
|
const nodeMode = this.getNodeParameter('mode', 0) as CodeExecutionMode;
|
|
const workflowMode = this.getMode();
|
|
const codeParameterName = isPyLang ? 'pythonCode' : 'jsCode';
|
|
|
|
if (isJsLang) {
|
|
const code = this.getNodeParameter(codeParameterName, 0) as string;
|
|
const sandbox = new JsTaskRunnerSandbox(workflowMode, this);
|
|
const numInputItems = this.getInputData().length;
|
|
|
|
return nodeMode === 'runOnceForAllItems'
|
|
? [await sandbox.runCodeAllItems(code)]
|
|
: [await sandbox.runCodeForEachItem(code, numInputItems)];
|
|
}
|
|
|
|
if (isPyLang) {
|
|
const runnerStatus = this.getRunnerStatus('python');
|
|
if (!runnerStatus.available) {
|
|
throw new PythonRunnerUnavailableError(
|
|
runnerStatus.reason as 'python' | 'venv' | undefined,
|
|
);
|
|
}
|
|
|
|
const code = this.getNodeParameter(codeParameterName, 0) as string;
|
|
const sandbox = new PythonTaskRunnerSandbox(code, nodeMode, workflowMode, this);
|
|
|
|
return [await sandbox.runUsingIncomingItems()];
|
|
}
|
|
|
|
throw new UnexpectedError(`Unsupported language: ${language}`);
|
|
}
|
|
}
|