mirror of
https://github.com/Abdulazizzn/n8n-enterprise-unlocked.git
synced 2025-12-17 10:02:05 +00:00
refactor(core): Remove legacy expression evaluator (#14518)
This commit is contained in:
@@ -1,4 +1,4 @@
|
|||||||
import type { ExpressionEvaluatorType, LogLevel, WorkflowSettings } from 'n8n-workflow';
|
import type { LogLevel, WorkflowSettings } from 'n8n-workflow';
|
||||||
|
|
||||||
import { type InsightsDateRange } from './schemas/insights.schema';
|
import { type InsightsDateRange } from './schemas/insights.schema';
|
||||||
|
|
||||||
@@ -157,9 +157,6 @@ export interface FrontendSettings {
|
|||||||
variables: {
|
variables: {
|
||||||
limit: number;
|
limit: number;
|
||||||
};
|
};
|
||||||
expressions: {
|
|
||||||
evaluator: ExpressionEvaluatorType;
|
|
||||||
};
|
|
||||||
mfa: {
|
mfa: {
|
||||||
enabled: boolean;
|
enabled: boolean;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -25,7 +25,6 @@ import { DeprecationService } from '@/deprecation/deprecation.service';
|
|||||||
import { TestRunnerService } from '@/evaluation.ee/test-runner/test-runner.service.ee';
|
import { TestRunnerService } from '@/evaluation.ee/test-runner/test-runner.service.ee';
|
||||||
import { MessageEventBus } from '@/eventbus/message-event-bus/message-event-bus';
|
import { MessageEventBus } from '@/eventbus/message-event-bus/message-event-bus';
|
||||||
import { TelemetryEventRelay } from '@/events/relays/telemetry.event-relay';
|
import { TelemetryEventRelay } from '@/events/relays/telemetry.event-relay';
|
||||||
import { initExpressionEvaluator } from '@/expression-evaluator';
|
|
||||||
import { ExternalHooks } from '@/external-hooks';
|
import { ExternalHooks } from '@/external-hooks';
|
||||||
import { ExternalSecretsManager } from '@/external-secrets.ee/external-secrets-manager.ee';
|
import { ExternalSecretsManager } from '@/external-secrets.ee/external-secrets-manager.ee';
|
||||||
import { License } from '@/license';
|
import { License } from '@/license';
|
||||||
@@ -116,7 +115,6 @@ export abstract class BaseCommand extends Command {
|
|||||||
serverName: deploymentName,
|
serverName: deploymentName,
|
||||||
releaseDate: N8N_RELEASE_DATE,
|
releaseDate: N8N_RELEASE_DATE,
|
||||||
});
|
});
|
||||||
initExpressionEvaluator();
|
|
||||||
|
|
||||||
process.once('SIGTERM', this.onTerminationSignal('SIGTERM'));
|
process.once('SIGTERM', this.onTerminationSignal('SIGTERM'));
|
||||||
process.once('SIGINT', this.onTerminationSignal('SIGINT'));
|
process.once('SIGINT', this.onTerminationSignal('SIGINT'));
|
||||||
|
|||||||
@@ -279,21 +279,6 @@ export const schema = {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
expression: {
|
|
||||||
evaluator: {
|
|
||||||
doc: 'Expression evaluator to use',
|
|
||||||
format: ['tmpl', 'tournament'] as const,
|
|
||||||
default: 'tournament',
|
|
||||||
env: 'N8N_EXPRESSION_EVALUATOR',
|
|
||||||
},
|
|
||||||
reportDifference: {
|
|
||||||
doc: 'Whether to report differences in the evaluator outputs',
|
|
||||||
format: Boolean,
|
|
||||||
default: false,
|
|
||||||
env: 'N8N_EXPRESSION_REPORT_DIFFERENCE',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
|
|
||||||
proxy_hops: {
|
proxy_hops: {
|
||||||
format: Number,
|
format: Number,
|
||||||
default: 0,
|
default: 0,
|
||||||
|
|||||||
@@ -76,6 +76,14 @@ export class DeprecationService {
|
|||||||
envVar: 'N8N_PARTIAL_EXECUTION_VERSION_DEFAULT',
|
envVar: 'N8N_PARTIAL_EXECUTION_VERSION_DEFAULT',
|
||||||
message: 'This environment variable is internal and should not be set.',
|
message: 'This environment variable is internal and should not be set.',
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
envVar: 'N8N_EXPRESSION_EVALUATOR',
|
||||||
|
message: `n8n has replaced \`tmpl\` with \`tournament\` as expression evaluator. ${SAFE_TO_REMOVE}`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
envVar: 'N8N_EXPRESSION_REPORT_DIFFERENCE',
|
||||||
|
message: `n8n has replaced \`tmpl\` with \`tournament\` as expression evaluator. ${SAFE_TO_REMOVE}`,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
envVar: 'EXECUTIONS_PROCESS',
|
envVar: 'EXECUTIONS_PROCESS',
|
||||||
message: SAFE_TO_REMOVE,
|
message: SAFE_TO_REMOVE,
|
||||||
|
|||||||
@@ -1,17 +0,0 @@
|
|||||||
import { Container } from '@n8n/di';
|
|
||||||
import { ErrorReporter } from 'n8n-core';
|
|
||||||
import { ExpressionEvaluatorProxy } from 'n8n-workflow';
|
|
||||||
|
|
||||||
import config from '@/config';
|
|
||||||
|
|
||||||
export const initExpressionEvaluator = () => {
|
|
||||||
ExpressionEvaluatorProxy.setEvaluator(config.getEnv('expression.evaluator'));
|
|
||||||
ExpressionEvaluatorProxy.setDifferEnabled(config.getEnv('expression.reportDifference'));
|
|
||||||
ExpressionEvaluatorProxy.setDiffReporter((expr) => {
|
|
||||||
Container.get(ErrorReporter).warn('Expression difference', {
|
|
||||||
extra: {
|
|
||||||
expression: expr,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
});
|
|
||||||
};
|
|
||||||
@@ -220,9 +220,6 @@ export class FrontendService {
|
|||||||
variables: {
|
variables: {
|
||||||
limit: 0,
|
limit: 0,
|
||||||
},
|
},
|
||||||
expressions: {
|
|
||||||
evaluator: config.getEnv('expression.evaluator'),
|
|
||||||
},
|
|
||||||
banners: {
|
banners: {
|
||||||
dismissed: [],
|
dismissed: [],
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -12,12 +12,7 @@ import type {
|
|||||||
IWorkflowExecuteAdditionalData,
|
IWorkflowExecuteAdditionalData,
|
||||||
WorkflowTestData,
|
WorkflowTestData,
|
||||||
} from 'n8n-workflow';
|
} from 'n8n-workflow';
|
||||||
import {
|
import { createDeferredPromise, UnexpectedError, Workflow } from 'n8n-workflow';
|
||||||
createDeferredPromise,
|
|
||||||
ExpressionEvaluatorProxy,
|
|
||||||
UnexpectedError,
|
|
||||||
Workflow,
|
|
||||||
} from 'n8n-workflow';
|
|
||||||
import nock from 'nock';
|
import nock from 'nock';
|
||||||
import { readFileSync, mkdtempSync, existsSync, rmSync } from 'node:fs';
|
import { readFileSync, mkdtempSync, existsSync, rmSync } from 'node:fs';
|
||||||
import { tmpdir } from 'node:os';
|
import { tmpdir } from 'node:os';
|
||||||
@@ -49,7 +44,6 @@ export class NodeTestHarness {
|
|||||||
private readonly packagePaths: string[];
|
private readonly packagePaths: string[];
|
||||||
|
|
||||||
constructor({ additionalPackagePaths }: TestHarnessOptions = {}) {
|
constructor({ additionalPackagePaths }: TestHarnessOptions = {}) {
|
||||||
ExpressionEvaluatorProxy.setEvaluator('tournament');
|
|
||||||
this.testDir = path.dirname(callsites()[1].getFileName()!);
|
this.testDir = path.dirname(callsites()[1].getFileName()!);
|
||||||
this.packagePaths = additionalPackagePaths ?? [];
|
this.packagePaths = additionalPackagePaths ?? [];
|
||||||
this.packagePaths.unshift(this.packageDir);
|
this.packagePaths.unshift(this.packageDir);
|
||||||
|
|||||||
@@ -44,9 +44,6 @@ export const defaultSettings: FrontendSettings = {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
expressions: {
|
|
||||||
evaluator: 'tournament',
|
|
||||||
},
|
|
||||||
executionMode: 'regular',
|
executionMode: 'regular',
|
||||||
isMultiMain: false,
|
isMultiMain: false,
|
||||||
executionTimeout: 0,
|
executionTimeout: 0,
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import { ref } from 'vue';
|
|||||||
import { useHistoryStore } from '@/stores/history.store';
|
import { useHistoryStore } from '@/stores/history.store';
|
||||||
import { CUSTOM_API_CALL_KEY, PLACEHOLDER_FILLED_AT_EXECUTION_TIME } from '@/constants';
|
import { CUSTOM_API_CALL_KEY, PLACEHOLDER_FILLED_AT_EXECUTION_TIME } from '@/constants';
|
||||||
|
|
||||||
import { NodeHelpers, ExpressionEvaluatorProxy, NodeConnectionTypes } from 'n8n-workflow';
|
import { NodeHelpers, NodeConnectionTypes } from 'n8n-workflow';
|
||||||
import type {
|
import type {
|
||||||
INodeProperties,
|
INodeProperties,
|
||||||
INodeCredentialDescription,
|
INodeCredentialDescription,
|
||||||
@@ -37,7 +37,6 @@ import type {
|
|||||||
|
|
||||||
import { isString } from '@/utils/typeGuards';
|
import { isString } from '@/utils/typeGuards';
|
||||||
import { isObject } from '@/utils/objectUtils';
|
import { isObject } from '@/utils/objectUtils';
|
||||||
import { useSettingsStore } from '@/stores/settings.store';
|
|
||||||
import { useWorkflowsStore } from '@/stores/workflows.store';
|
import { useWorkflowsStore } from '@/stores/workflows.store';
|
||||||
import { useNodeTypesStore } from '@/stores/nodeTypes.store';
|
import { useNodeTypesStore } from '@/stores/nodeTypes.store';
|
||||||
import { useCredentialsStore } from '@/stores/credentials.store';
|
import { useCredentialsStore } from '@/stores/credentials.store';
|
||||||
@@ -702,9 +701,6 @@ export function useNodeHelpers() {
|
|||||||
|
|
||||||
if (nodeType?.subtitle !== undefined) {
|
if (nodeType?.subtitle !== undefined) {
|
||||||
try {
|
try {
|
||||||
ExpressionEvaluatorProxy.setEvaluator(
|
|
||||||
useSettingsStore().settings.expressions?.evaluator ?? 'tmpl',
|
|
||||||
);
|
|
||||||
return workflow.expression.getSimpleParameterValue(
|
return workflow.expression.getSimpleParameterValue(
|
||||||
data,
|
data,
|
||||||
nodeType.subtitle,
|
nodeType.subtitle,
|
||||||
|
|||||||
@@ -24,12 +24,7 @@ import type {
|
|||||||
NodeParameterValue,
|
NodeParameterValue,
|
||||||
Workflow,
|
Workflow,
|
||||||
} from 'n8n-workflow';
|
} from 'n8n-workflow';
|
||||||
import {
|
import { NodeConnectionTypes, NodeHelpers, WEBHOOK_NODE_TYPE } from 'n8n-workflow';
|
||||||
NodeConnectionTypes,
|
|
||||||
ExpressionEvaluatorProxy,
|
|
||||||
NodeHelpers,
|
|
||||||
WEBHOOK_NODE_TYPE,
|
|
||||||
} from 'n8n-workflow';
|
|
||||||
|
|
||||||
import type {
|
import type {
|
||||||
ICredentialsResponse,
|
ICredentialsResponse,
|
||||||
@@ -60,7 +55,6 @@ import { useTemplatesStore } from '@/stores/templates.store';
|
|||||||
import { useUIStore } from '@/stores/ui.store';
|
import { useUIStore } from '@/stores/ui.store';
|
||||||
import { useWorkflowsStore } from '@/stores/workflows.store';
|
import { useWorkflowsStore } from '@/stores/workflows.store';
|
||||||
import { getSourceItems } from '@/utils/pairedItemUtils';
|
import { getSourceItems } from '@/utils/pairedItemUtils';
|
||||||
import { useSettingsStore } from '@/stores/settings.store';
|
|
||||||
import { getCredentialTypeName, isCredentialOnlyNodeType } from '@/utils/credentialOnlyNodes';
|
import { getCredentialTypeName, isCredentialOnlyNodeType } from '@/utils/credentialOnlyNodes';
|
||||||
import { useDocumentTitle } from '@/composables/useDocumentTitle';
|
import { useDocumentTitle } from '@/composables/useDocumentTitle';
|
||||||
import { useExternalHooks } from '@/composables/useExternalHooks';
|
import { useExternalHooks } from '@/composables/useExternalHooks';
|
||||||
@@ -229,9 +223,6 @@ export function resolveParameter<T = IDataObject>(
|
|||||||
_executeData = executeData(parentNode, contextNode!.name, inputName, runIndexParent);
|
_executeData = executeData(parentNode, contextNode!.name, inputName, runIndexParent);
|
||||||
}
|
}
|
||||||
|
|
||||||
ExpressionEvaluatorProxy.setEvaluator(
|
|
||||||
useSettingsStore().settings.expressions?.evaluator ?? 'tmpl',
|
|
||||||
);
|
|
||||||
return workflow.expression.getParameterValue(
|
return workflow.expression.getParameterValue(
|
||||||
parameter,
|
parameter,
|
||||||
runExecutionData,
|
runExecutionData,
|
||||||
|
|||||||
@@ -10,7 +10,6 @@ import type { ILdapConfig } from '@/Interface';
|
|||||||
import { STORES, INSECURE_CONNECTION_WARNING } from '@/constants';
|
import { STORES, INSECURE_CONNECTION_WARNING } from '@/constants';
|
||||||
import { UserManagementAuthenticationMethod } from '@/Interface';
|
import { UserManagementAuthenticationMethod } from '@/Interface';
|
||||||
import type { IDataObject, WorkflowSettings } from 'n8n-workflow';
|
import type { IDataObject, WorkflowSettings } from 'n8n-workflow';
|
||||||
import { ExpressionEvaluatorProxy } from 'n8n-workflow';
|
|
||||||
import { defineStore } from 'pinia';
|
import { defineStore } from 'pinia';
|
||||||
import { useRootStore } from './root.store';
|
import { useRootStore } from './root.store';
|
||||||
import { useUIStore } from './ui.store';
|
import { useUIStore } from './ui.store';
|
||||||
@@ -300,8 +299,6 @@ export const useSettingsStore = defineStore(STORES.SETTINGS, () => {
|
|||||||
try {
|
try {
|
||||||
await getSettings();
|
await getSettings();
|
||||||
|
|
||||||
ExpressionEvaluatorProxy.setEvaluator(settings.value.expressions.evaluator);
|
|
||||||
|
|
||||||
initialized.value = true;
|
initialized.value = true;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
showToast({
|
showToast({
|
||||||
|
|||||||
@@ -43,7 +43,6 @@
|
|||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@n8n/tournament": "1.0.6",
|
"@n8n/tournament": "1.0.6",
|
||||||
"@n8n_io/riot-tmpl": "4.0.0",
|
|
||||||
"ast-types": "0.15.2",
|
"ast-types": "0.15.2",
|
||||||
"axios": "catalog:",
|
"axios": "catalog:",
|
||||||
"callsites": "catalog:",
|
"callsites": "catalog:",
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
import * as tmpl from '@n8n_io/riot-tmpl';
|
|
||||||
import { DateTime, Duration, Interval } from 'luxon';
|
import { DateTime, Duration, Interval } from 'luxon';
|
||||||
|
|
||||||
import { ApplicationError } from './errors/application.error';
|
import { ApplicationError } from './errors/application.error';
|
||||||
@@ -53,7 +52,7 @@ export class Expression {
|
|||||||
constructor(private readonly workflow: Workflow) {}
|
constructor(private readonly workflow: Workflow) {}
|
||||||
|
|
||||||
static resolveWithoutWorkflow(expression: string, data: IDataObject = {}) {
|
static resolveWithoutWorkflow(expression: string, data: IDataObject = {}) {
|
||||||
return tmpl.tmpl(expression, data);
|
return evaluateExpression(expression, data);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -327,10 +326,7 @@ export class Expression {
|
|||||||
return returnValue;
|
return returnValue;
|
||||||
}
|
}
|
||||||
|
|
||||||
private renderExpression(
|
private renderExpression(expression: string, data: IWorkflowDataProxyData) {
|
||||||
expression: string,
|
|
||||||
data: IWorkflowDataProxyData,
|
|
||||||
): tmpl.ReturnValue | undefined {
|
|
||||||
try {
|
try {
|
||||||
return evaluateExpression(expression, data);
|
return evaluateExpression(expression, data);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|||||||
@@ -1,155 +1,21 @@
|
|||||||
import type { ReturnValue, TmplDifference } from '@n8n/tournament';
|
|
||||||
import { Tournament } from '@n8n/tournament';
|
import { Tournament } from '@n8n/tournament';
|
||||||
import * as tmpl from '@n8n_io/riot-tmpl';
|
|
||||||
|
|
||||||
import { PrototypeSanitizer } from './ExpressionSandboxing';
|
import { PrototypeSanitizer } from './ExpressionSandboxing';
|
||||||
import type { ExpressionEvaluatorType } from './Interfaces';
|
|
||||||
import * as LoggerProxy from './LoggerProxy';
|
|
||||||
|
|
||||||
type Evaluator = (expr: string, data: unknown) => tmpl.ReturnValue;
|
type Evaluator = (expr: string, data: unknown) => string | null | (() => unknown);
|
||||||
type ErrorHandler = (error: Error) => void;
|
type ErrorHandler = (error: Error) => void;
|
||||||
type DifferenceHandler = (expr: string) => void;
|
|
||||||
|
|
||||||
// Set it to use double curly brackets instead of single ones
|
const errorHandler: ErrorHandler = () => {};
|
||||||
tmpl.brackets.set('{{ }}');
|
|
||||||
|
|
||||||
let errorHandler: ErrorHandler = () => {};
|
|
||||||
let differenceHandler: DifferenceHandler = () => {};
|
|
||||||
const differenceChecker = (diff: TmplDifference) => {
|
|
||||||
try {
|
|
||||||
if (diff.same) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
// eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing
|
|
||||||
if (diff.has?.function || diff.has?.templateString) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (diff.expression === 'UNPARSEABLE') {
|
|
||||||
differenceHandler(diff.expression);
|
|
||||||
} else {
|
|
||||||
differenceHandler(diff.expression.value);
|
|
||||||
}
|
|
||||||
} catch {
|
|
||||||
LoggerProxy.error('Expression evaluator difference checker failed');
|
|
||||||
}
|
|
||||||
};
|
|
||||||
const tournamentEvaluator = new Tournament(errorHandler, undefined, undefined, {
|
const tournamentEvaluator = new Tournament(errorHandler, undefined, undefined, {
|
||||||
before: [],
|
before: [],
|
||||||
after: [PrototypeSanitizer],
|
after: [PrototypeSanitizer],
|
||||||
});
|
});
|
||||||
let evaluator: Evaluator = tmpl.tmpl;
|
const evaluator: Evaluator = tournamentEvaluator.execute.bind(tournamentEvaluator);
|
||||||
let currentEvaluatorType: ExpressionEvaluatorType = 'tmpl';
|
|
||||||
let diffExpressions = false;
|
|
||||||
|
|
||||||
export const setErrorHandler = (handler: ErrorHandler) => {
|
export const setErrorHandler = (handler: ErrorHandler) => {
|
||||||
errorHandler = handler;
|
|
||||||
tmpl.tmpl.errorHandler = handler;
|
|
||||||
tournamentEvaluator.errorHandler = handler;
|
tournamentEvaluator.errorHandler = handler;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const setEvaluator = (evalType: ExpressionEvaluatorType) => {
|
|
||||||
currentEvaluatorType = evalType;
|
|
||||||
if (evalType === 'tmpl') {
|
|
||||||
evaluator = tmpl.tmpl;
|
|
||||||
} else if (evalType === 'tournament') {
|
|
||||||
evaluator = tournamentEvaluator.execute.bind(tournamentEvaluator);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
export const setDiffReporter = (reporter: (expr: string) => void) => {
|
|
||||||
differenceHandler = reporter;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const setDifferEnabled = (enabled: boolean) => {
|
|
||||||
diffExpressions = enabled;
|
|
||||||
};
|
|
||||||
|
|
||||||
const diffCache: Record<string, TmplDifference | null> = {};
|
|
||||||
|
|
||||||
export const checkEvaluatorDifferences = (expr: string): TmplDifference | null => {
|
|
||||||
if (expr in diffCache) {
|
|
||||||
return diffCache[expr];
|
|
||||||
}
|
|
||||||
let diff: TmplDifference | null;
|
|
||||||
try {
|
|
||||||
diff = tournamentEvaluator.tmplDiff(expr);
|
|
||||||
} catch {
|
|
||||||
// We don't include the expression for privacy reasons
|
|
||||||
try {
|
|
||||||
differenceHandler('ERROR');
|
|
||||||
} catch {}
|
|
||||||
diff = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (diff?.same === false) {
|
|
||||||
differenceChecker(diff);
|
|
||||||
}
|
|
||||||
|
|
||||||
diffCache[expr] = diff;
|
|
||||||
return diff;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const getEvaluator = () => {
|
|
||||||
return evaluator;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const evaluateExpression: Evaluator = (expr, data) => {
|
export const evaluateExpression: Evaluator = (expr, data) => {
|
||||||
if (!diffExpressions) {
|
return evaluator(expr, data);
|
||||||
return evaluator(expr, data);
|
|
||||||
}
|
|
||||||
const diff = checkEvaluatorDifferences(expr);
|
|
||||||
|
|
||||||
// We already know that they're different so don't bother
|
|
||||||
// evaluating with both evaluators
|
|
||||||
if (!diff?.same) {
|
|
||||||
return evaluator(expr, data);
|
|
||||||
}
|
|
||||||
|
|
||||||
let tmplValue: tmpl.ReturnValue;
|
|
||||||
let tournValue: ReturnValue;
|
|
||||||
let wasTmplError = false;
|
|
||||||
let tmplError: unknown;
|
|
||||||
let wasTournError = false;
|
|
||||||
let tournError: unknown;
|
|
||||||
|
|
||||||
try {
|
|
||||||
tmplValue = tmpl.tmpl(expr, data);
|
|
||||||
} catch (error) {
|
|
||||||
tmplError = error;
|
|
||||||
wasTmplError = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
tournValue = tournamentEvaluator.execute(expr, data);
|
|
||||||
} catch (error) {
|
|
||||||
tournError = error;
|
|
||||||
wasTournError = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (
|
|
||||||
wasTmplError !== wasTournError ||
|
|
||||||
JSON.stringify(tmplValue!) !== JSON.stringify(tournValue!)
|
|
||||||
) {
|
|
||||||
try {
|
|
||||||
if (diff.expression) {
|
|
||||||
differenceHandler(diff.expression.value);
|
|
||||||
} else {
|
|
||||||
differenceHandler('VALUEDIFF');
|
|
||||||
}
|
|
||||||
} catch {
|
|
||||||
LoggerProxy.error('Failed to report error difference');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (currentEvaluatorType === 'tmpl') {
|
|
||||||
if (wasTmplError) {
|
|
||||||
throw tmplError;
|
|
||||||
}
|
|
||||||
return tmplValue!;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (wasTournError) {
|
|
||||||
throw tournError;
|
|
||||||
}
|
|
||||||
return tournValue!;
|
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -134,7 +134,7 @@ export const extendTransform = (expression: string): { code: string } | undefine
|
|||||||
const chainNumber = currentChain;
|
const chainNumber = currentChain;
|
||||||
currentChain += 1;
|
currentChain += 1;
|
||||||
|
|
||||||
// This is to match our fork of tmpl
|
// This is to match behavior in our original expression evaluator (tmpl)
|
||||||
const globalIdentifier = types.builders.identifier(
|
const globalIdentifier = types.builders.identifier(
|
||||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||||
typeof window !== 'object' ? 'global' : 'window',
|
typeof window !== 'object' ? 'global' : 'window',
|
||||||
|
|||||||
@@ -6,8 +6,9 @@ export interface ExpressionText {
|
|||||||
export interface ExpressionCode {
|
export interface ExpressionCode {
|
||||||
type: 'code';
|
type: 'code';
|
||||||
text: string;
|
text: string;
|
||||||
// tmpl has different behaviours if the last expression
|
|
||||||
// doesn't close itself.
|
// This is to match behavior in our original expression evaluator (tmpl),
|
||||||
|
// which has different behaviours if the last expression doesn't close itself.
|
||||||
hasClosingBrackets: boolean;
|
hasClosingBrackets: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2872,8 +2872,6 @@ export interface ICheckProcessedContextData {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export type ExpressionEvaluatorType = 'tmpl' | 'tournament';
|
|
||||||
|
|
||||||
export type N8nAIProviderType = 'openai' | 'unknown';
|
export type N8nAIProviderType = 'openai' | 'unknown';
|
||||||
|
|
||||||
export interface SecretsHelpersBase {
|
export interface SecretsHelpersBase {
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
import * as LoggerProxy from './LoggerProxy';
|
import * as LoggerProxy from './LoggerProxy';
|
||||||
export * as ExpressionEvaluatorProxy from './ExpressionEvaluatorProxy';
|
|
||||||
import * as NodeHelpers from './NodeHelpers';
|
import * as NodeHelpers from './NodeHelpers';
|
||||||
import * as ObservableObject from './ObservableObject';
|
import * as ObservableObject from './ObservableObject';
|
||||||
import * as TelemetryHelpers from './TelemetryHelpers';
|
import * as TelemetryHelpers from './TelemetryHelpers';
|
||||||
|
|||||||
@@ -5,7 +5,6 @@
|
|||||||
import { DateTime, Duration, Interval } from 'luxon';
|
import { DateTime, Duration, Interval } from 'luxon';
|
||||||
|
|
||||||
import { ExpressionError } from '@/errors/expression.error';
|
import { ExpressionError } from '@/errors/expression.error';
|
||||||
import { setDifferEnabled, setEvaluator } from '@/ExpressionEvaluatorProxy';
|
|
||||||
import { extendSyntax } from '@/Extensions/ExpressionExtension';
|
import { extendSyntax } from '@/Extensions/ExpressionExtension';
|
||||||
import type { INodeExecutionData } from '@/Interfaces';
|
import type { INodeExecutionData } from '@/Interfaces';
|
||||||
import { Workflow } from '@/Workflow';
|
import { Workflow } from '@/Workflow';
|
||||||
@@ -15,202 +14,206 @@ import { baseFixtures } from './ExpressionFixtures/base';
|
|||||||
import type { ExpressionTestEvaluation, ExpressionTestTransform } from './ExpressionFixtures/base';
|
import type { ExpressionTestEvaluation, ExpressionTestTransform } from './ExpressionFixtures/base';
|
||||||
import * as Helpers from './Helpers';
|
import * as Helpers from './Helpers';
|
||||||
|
|
||||||
setDifferEnabled(true);
|
describe('Expression', () => {
|
||||||
|
describe('getParameterValue()', () => {
|
||||||
|
const nodeTypes = Helpers.NodeTypes();
|
||||||
|
const workflow = new Workflow({
|
||||||
|
id: '1',
|
||||||
|
nodes: [
|
||||||
|
{
|
||||||
|
name: 'node',
|
||||||
|
typeVersion: 1,
|
||||||
|
type: 'test.set',
|
||||||
|
id: 'uuid-1234',
|
||||||
|
position: [0, 0],
|
||||||
|
parameters: {},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
connections: {},
|
||||||
|
active: false,
|
||||||
|
nodeTypes,
|
||||||
|
});
|
||||||
|
const expression = workflow.expression;
|
||||||
|
|
||||||
for (const evaluator of ['tmpl', 'tournament'] as const) {
|
const evaluate = (value: string) =>
|
||||||
setEvaluator(evaluator);
|
expression.getParameterValue(value, null, 0, 0, 'node', [], 'manual', {});
|
||||||
describe(`Expression (with ${evaluator})`, () => {
|
|
||||||
describe('getParameterValue()', () => {
|
|
||||||
const nodeTypes = Helpers.NodeTypes();
|
|
||||||
const workflow = new Workflow({
|
|
||||||
id: '1',
|
|
||||||
nodes: [
|
|
||||||
{
|
|
||||||
name: 'node',
|
|
||||||
typeVersion: 1,
|
|
||||||
type: 'test.set',
|
|
||||||
id: 'uuid-1234',
|
|
||||||
position: [0, 0],
|
|
||||||
parameters: {},
|
|
||||||
},
|
|
||||||
],
|
|
||||||
connections: {},
|
|
||||||
active: false,
|
|
||||||
nodeTypes,
|
|
||||||
});
|
|
||||||
const expression = workflow.expression;
|
|
||||||
|
|
||||||
const evaluate = (value: string) =>
|
it('should not be able to use global built-ins from denylist', () => {
|
||||||
expression.getParameterValue(value, null, 0, 0, 'node', [], 'manual', {});
|
expect(evaluate('={{document}}')).toEqual({});
|
||||||
|
expect(evaluate('={{window}}')).toEqual({});
|
||||||
|
|
||||||
it('should not be able to use global built-ins from denylist', () => {
|
expect(evaluate('={{Window}}')).toEqual({});
|
||||||
expect(evaluate('={{document}}')).toEqual({});
|
expect(evaluate('={{globalThis}}')).toEqual({});
|
||||||
expect(evaluate('={{window}}')).toEqual({});
|
expect(evaluate('={{self}}')).toEqual({});
|
||||||
|
|
||||||
expect(evaluate('={{Window}}')).toEqual({});
|
expect(evaluate('={{alert}}')).toEqual({});
|
||||||
expect(evaluate('={{globalThis}}')).toEqual({});
|
expect(evaluate('={{prompt}}')).toEqual({});
|
||||||
expect(evaluate('={{self}}')).toEqual({});
|
expect(evaluate('={{confirm}}')).toEqual({});
|
||||||
|
|
||||||
expect(evaluate('={{alert}}')).toEqual({});
|
expect(evaluate('={{eval}}')).toEqual({});
|
||||||
expect(evaluate('={{prompt}}')).toEqual({});
|
expect(evaluate('={{uneval}}')).toEqual({});
|
||||||
expect(evaluate('={{confirm}}')).toEqual({});
|
expect(evaluate('={{setTimeout}}')).toEqual({});
|
||||||
|
expect(evaluate('={{setInterval}}')).toEqual({});
|
||||||
|
expect(evaluate('={{Function}}')).toEqual({});
|
||||||
|
|
||||||
expect(evaluate('={{eval}}')).toEqual({});
|
expect(evaluate('={{fetch}}')).toEqual({});
|
||||||
expect(evaluate('={{uneval}}')).toEqual({});
|
expect(evaluate('={{XMLHttpRequest}}')).toEqual({});
|
||||||
expect(evaluate('={{setTimeout}}')).toEqual({});
|
|
||||||
expect(evaluate('={{setInterval}}')).toEqual({});
|
|
||||||
expect(evaluate('={{Function}}')).toEqual({});
|
|
||||||
|
|
||||||
expect(evaluate('={{fetch}}')).toEqual({});
|
expect(evaluate('={{Promise}}')).toEqual({});
|
||||||
expect(evaluate('={{XMLHttpRequest}}')).toEqual({});
|
expect(evaluate('={{Generator}}')).toEqual({});
|
||||||
|
expect(evaluate('={{GeneratorFunction}}')).toEqual({});
|
||||||
|
expect(evaluate('={{AsyncFunction}}')).toEqual({});
|
||||||
|
expect(evaluate('={{AsyncGenerator}}')).toEqual({});
|
||||||
|
expect(evaluate('={{AsyncGeneratorFunction}}')).toEqual({});
|
||||||
|
|
||||||
expect(evaluate('={{Promise}}')).toEqual({});
|
expect(evaluate('={{WebAssembly}}')).toEqual({});
|
||||||
expect(evaluate('={{Generator}}')).toEqual({});
|
|
||||||
expect(evaluate('={{GeneratorFunction}}')).toEqual({});
|
|
||||||
expect(evaluate('={{AsyncFunction}}')).toEqual({});
|
|
||||||
expect(evaluate('={{AsyncGenerator}}')).toEqual({});
|
|
||||||
expect(evaluate('={{AsyncGeneratorFunction}}')).toEqual({});
|
|
||||||
|
|
||||||
expect(evaluate('={{WebAssembly}}')).toEqual({});
|
expect(evaluate('={{Reflect}}')).toEqual({});
|
||||||
|
expect(evaluate('={{Proxy}}')).toEqual({});
|
||||||
|
|
||||||
expect(evaluate('={{Reflect}}')).toEqual({});
|
expect(() => evaluate('={{constructor}}')).toThrowError(
|
||||||
expect(evaluate('={{Proxy}}')).toEqual({});
|
new ExpressionError('Cannot access "constructor" due to security concerns'),
|
||||||
|
);
|
||||||
|
|
||||||
expect(() => evaluate('={{constructor}}')).toThrowError(
|
expect(evaluate('={{escape}}')).toEqual({});
|
||||||
new ExpressionError('Cannot access "constructor" due to security concerns'),
|
expect(evaluate('={{unescape}}')).toEqual({});
|
||||||
);
|
|
||||||
|
|
||||||
expect(evaluate('={{escape}}')).toEqual({});
|
|
||||||
expect(evaluate('={{unescape}}')).toEqual({});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should be able to use global built-ins from allowlist', () => {
|
|
||||||
expect(evaluate('={{new Date()}}')).toBeInstanceOf(Date);
|
|
||||||
expect(evaluate('={{DateTime.now().toLocaleString()}}')).toEqual(
|
|
||||||
DateTime.now().toLocaleString(),
|
|
||||||
);
|
|
||||||
|
|
||||||
jest.useFakeTimers({ now: new Date() });
|
|
||||||
expect(evaluate('={{Interval.after(new Date(), 100)}}')).toEqual(
|
|
||||||
Interval.after(new Date(), 100),
|
|
||||||
);
|
|
||||||
jest.useRealTimers();
|
|
||||||
|
|
||||||
expect(evaluate('={{Duration.fromMillis(100)}}')).toEqual(Duration.fromMillis(100));
|
|
||||||
|
|
||||||
expect(evaluate('={{new Object()}}')).toEqual(new Object());
|
|
||||||
|
|
||||||
expect(evaluate('={{new Array()}}')).toEqual([]);
|
|
||||||
expect(evaluate('={{new Int8Array()}}')).toEqual(new Int8Array());
|
|
||||||
expect(evaluate('={{new Uint8Array()}}')).toEqual(new Uint8Array());
|
|
||||||
expect(evaluate('={{new Uint8ClampedArray()}}')).toEqual(new Uint8ClampedArray());
|
|
||||||
expect(evaluate('={{new Int16Array()}}')).toEqual(new Int16Array());
|
|
||||||
expect(evaluate('={{new Uint16Array()}}')).toEqual(new Uint16Array());
|
|
||||||
expect(evaluate('={{new Int32Array()}}')).toEqual(new Int32Array());
|
|
||||||
expect(evaluate('={{new Uint32Array()}}')).toEqual(new Uint32Array());
|
|
||||||
expect(evaluate('={{new Float32Array()}}')).toEqual(new Float32Array());
|
|
||||||
expect(evaluate('={{new Float64Array()}}')).toEqual(new Float64Array());
|
|
||||||
expect(evaluate('={{new BigInt64Array()}}')).toEqual(new BigInt64Array());
|
|
||||||
expect(evaluate('={{new BigUint64Array()}}')).toEqual(new BigUint64Array());
|
|
||||||
|
|
||||||
expect(evaluate('={{new Map()}}')).toEqual(new Map());
|
|
||||||
expect(evaluate('={{new WeakMap()}}')).toEqual(new WeakMap());
|
|
||||||
expect(evaluate('={{new Set()}}')).toEqual(new Set());
|
|
||||||
expect(evaluate('={{new WeakSet()}}')).toEqual(new WeakSet());
|
|
||||||
|
|
||||||
expect(evaluate('={{new Error()}}')).toEqual(new Error());
|
|
||||||
expect(evaluate('={{new TypeError()}}')).toEqual(new TypeError());
|
|
||||||
expect(evaluate('={{new SyntaxError()}}')).toEqual(new SyntaxError());
|
|
||||||
expect(evaluate('={{new EvalError()}}')).toEqual(new EvalError());
|
|
||||||
expect(evaluate('={{new RangeError()}}')).toEqual(new RangeError());
|
|
||||||
expect(evaluate('={{new ReferenceError()}}')).toEqual(new ReferenceError());
|
|
||||||
expect(evaluate('={{new URIError()}}')).toEqual(new URIError());
|
|
||||||
|
|
||||||
expect(evaluate('={{Intl}}')).toEqual(Intl);
|
|
||||||
|
|
||||||
expect(evaluate('={{new String()}}')).toEqual(new String());
|
|
||||||
expect(evaluate("={{new RegExp('')}}")).toEqual(new RegExp(''));
|
|
||||||
|
|
||||||
expect(evaluate('={{Math}}')).toEqual(Math);
|
|
||||||
expect(evaluate('={{new Number()}}')).toEqual(new Number());
|
|
||||||
expect(evaluate("={{BigInt('1')}}")).toEqual(BigInt('1'));
|
|
||||||
expect(evaluate('={{Infinity}}')).toEqual(Infinity);
|
|
||||||
expect(evaluate('={{NaN}}')).toEqual(NaN);
|
|
||||||
expect(evaluate('={{isFinite(1)}}')).toEqual(isFinite(1));
|
|
||||||
expect(evaluate('={{isNaN(1)}}')).toEqual(isNaN(1));
|
|
||||||
expect(evaluate("={{parseFloat('1')}}")).toEqual(parseFloat('1'));
|
|
||||||
expect(evaluate("={{parseInt('1', 10)}}")).toEqual(parseInt('1', 10));
|
|
||||||
|
|
||||||
expect(evaluate('={{JSON.stringify({})}}')).toEqual(JSON.stringify({}));
|
|
||||||
expect(evaluate('={{new ArrayBuffer(10)}}')).toEqual(new ArrayBuffer(10));
|
|
||||||
expect(evaluate('={{new SharedArrayBuffer(10)}}')).toEqual(new SharedArrayBuffer(10));
|
|
||||||
expect(evaluate('={{Atomics}}')).toEqual(Atomics);
|
|
||||||
expect(evaluate('={{new DataView(new ArrayBuffer(1))}}')).toEqual(
|
|
||||||
new DataView(new ArrayBuffer(1)),
|
|
||||||
);
|
|
||||||
|
|
||||||
expect(evaluate("={{encodeURI('https://google.com')}}")).toEqual(
|
|
||||||
encodeURI('https://google.com'),
|
|
||||||
);
|
|
||||||
expect(evaluate("={{encodeURIComponent('https://google.com')}}")).toEqual(
|
|
||||||
encodeURIComponent('https://google.com'),
|
|
||||||
);
|
|
||||||
expect(evaluate("={{decodeURI('https://google.com')}}")).toEqual(
|
|
||||||
decodeURI('https://google.com'),
|
|
||||||
);
|
|
||||||
expect(evaluate("={{decodeURIComponent('https://google.com')}}")).toEqual(
|
|
||||||
decodeURIComponent('https://google.com'),
|
|
||||||
);
|
|
||||||
|
|
||||||
expect(evaluate('={{Boolean(1)}}')).toEqual(Boolean(1));
|
|
||||||
expect(evaluate('={{Symbol(1).toString()}}')).toEqual(Symbol(1).toString());
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('Test all expression value fixtures', () => {
|
it('should be able to use global built-ins from allowlist', () => {
|
||||||
const expression = workflow.expression;
|
expect(evaluate('={{new Date()}}')).toBeInstanceOf(Date);
|
||||||
|
expect(evaluate('={{DateTime.now().toLocaleString()}}')).toEqual(
|
||||||
|
DateTime.now().toLocaleString(),
|
||||||
|
);
|
||||||
|
|
||||||
const evaluate = (value: string, data: INodeExecutionData[]) => {
|
jest.useFakeTimers({ now: new Date() });
|
||||||
const itemIndex = data.length === 0 ? -1 : 0;
|
expect(evaluate('={{Interval.after(new Date(), 100)}}')).toEqual(
|
||||||
return expression.getParameterValue(value, null, 0, itemIndex, 'node', data, 'manual', {});
|
Interval.after(new Date(), 100),
|
||||||
};
|
);
|
||||||
|
jest.useRealTimers();
|
||||||
|
|
||||||
for (const t of baseFixtures) {
|
expect(evaluate('={{Duration.fromMillis(100)}}')).toEqual(Duration.fromMillis(100));
|
||||||
if (!t.tests.some((test) => test.type === 'evaluation')) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
test(t.expression, () => {
|
|
||||||
const evaluationTests = t.tests.filter(
|
|
||||||
(test): test is ExpressionTestEvaluation => test.type === 'evaluation',
|
|
||||||
);
|
|
||||||
|
|
||||||
for (const test of evaluationTests) {
|
expect(evaluate('={{new Object()}}')).toEqual(new Object());
|
||||||
const input = test.input.map((d) => ({ json: d })) as any;
|
|
||||||
|
|
||||||
if ('error' in test) {
|
expect(evaluate('={{new Array()}}')).toEqual([]);
|
||||||
expect(() => evaluate(t.expression, input)).toThrowError(test.error);
|
expect(evaluate('={{new Int8Array()}}')).toEqual(new Int8Array());
|
||||||
} else {
|
expect(evaluate('={{new Uint8Array()}}')).toEqual(new Uint8Array());
|
||||||
expect(evaluate(t.expression, input)).toStrictEqual(test.output);
|
expect(evaluate('={{new Uint8ClampedArray()}}')).toEqual(new Uint8ClampedArray());
|
||||||
}
|
expect(evaluate('={{new Int16Array()}}')).toEqual(new Int16Array());
|
||||||
}
|
expect(evaluate('={{new Uint16Array()}}')).toEqual(new Uint16Array());
|
||||||
});
|
expect(evaluate('={{new Int32Array()}}')).toEqual(new Int32Array());
|
||||||
}
|
expect(evaluate('={{new Uint32Array()}}')).toEqual(new Uint32Array());
|
||||||
|
expect(evaluate('={{new Float32Array()}}')).toEqual(new Float32Array());
|
||||||
|
expect(evaluate('={{new Float64Array()}}')).toEqual(new Float64Array());
|
||||||
|
expect(evaluate('={{new BigInt64Array()}}')).toEqual(new BigInt64Array());
|
||||||
|
expect(evaluate('={{new BigUint64Array()}}')).toEqual(new BigUint64Array());
|
||||||
|
|
||||||
|
expect(evaluate('={{new Map()}}')).toEqual(new Map());
|
||||||
|
expect(evaluate('={{new WeakMap()}}')).toEqual(new WeakMap());
|
||||||
|
expect(evaluate('={{new Set()}}')).toEqual(new Set());
|
||||||
|
expect(evaluate('={{new WeakSet()}}')).toEqual(new WeakSet());
|
||||||
|
|
||||||
|
expect(evaluate('={{new Error()}}')).toEqual(new Error());
|
||||||
|
expect(evaluate('={{new TypeError()}}')).toEqual(new TypeError());
|
||||||
|
expect(evaluate('={{new SyntaxError()}}')).toEqual(new SyntaxError());
|
||||||
|
expect(evaluate('={{new EvalError()}}')).toEqual(new EvalError());
|
||||||
|
expect(evaluate('={{new RangeError()}}')).toEqual(new RangeError());
|
||||||
|
expect(evaluate('={{new ReferenceError()}}')).toEqual(new ReferenceError());
|
||||||
|
expect(evaluate('={{new URIError()}}')).toEqual(new URIError());
|
||||||
|
|
||||||
|
expect(evaluate('={{Intl}}')).toEqual(Intl);
|
||||||
|
|
||||||
|
expect(evaluate('={{new String()}}')).toEqual(new String());
|
||||||
|
expect(evaluate("={{new RegExp('')}}")).toEqual(new RegExp(''));
|
||||||
|
|
||||||
|
expect(evaluate('={{Math}}')).toEqual(Math);
|
||||||
|
expect(evaluate('={{new Number()}}')).toEqual(new Number());
|
||||||
|
expect(evaluate("={{BigInt('1')}}")).toEqual(BigInt('1'));
|
||||||
|
expect(evaluate('={{Infinity}}')).toEqual(Infinity);
|
||||||
|
expect(evaluate('={{NaN}}')).toEqual(NaN);
|
||||||
|
expect(evaluate('={{isFinite(1)}}')).toEqual(isFinite(1));
|
||||||
|
expect(evaluate('={{isNaN(1)}}')).toEqual(isNaN(1));
|
||||||
|
expect(evaluate("={{parseFloat('1')}}")).toEqual(parseFloat('1'));
|
||||||
|
expect(evaluate("={{parseInt('1', 10)}}")).toEqual(parseInt('1', 10));
|
||||||
|
|
||||||
|
expect(evaluate('={{JSON.stringify({})}}')).toEqual(JSON.stringify({}));
|
||||||
|
expect(evaluate('={{new ArrayBuffer(10)}}')).toEqual(new ArrayBuffer(10));
|
||||||
|
expect(evaluate('={{new SharedArrayBuffer(10)}}')).toEqual(new SharedArrayBuffer(10));
|
||||||
|
expect(evaluate('={{Atomics}}')).toEqual(Atomics);
|
||||||
|
expect(evaluate('={{new DataView(new ArrayBuffer(1))}}')).toEqual(
|
||||||
|
new DataView(new ArrayBuffer(1)),
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(evaluate("={{encodeURI('https://google.com')}}")).toEqual(
|
||||||
|
encodeURI('https://google.com'),
|
||||||
|
);
|
||||||
|
expect(evaluate("={{encodeURIComponent('https://google.com')}}")).toEqual(
|
||||||
|
encodeURIComponent('https://google.com'),
|
||||||
|
);
|
||||||
|
expect(evaluate("={{decodeURI('https://google.com')}}")).toEqual(
|
||||||
|
decodeURI('https://google.com'),
|
||||||
|
);
|
||||||
|
expect(evaluate("={{decodeURIComponent('https://google.com')}}")).toEqual(
|
||||||
|
decodeURIComponent('https://google.com'),
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(evaluate('={{Boolean(1)}}')).toEqual(Boolean(1));
|
||||||
|
expect(evaluate('={{Symbol(1).toString()}}')).toEqual(Symbol(1).toString());
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('Test all expression transform fixtures', () => {
|
it('should not able to do arbitrary code execution', () => {
|
||||||
for (const t of baseFixtures) {
|
const testFn = jest.fn();
|
||||||
if (!t.tests.some((test) => test.type === 'transform')) {
|
Object.assign(global, { testFn });
|
||||||
continue;
|
expect(() => evaluate("={{ Date['constructor']('testFn()')()}}")).toThrowError(
|
||||||
}
|
new ExpressionError('Cannot access "constructor" due to security concerns'),
|
||||||
test(t.expression, () => {
|
);
|
||||||
for (const test of t.tests.filter(
|
expect(testFn).not.toHaveBeenCalled();
|
||||||
(test): test is ExpressionTestTransform => test.type === 'transform',
|
|
||||||
)) {
|
|
||||||
const expr = t.expression;
|
|
||||||
expect(extendSyntax(expr, test.forceTransform)).toEqual(test.result ?? expr);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
|
||||||
|
describe('Test all expression value fixtures', () => {
|
||||||
|
const expression = workflow.expression;
|
||||||
|
|
||||||
|
const evaluate = (value: string, data: INodeExecutionData[]) => {
|
||||||
|
const itemIndex = data.length === 0 ? -1 : 0;
|
||||||
|
return expression.getParameterValue(value, null, 0, itemIndex, 'node', data, 'manual', {});
|
||||||
|
};
|
||||||
|
|
||||||
|
for (const t of baseFixtures) {
|
||||||
|
if (!t.tests.some((test) => test.type === 'evaluation')) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
test(t.expression, () => {
|
||||||
|
const evaluationTests = t.tests.filter(
|
||||||
|
(test): test is ExpressionTestEvaluation => test.type === 'evaluation',
|
||||||
|
);
|
||||||
|
|
||||||
|
for (const test of evaluationTests) {
|
||||||
|
const input = test.input.map((d) => ({ json: d })) as any;
|
||||||
|
|
||||||
|
if ('error' in test) {
|
||||||
|
expect(() => evaluate(t.expression, input)).toThrowError(test.error);
|
||||||
|
} else {
|
||||||
|
expect(evaluate(t.expression, input)).toStrictEqual(test.output);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('Test all expression transform fixtures', () => {
|
||||||
|
for (const t of baseFixtures) {
|
||||||
|
if (!t.tests.some((test) => test.type === 'transform')) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
test(t.expression, () => {
|
||||||
|
for (const test of t.tests.filter(
|
||||||
|
(test): test is ExpressionTestTransform => test.type === 'transform',
|
||||||
|
)) {
|
||||||
|
const expr = t.expression;
|
||||||
|
expect(extendSyntax(expr, test.forceTransform)).toEqual(test.result ?? expr);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ describe('Expression Extension Transforms', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('tmpl Expression Parser', () => {
|
describe('Expression Parser', () => {
|
||||||
describe('Compatible splitting', () => {
|
describe('Compatible splitting', () => {
|
||||||
test('Lone expression', () => {
|
test('Lone expression', () => {
|
||||||
expect(splitExpression('{{ "" }}')).toEqual([
|
expect(splitExpression('{{ "" }}')).toEqual([
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import { ApplicationError } from '@/errors';
|
||||||
import { objectExtensions } from '@/Extensions/ObjectExtensions';
|
import { objectExtensions } from '@/Extensions/ObjectExtensions';
|
||||||
|
|
||||||
import { evaluate } from './Helpers';
|
import { evaluate } from './Helpers';
|
||||||
@@ -126,7 +127,9 @@ describe('Data Transformation Functions', () => {
|
|||||||
test('should not allow prototype pollution', () => {
|
test('should not allow prototype pollution', () => {
|
||||||
['{__proto__: {polluted: true}}', '{constructor: {prototype: {polluted: true}}}'].forEach(
|
['{__proto__: {polluted: true}}', '{constructor: {prototype: {polluted: true}}}'].forEach(
|
||||||
(testExpression) => {
|
(testExpression) => {
|
||||||
expect(evaluate(`={{ (${testExpression}).compact() }}`)).toEqual(null);
|
expect(() => evaluate(`={{ (${testExpression}).compact() }}`)).toThrow(
|
||||||
|
ApplicationError,
|
||||||
|
);
|
||||||
expect(({} as any).polluted).toBeUndefined();
|
expect(({} as any).polluted).toBeUndefined();
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|||||||
34
pnpm-lock.yaml
generated
34
pnpm-lock.yaml
generated
@@ -717,7 +717,7 @@ importers:
|
|||||||
version: 4.3.0
|
version: 4.3.0
|
||||||
'@getzep/zep-cloud':
|
'@getzep/zep-cloud':
|
||||||
specifier: 1.0.12
|
specifier: 1.0.12
|
||||||
version: 1.0.12(@langchain/core@0.3.30(openai@4.78.1(encoding@0.1.13)(zod@3.24.1)))(encoding@0.1.13)(langchain@0.3.11(b4eb53fe8b825d6e8edd96cc3d942586))
|
version: 1.0.12(@langchain/core@0.3.30(openai@4.78.1(encoding@0.1.13)(zod@3.24.1)))(encoding@0.1.13)(langchain@0.3.11(7f2a4b9c5436679ca8b0df05212b4905))
|
||||||
'@getzep/zep-js':
|
'@getzep/zep-js':
|
||||||
specifier: 0.9.0
|
specifier: 0.9.0
|
||||||
version: 0.9.0
|
version: 0.9.0
|
||||||
@@ -744,7 +744,7 @@ importers:
|
|||||||
version: 0.3.2(@langchain/core@0.3.30(openai@4.78.1(encoding@0.1.13)(zod@3.24.1)))(encoding@0.1.13)
|
version: 0.3.2(@langchain/core@0.3.30(openai@4.78.1(encoding@0.1.13)(zod@3.24.1)))(encoding@0.1.13)
|
||||||
'@langchain/community':
|
'@langchain/community':
|
||||||
specifier: 'catalog:'
|
specifier: 'catalog:'
|
||||||
version: 0.3.24(67fb36bad0bcdd2b0df3579415b33a93)
|
version: 0.3.24(0b620065402de60ffbc4ade3af2d8197)
|
||||||
'@langchain/core':
|
'@langchain/core':
|
||||||
specifier: 'catalog:'
|
specifier: 'catalog:'
|
||||||
version: 0.3.30(openai@4.78.1(encoding@0.1.13)(zod@3.24.1))
|
version: 0.3.30(openai@4.78.1(encoding@0.1.13)(zod@3.24.1))
|
||||||
@@ -846,7 +846,7 @@ importers:
|
|||||||
version: 23.0.1
|
version: 23.0.1
|
||||||
langchain:
|
langchain:
|
||||||
specifier: 0.3.11
|
specifier: 0.3.11
|
||||||
version: 0.3.11(b4eb53fe8b825d6e8edd96cc3d942586)
|
version: 0.3.11(7f2a4b9c5436679ca8b0df05212b4905)
|
||||||
lodash:
|
lodash:
|
||||||
specifier: 'catalog:'
|
specifier: 'catalog:'
|
||||||
version: 4.17.21
|
version: 4.17.21
|
||||||
@@ -2489,9 +2489,6 @@ importers:
|
|||||||
'@n8n/tournament':
|
'@n8n/tournament':
|
||||||
specifier: 1.0.6
|
specifier: 1.0.6
|
||||||
version: 1.0.6
|
version: 1.0.6
|
||||||
'@n8n_io/riot-tmpl':
|
|
||||||
specifier: 4.0.0
|
|
||||||
version: 4.0.0
|
|
||||||
ast-types:
|
ast-types:
|
||||||
specifier: 0.15.2
|
specifier: 0.15.2
|
||||||
version: 0.15.2
|
version: 0.15.2
|
||||||
@@ -4956,9 +4953,6 @@ packages:
|
|||||||
resolution: {integrity: sha512-HrxdwlZw3MfYrfgvjxIGTyi3gJUCOFHtMcL5OpqpHhwfMGwuBauRgUOt63Twx4qs7f2bnz5yHV3jvuU3mF45UQ==}
|
resolution: {integrity: sha512-HrxdwlZw3MfYrfgvjxIGTyi3gJUCOFHtMcL5OpqpHhwfMGwuBauRgUOt63Twx4qs7f2bnz5yHV3jvuU3mF45UQ==}
|
||||||
engines: {node: '>=18.12.1'}
|
engines: {node: '>=18.12.1'}
|
||||||
|
|
||||||
'@n8n_io/riot-tmpl@4.0.0':
|
|
||||||
resolution: {integrity: sha512-/xw8HQgYQlBCrt3IKpNSSB1CgpP7XArw1QTRjP+KEw+OHT8XGvHxXrW9VGdUu9RwDnzm/LFu+dNLeDmwJMeOwQ==}
|
|
||||||
|
|
||||||
'@n8n_io/riot-tmpl@4.0.1':
|
'@n8n_io/riot-tmpl@4.0.1':
|
||||||
resolution: {integrity: sha512-/zdRbEfTFjsm1NqnpPQHgZTkTdbp5v3VUxGeMA9098sps8jRCTraQkc3AQstJgHUm7ylBXJcIVhnVeLUMWAfwQ==}
|
resolution: {integrity: sha512-/zdRbEfTFjsm1NqnpPQHgZTkTdbp5v3VUxGeMA9098sps8jRCTraQkc3AQstJgHUm7ylBXJcIVhnVeLUMWAfwQ==}
|
||||||
|
|
||||||
@@ -16465,7 +16459,7 @@ snapshots:
|
|||||||
'@gar/promisify@1.1.3':
|
'@gar/promisify@1.1.3':
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
'@getzep/zep-cloud@1.0.12(@langchain/core@0.3.30(openai@4.78.1(encoding@0.1.13)(zod@3.24.1)))(encoding@0.1.13)(langchain@0.3.11(b4eb53fe8b825d6e8edd96cc3d942586))':
|
'@getzep/zep-cloud@1.0.12(@langchain/core@0.3.30(openai@4.78.1(encoding@0.1.13)(zod@3.24.1)))(encoding@0.1.13)(langchain@0.3.11(7f2a4b9c5436679ca8b0df05212b4905))':
|
||||||
dependencies:
|
dependencies:
|
||||||
form-data: 4.0.0
|
form-data: 4.0.0
|
||||||
node-fetch: 2.7.0(encoding@0.1.13)
|
node-fetch: 2.7.0(encoding@0.1.13)
|
||||||
@@ -16474,7 +16468,7 @@ snapshots:
|
|||||||
zod: 3.24.1
|
zod: 3.24.1
|
||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
'@langchain/core': 0.3.30(openai@4.78.1(encoding@0.1.13)(zod@3.24.1))
|
'@langchain/core': 0.3.30(openai@4.78.1(encoding@0.1.13)(zod@3.24.1))
|
||||||
langchain: 0.3.11(b4eb53fe8b825d6e8edd96cc3d942586)
|
langchain: 0.3.11(7f2a4b9c5436679ca8b0df05212b4905)
|
||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- encoding
|
- encoding
|
||||||
|
|
||||||
@@ -16986,7 +16980,7 @@ snapshots:
|
|||||||
- aws-crt
|
- aws-crt
|
||||||
- encoding
|
- encoding
|
||||||
|
|
||||||
'@langchain/community@0.3.24(67fb36bad0bcdd2b0df3579415b33a93)':
|
'@langchain/community@0.3.24(0b620065402de60ffbc4ade3af2d8197)':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@browserbasehq/stagehand': 1.9.0(@playwright/test@1.49.1)(deepmerge@4.3.1)(dotenv@16.4.5)(encoding@0.1.13)(openai@4.78.1(encoding@0.1.13)(zod@3.24.1))(zod@3.24.1)
|
'@browserbasehq/stagehand': 1.9.0(@playwright/test@1.49.1)(deepmerge@4.3.1)(dotenv@16.4.5)(encoding@0.1.13)(openai@4.78.1(encoding@0.1.13)(zod@3.24.1))(zod@3.24.1)
|
||||||
'@ibm-cloud/watsonx-ai': 1.1.2
|
'@ibm-cloud/watsonx-ai': 1.1.2
|
||||||
@@ -16997,7 +16991,7 @@ snapshots:
|
|||||||
flat: 5.0.2
|
flat: 5.0.2
|
||||||
ibm-cloud-sdk-core: 5.3.2
|
ibm-cloud-sdk-core: 5.3.2
|
||||||
js-yaml: 4.1.0
|
js-yaml: 4.1.0
|
||||||
langchain: 0.3.11(b4eb53fe8b825d6e8edd96cc3d942586)
|
langchain: 0.3.11(7f2a4b9c5436679ca8b0df05212b4905)
|
||||||
langsmith: 0.2.15(openai@4.78.1(encoding@0.1.13)(zod@3.24.1))
|
langsmith: 0.2.15(openai@4.78.1(encoding@0.1.13)(zod@3.24.1))
|
||||||
openai: 4.78.1(encoding@0.1.13)(zod@3.24.1)
|
openai: 4.78.1(encoding@0.1.13)(zod@3.24.1)
|
||||||
uuid: 10.0.0
|
uuid: 10.0.0
|
||||||
@@ -17012,7 +17006,7 @@ snapshots:
|
|||||||
'@aws-sdk/credential-provider-node': 3.808.0
|
'@aws-sdk/credential-provider-node': 3.808.0
|
||||||
'@azure/storage-blob': 12.26.0
|
'@azure/storage-blob': 12.26.0
|
||||||
'@browserbasehq/sdk': 2.0.0(encoding@0.1.13)
|
'@browserbasehq/sdk': 2.0.0(encoding@0.1.13)
|
||||||
'@getzep/zep-cloud': 1.0.12(@langchain/core@0.3.30(openai@4.78.1(encoding@0.1.13)(zod@3.24.1)))(encoding@0.1.13)(langchain@0.3.11(b4eb53fe8b825d6e8edd96cc3d942586))
|
'@getzep/zep-cloud': 1.0.12(@langchain/core@0.3.30(openai@4.78.1(encoding@0.1.13)(zod@3.24.1)))(encoding@0.1.13)(langchain@0.3.11(7f2a4b9c5436679ca8b0df05212b4905))
|
||||||
'@getzep/zep-js': 0.9.0
|
'@getzep/zep-js': 0.9.0
|
||||||
'@google-ai/generativelanguage': 2.6.0(encoding@0.1.13)
|
'@google-ai/generativelanguage': 2.6.0(encoding@0.1.13)
|
||||||
'@google-cloud/storage': 7.12.1(encoding@0.1.13)
|
'@google-cloud/storage': 7.12.1(encoding@0.1.13)
|
||||||
@@ -17462,10 +17456,6 @@ snapshots:
|
|||||||
node-rsa: 1.1.1
|
node-rsa: 1.1.1
|
||||||
undici: 7.7.0
|
undici: 7.7.0
|
||||||
|
|
||||||
'@n8n_io/riot-tmpl@4.0.0':
|
|
||||||
dependencies:
|
|
||||||
eslint-config-riot: 1.0.0
|
|
||||||
|
|
||||||
'@n8n_io/riot-tmpl@4.0.1':
|
'@n8n_io/riot-tmpl@4.0.1':
|
||||||
dependencies:
|
dependencies:
|
||||||
eslint-config-riot: 1.0.0
|
eslint-config-riot: 1.0.0
|
||||||
@@ -23203,7 +23193,7 @@ snapshots:
|
|||||||
'@types/debug': 4.1.12
|
'@types/debug': 4.1.12
|
||||||
'@types/node': 18.16.16
|
'@types/node': 18.16.16
|
||||||
'@types/tough-cookie': 4.0.2
|
'@types/tough-cookie': 4.0.2
|
||||||
axios: 1.8.3(debug@4.4.0)
|
axios: 1.8.3
|
||||||
camelcase: 6.3.0
|
camelcase: 6.3.0
|
||||||
debug: 4.4.0(supports-color@8.1.1)
|
debug: 4.4.0(supports-color@8.1.1)
|
||||||
dotenv: 16.4.5
|
dotenv: 16.4.5
|
||||||
@@ -23213,7 +23203,7 @@ snapshots:
|
|||||||
isstream: 0.1.2
|
isstream: 0.1.2
|
||||||
jsonwebtoken: 9.0.2
|
jsonwebtoken: 9.0.2
|
||||||
mime-types: 2.1.35
|
mime-types: 2.1.35
|
||||||
retry-axios: 2.6.0(axios@1.8.3)
|
retry-axios: 2.6.0(axios@1.8.3(debug@4.4.0))
|
||||||
tough-cookie: 4.1.3
|
tough-cookie: 4.1.3
|
||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- supports-color
|
- supports-color
|
||||||
@@ -24207,7 +24197,7 @@ snapshots:
|
|||||||
|
|
||||||
kuler@2.0.0: {}
|
kuler@2.0.0: {}
|
||||||
|
|
||||||
langchain@0.3.11(b4eb53fe8b825d6e8edd96cc3d942586):
|
langchain@0.3.11(7f2a4b9c5436679ca8b0df05212b4905):
|
||||||
dependencies:
|
dependencies:
|
||||||
'@langchain/core': 0.3.30(openai@4.78.1(encoding@0.1.13)(zod@3.24.1))
|
'@langchain/core': 0.3.30(openai@4.78.1(encoding@0.1.13)(zod@3.24.1))
|
||||||
'@langchain/openai': 0.3.17(@langchain/core@0.3.30(openai@4.78.1(encoding@0.1.13)(zod@3.24.1)))(encoding@0.1.13)
|
'@langchain/openai': 0.3.17(@langchain/core@0.3.30(openai@4.78.1(encoding@0.1.13)(zod@3.24.1)))(encoding@0.1.13)
|
||||||
@@ -26584,7 +26574,7 @@ snapshots:
|
|||||||
onetime: 5.1.2
|
onetime: 5.1.2
|
||||||
signal-exit: 3.0.7
|
signal-exit: 3.0.7
|
||||||
|
|
||||||
retry-axios@2.6.0(axios@1.8.3):
|
retry-axios@2.6.0(axios@1.8.3(debug@4.4.0)):
|
||||||
dependencies:
|
dependencies:
|
||||||
axios: 1.8.3
|
axios: 1.8.3
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user