first commit
Security: Sync from Public / sync-from-public (push) Has been cancelled
Test: Benchmark Nightly / build (push) Has been cancelled
Test: Benchmark Nightly / Notify Cats on failure (push) Has been cancelled
CI: Python / Checks (push) Has been cancelled
Test: Evals Python / Workflow Comparison Python (push) Has been cancelled
Util: Check Docs URLs / check-docs-urls (push) Has been cancelled
Test: Visual Storybook / Cloudflare Pages (push) Has been cancelled
Test: E2E Performance / build-and-test-performance (push) Has been cancelled
Test: Workflows Nightly / Run Workflow Tests (push) Has been cancelled
Util: Cleanup CI Docker Images / Delete stale CI images (push) Has been cancelled
Test: Benchmark Destroy Env / build (push) Has been cancelled
Util: Update Node Popularity / update-popularity (push) Has been cancelled
Test: E2E Coverage Weekly / Coverage Tests (push) Has been cancelled
Security: Sync from Public / sync-from-public (push) Has been cancelled
Test: Benchmark Nightly / build (push) Has been cancelled
Test: Benchmark Nightly / Notify Cats on failure (push) Has been cancelled
CI: Python / Checks (push) Has been cancelled
Test: Evals Python / Workflow Comparison Python (push) Has been cancelled
Util: Check Docs URLs / check-docs-urls (push) Has been cancelled
Test: Visual Storybook / Cloudflare Pages (push) Has been cancelled
Test: E2E Performance / build-and-test-performance (push) Has been cancelled
Test: Workflows Nightly / Run Workflow Tests (push) Has been cancelled
Util: Cleanup CI Docker Images / Delete stale CI images (push) Has been cancelled
Test: Benchmark Destroy Env / build (push) Has been cancelled
Util: Update Node Popularity / update-popularity (push) Has been cancelled
Test: E2E Coverage Weekly / Coverage Tests (push) Has been cancelled
This commit is contained in:
@@ -0,0 +1,205 @@
|
||||
/* 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: 'file:code.svg',
|
||||
group: ['transform'],
|
||||
version: [1, 2],
|
||||
defaultVersion: 2,
|
||||
description: 'Run custom JavaScript or Python code',
|
||||
defaults: {
|
||||
name: 'Code',
|
||||
},
|
||||
inputs: [NodeConnectionTypes.Main],
|
||||
outputs: [NodeConnectionTypes.Main],
|
||||
builderHint: {
|
||||
message:
|
||||
'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}`);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user