fix(Code Node): Consistent redirection of stdout for JS and Python sandboxes (#6818)

Co-authored-by: Marcus <marcus@n8n.io>
This commit is contained in:
कारतोफ्फेलस्क्रिप्ट™ 2023-08-01 17:47:43 +02:00 committed by GitHub
parent 34df8b6238
commit f718c2291f
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 28 additions and 31 deletions

View file

@ -92,8 +92,9 @@ export class Code implements INodeType {
const nodeMode = this.getNodeParameter('mode', 0) as CodeExecutionMode;
const workflowMode = this.getMode();
const node = this.getNode();
const language: CodeNodeEditorLanguage =
this.getNode()?.typeVersion === 2
node.typeVersion === 2
? (this.getNodeParameter('language', 0) as CodeNodeEditorLanguage)
: 'javaScript';
const codeParameterName = language === 'python' ? 'pythonCode' : 'jsCode';
@ -107,16 +108,16 @@ export class Code implements INodeType {
context.item = context.$input.item;
}
if (language === 'python') {
context.printOverwrite = workflowMode === 'manual' ? this.sendMessageToUI : null;
return new PythonSandbox(context, code, index, this.helpers);
} else {
const sandbox = new JavaScriptSandbox(context, code, index, workflowMode, this.helpers);
if (workflowMode === 'manual') {
sandbox.vm.on('console.log', this.sendMessageToUI);
}
return sandbox;
}
const Sandbox = language === 'python' ? PythonSandbox : JavaScriptSandbox;
const sandbox = new Sandbox(context, code, index, this.helpers);
sandbox.on(
'output',
workflowMode === 'manual'
? this.sendMessageToUI
: (...args) =>
console.log(`[Workflow "${this.getWorkflow().id}"][Node "${node.name}"]`, ...args),
);
return sandbox;
};
// ----------------------------------

View file

@ -1,6 +1,5 @@
import type { NodeVMOptions } from 'vm2';
import { NodeVM, makeResolverFromLegacyOptions } from 'vm2';
import type { IExecuteFunctions, INodeExecutionData, WorkflowExecuteMode } from 'n8n-workflow';
import type { IExecuteFunctions, INodeExecutionData } from 'n8n-workflow';
import { ValidationError } from './ValidationError';
import { ExecutionError } from './ExecutionError';
@ -20,23 +19,13 @@ export const vmResolver = makeResolverFromLegacyOptions({
builtin: builtIn?.split(',') ?? [],
});
const getSandboxOptions = (
context: SandboxContext,
workflowMode: WorkflowExecuteMode,
): NodeVMOptions => ({
console: workflowMode === 'manual' ? 'redirect' : 'inherit',
sandbox: context,
require: vmResolver,
});
export class JavaScriptSandbox extends Sandbox {
readonly vm: NodeVM;
private readonly vm: NodeVM;
constructor(
context: SandboxContext,
private jsCode: string,
itemIndex: number | undefined,
workflowMode: WorkflowExecuteMode,
helpers: IExecuteFunctions['helpers'],
) {
super(
@ -49,7 +38,13 @@ export class JavaScriptSandbox extends Sandbox {
itemIndex,
helpers,
);
this.vm = new NodeVM(getSandboxOptions(context, workflowMode));
this.vm = new NodeVM({
console: 'redirect',
sandbox: context,
require: vmResolver,
});
this.vm.on('console.log', (...args: unknown[]) => this.emit('output', ...args));
}
async runCodeAllItems(): Promise<INodeExecutionData[]> {

View file

@ -67,10 +67,7 @@ export class PythonSandbox extends Sandbox {
globalsDict.set(key, value);
}
await pyodide.runPythonAsync(`
if 'printOverwrite' in globals():
print = printOverwrite
`);
pyodide.setStdout({ batched: (str) => this.emit('output', str) });
const runCode = `
async def __main():

View file

@ -1,3 +1,4 @@
import { EventEmitter } from 'events';
import { ValidationError } from './ValidationError';
import { isObject } from './utils';
@ -31,12 +32,14 @@ export function getSandboxContext(this: IExecuteFunctions, index: number): Sandb
};
}
export abstract class Sandbox {
export abstract class Sandbox extends EventEmitter {
constructor(
private textKeys: SandboxTextKeys,
protected itemIndex: number | undefined,
protected helpers: IExecuteFunctions['helpers'],
) {}
) {
super();
}
abstract runCodeAllItems(): Promise<INodeExecutionData[]>;

View file

@ -20,6 +20,7 @@ describe('Test Code Node', () => {
describe('Code Node unit test', () => {
const node = new Code();
const thisArg = mock<IExecuteFunctions>({
getNode: () => mock(),
helpers: { normalizeItems },
prepareOutputData: NodeHelpers.prepareOutputData,
});