/* 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}`); } }