n8n/packages/nodes-base/nodes/Code/Code.node.ts
Mutasem Aldmour 72eca2f398
refactor: Rename node-level builderHint.message to searchHint and propertyHint (#30062)
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-08 13:32:50 +00:00

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