mirror of
https://github.com/n8n-io/n8n.git
synced 2024-11-12 15:44:06 -08:00
feat(core): Add Tournament as the new default expression evaluator (#6964)
Github issue / Community forum post (link here to close automatically): --------- Co-authored-by: Omar Ajoue <krynble@gmail.com> Co-authored-by: कारतोफ्फेलस्क्रिप्ट™ <aditya@netroy.in>
This commit is contained in:
parent
67b985fe89
commit
bf74f09d69
14
packages/cli/src/ExpressionEvalator.ts
Normal file
14
packages/cli/src/ExpressionEvalator.ts
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
import config from '@/config';
|
||||||
|
import { ErrorReporterProxy, ExpressionEvaluatorProxy } from 'n8n-workflow';
|
||||||
|
|
||||||
|
export const initExpressionEvaluator = () => {
|
||||||
|
ExpressionEvaluatorProxy.setEvaluator(config.getEnv('expression.evaluator'));
|
||||||
|
ExpressionEvaluatorProxy.setDifferEnabled(config.getEnv('expression.reportDifference'));
|
||||||
|
ExpressionEvaluatorProxy.setDiffReporter((expr) => {
|
||||||
|
ErrorReporterProxy.warn('Expression difference', {
|
||||||
|
extra: {
|
||||||
|
expression: expr,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
|
@ -336,6 +336,9 @@ export class Server extends AbstractServer {
|
||||||
variables: {
|
variables: {
|
||||||
limit: 0,
|
limit: 0,
|
||||||
},
|
},
|
||||||
|
expressions: {
|
||||||
|
evaluator: config.getEnv('expression.evaluator'),
|
||||||
|
},
|
||||||
banners: {
|
banners: {
|
||||||
dismissed: [],
|
dismissed: [],
|
||||||
},
|
},
|
||||||
|
|
|
@ -21,6 +21,7 @@ import { InternalHooks } from '@/InternalHooks';
|
||||||
import { PostHogClient } from '@/posthog';
|
import { PostHogClient } from '@/posthog';
|
||||||
import { License } from '@/License';
|
import { License } from '@/License';
|
||||||
import { ExternalSecretsManager } from '@/ExternalSecrets/ExternalSecretsManager.ee';
|
import { ExternalSecretsManager } from '@/ExternalSecrets/ExternalSecretsManager.ee';
|
||||||
|
import { initExpressionEvaluator } from '@/ExpressionEvalator';
|
||||||
|
|
||||||
export abstract class BaseCommand extends Command {
|
export abstract class BaseCommand extends Command {
|
||||||
protected logger = LoggerProxy.init(getLogger());
|
protected logger = LoggerProxy.init(getLogger());
|
||||||
|
@ -39,6 +40,7 @@ export abstract class BaseCommand extends Command {
|
||||||
|
|
||||||
async init(): Promise<void> {
|
async init(): Promise<void> {
|
||||||
await initErrorHandling();
|
await initErrorHandling();
|
||||||
|
initExpressionEvaluator();
|
||||||
|
|
||||||
process.once('SIGTERM', async () => this.stopProcess());
|
process.once('SIGTERM', async () => this.stopProcess());
|
||||||
process.once('SIGINT', async () => this.stopProcess());
|
process.once('SIGINT', async () => this.stopProcess());
|
||||||
|
|
|
@ -1199,6 +1199,21 @@ 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',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
sourceControl: {
|
sourceControl: {
|
||||||
defaultKeyPairType: {
|
defaultKeyPairType: {
|
||||||
doc: 'Default SSH key type to use when generating SSH keys',
|
doc: 'Default SSH key type to use when generating SSH keys',
|
||||||
|
|
|
@ -59,6 +59,7 @@ import { useHistoryHelper } from '@/composables/useHistoryHelper';
|
||||||
import { newVersions } from '@/mixins/newVersions';
|
import { newVersions } from '@/mixins/newVersions';
|
||||||
import { useRoute } from 'vue-router';
|
import { useRoute } from 'vue-router';
|
||||||
import { useExternalHooks } from '@/composables';
|
import { useExternalHooks } from '@/composables';
|
||||||
|
import { ExpressionEvaluatorProxy } from 'n8n-workflow';
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
name: 'App',
|
name: 'App',
|
||||||
|
@ -148,6 +149,7 @@ export default defineComponent({
|
||||||
},
|
},
|
||||||
async initialize(): Promise<void> {
|
async initialize(): Promise<void> {
|
||||||
await this.initSettings();
|
await this.initSettings();
|
||||||
|
ExpressionEvaluatorProxy.setEvaluator(useSettingsStore().settings.expressions.evaluator);
|
||||||
await Promise.all([this.loginWithCookie(), this.initTemplates()]);
|
await Promise.all([this.loginWithCookie(), this.initTemplates()]);
|
||||||
},
|
},
|
||||||
trackPage(): void {
|
trackPage(): void {
|
||||||
|
|
|
@ -48,6 +48,7 @@
|
||||||
"@types/xml2js": "^0.4.11"
|
"@types/xml2js": "^0.4.11"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@n8n/tournament": "^1.0.2",
|
||||||
"@n8n_io/riot-tmpl": "^4.0.0",
|
"@n8n_io/riot-tmpl": "^4.0.0",
|
||||||
"ast-types": "0.15.2",
|
"ast-types": "0.15.2",
|
||||||
"crypto-js": "^4.1.1",
|
"crypto-js": "^4.1.1",
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import * as tmpl from '@n8n_io/riot-tmpl';
|
|
||||||
import { DateTime, Duration, Interval } from 'luxon';
|
import { DateTime, Duration, Interval } from 'luxon';
|
||||||
|
import * as tmpl from '@n8n_io/riot-tmpl';
|
||||||
|
|
||||||
import type {
|
import type {
|
||||||
IDataObject,
|
IDataObject,
|
||||||
|
@ -22,6 +22,7 @@ import type { Workflow } from './Workflow';
|
||||||
import { extend, extendOptional } from './Extensions';
|
import { extend, extendOptional } from './Extensions';
|
||||||
import { extendedFunctions } from './Extensions/ExtendedFunctions';
|
import { extendedFunctions } from './Extensions/ExtendedFunctions';
|
||||||
import { extendSyntax } from './Extensions/ExpressionExtension';
|
import { extendSyntax } from './Extensions/ExpressionExtension';
|
||||||
|
import { evaluateExpression, setErrorHandler } from './ExpressionEvaluatorProxy';
|
||||||
|
|
||||||
const IS_FRONTEND_IN_DEV_MODE =
|
const IS_FRONTEND_IN_DEV_MODE =
|
||||||
typeof process === 'object' &&
|
typeof process === 'object' &&
|
||||||
|
@ -40,13 +41,10 @@ export const isExpressionError = (error: unknown): error is ExpressionError =>
|
||||||
export const isTypeError = (error: unknown): error is TypeError =>
|
export const isTypeError = (error: unknown): error is TypeError =>
|
||||||
error instanceof TypeError || (error instanceof Error && error.name === 'TypeError');
|
error instanceof TypeError || (error instanceof Error && error.name === 'TypeError');
|
||||||
|
|
||||||
// Set it to use double curly brackets instead of single ones
|
|
||||||
tmpl.brackets.set('{{ }}');
|
|
||||||
|
|
||||||
// Make sure that error get forwarded
|
// Make sure that error get forwarded
|
||||||
tmpl.tmpl.errorHandler = (error: Error) => {
|
setErrorHandler((error: Error) => {
|
||||||
if (isExpressionError(error)) throw error;
|
if (isExpressionError(error)) throw error;
|
||||||
};
|
});
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/naming-convention
|
// eslint-disable-next-line @typescript-eslint/naming-convention
|
||||||
const AsyncFunction = (async () => {}).constructor as FunctionConstructor;
|
const AsyncFunction = (async () => {}).constructor as FunctionConstructor;
|
||||||
|
@ -339,7 +337,7 @@ export class Expression {
|
||||||
[Function, AsyncFunction].forEach(({ prototype }) =>
|
[Function, AsyncFunction].forEach(({ prototype }) =>
|
||||||
Object.defineProperty(prototype, 'constructor', { value: fnConstructors.mock }),
|
Object.defineProperty(prototype, 'constructor', { value: fnConstructors.mock }),
|
||||||
);
|
);
|
||||||
return tmpl.tmpl(expression, data);
|
return evaluateExpression(expression, data);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
if (isExpressionError(error)) throw error;
|
if (isExpressionError(error)) throw error;
|
||||||
|
|
||||||
|
|
149
packages/workflow/src/ExpressionEvaluatorProxy.ts
Normal file
149
packages/workflow/src/ExpressionEvaluatorProxy.ts
Normal file
|
@ -0,0 +1,149 @@
|
||||||
|
import * as tmpl from '@n8n_io/riot-tmpl';
|
||||||
|
import type { ReturnValue, TmplDifference } from '@n8n/tournament';
|
||||||
|
import { Tournament } from '@n8n/tournament';
|
||||||
|
import type { ExpressionEvaluatorType } from './Interfaces';
|
||||||
|
import * as LoggerProxy from './LoggerProxy';
|
||||||
|
|
||||||
|
type Evaluator = (expr: string, data: unknown) => tmpl.ReturnValue;
|
||||||
|
type ErrorHandler = (error: Error) => void;
|
||||||
|
type DifferenceHandler = (expr: string) => void;
|
||||||
|
|
||||||
|
// Set it to use double curly brackets instead of single ones
|
||||||
|
tmpl.brackets.set('{{ }}');
|
||||||
|
|
||||||
|
let errorHandler: ErrorHandler = () => {};
|
||||||
|
let differenceHandler: DifferenceHandler = () => {};
|
||||||
|
const differenceChecker = (diff: TmplDifference) => {
|
||||||
|
try {
|
||||||
|
if (diff.same) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
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);
|
||||||
|
let evaluator: Evaluator = tmpl.tmpl;
|
||||||
|
let currentEvaluatorType: ExpressionEvaluatorType = 'tmpl';
|
||||||
|
let diffExpressions = false;
|
||||||
|
|
||||||
|
export const setErrorHandler = (handler: ErrorHandler) => {
|
||||||
|
errorHandler = handler;
|
||||||
|
tmpl.tmpl.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) => {
|
||||||
|
if (!diffExpressions) {
|
||||||
|
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!;
|
||||||
|
};
|
|
@ -2117,6 +2117,8 @@ export interface IPublicApiSettings {
|
||||||
|
|
||||||
export type ILogLevel = 'info' | 'debug' | 'warn' | 'error' | 'verbose' | 'silent';
|
export type ILogLevel = 'info' | 'debug' | 'warn' | 'error' | 'verbose' | 'silent';
|
||||||
|
|
||||||
|
export type ExpressionEvaluatorType = 'tmpl' | 'tournament';
|
||||||
|
|
||||||
export interface IN8nUISettings {
|
export interface IN8nUISettings {
|
||||||
endpointWebhook: string;
|
endpointWebhook: string;
|
||||||
endpointWebhookTest: string;
|
endpointWebhookTest: string;
|
||||||
|
@ -2203,6 +2205,9 @@ export interface IN8nUISettings {
|
||||||
variables: {
|
variables: {
|
||||||
limit: number;
|
limit: number;
|
||||||
};
|
};
|
||||||
|
expressions: {
|
||||||
|
evaluator: ExpressionEvaluatorType;
|
||||||
|
};
|
||||||
mfa: {
|
mfa: {
|
||||||
enabled: boolean;
|
enabled: boolean;
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
import * as LoggerProxy from './LoggerProxy';
|
import * as LoggerProxy from './LoggerProxy';
|
||||||
export * as ErrorReporterProxy from './ErrorReporterProxy';
|
export * as ErrorReporterProxy from './ErrorReporterProxy';
|
||||||
|
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';
|
||||||
|
|
|
@ -11,221 +11,227 @@ import { baseFixtures } from './ExpressionFixtures/base';
|
||||||
import type { INodeExecutionData } from '@/Interfaces';
|
import type { INodeExecutionData } from '@/Interfaces';
|
||||||
import { extendSyntax } from '@/Extensions/ExpressionExtension';
|
import { extendSyntax } from '@/Extensions/ExpressionExtension';
|
||||||
import { ExpressionError } from '@/ExpressionError';
|
import { ExpressionError } from '@/ExpressionError';
|
||||||
|
import { setDifferEnabled, setEvaluator } from '@/ExpressionEvaluatorProxy';
|
||||||
|
|
||||||
describe('Expression', () => {
|
setDifferEnabled(true);
|
||||||
describe('getParameterValue()', () => {
|
|
||||||
const nodeTypes = Helpers.NodeTypes();
|
|
||||||
const workflow = new Workflow({
|
|
||||||
nodes: [
|
|
||||||
{
|
|
||||||
name: 'node',
|
|
||||||
typeVersion: 1,
|
|
||||||
type: 'test.set',
|
|
||||||
id: 'uuid-1234',
|
|
||||||
position: [0, 0],
|
|
||||||
parameters: {},
|
|
||||||
},
|
|
||||||
],
|
|
||||||
connections: {},
|
|
||||||
active: false,
|
|
||||||
nodeTypes,
|
|
||||||
});
|
|
||||||
const expression = new Expression(workflow);
|
|
||||||
|
|
||||||
const evaluate = (value: string) =>
|
for (const evaluator of ['tmpl', 'tournament'] as const) {
|
||||||
expression.getParameterValue(value, null, 0, 0, 'node', [], 'manual', '', {});
|
setEvaluator(evaluator);
|
||||||
|
describe(`Expression (with ${evaluator})`, () => {
|
||||||
it('should not be able to use global built-ins from denylist', () => {
|
describe('getParameterValue()', () => {
|
||||||
expect(evaluate('={{document}}')).toEqual({});
|
const nodeTypes = Helpers.NodeTypes();
|
||||||
expect(evaluate('={{window}}')).toEqual({});
|
const workflow = new Workflow({
|
||||||
|
nodes: [
|
||||||
expect(evaluate('={{Window}}')).toEqual({});
|
{
|
||||||
expect(evaluate('={{globalThis}}')).toEqual({});
|
name: 'node',
|
||||||
expect(evaluate('={{self}}')).toEqual({});
|
typeVersion: 1,
|
||||||
|
type: 'test.set',
|
||||||
expect(evaluate('={{alert}}')).toEqual({});
|
id: 'uuid-1234',
|
||||||
expect(evaluate('={{prompt}}')).toEqual({});
|
position: [0, 0],
|
||||||
expect(evaluate('={{confirm}}')).toEqual({});
|
parameters: {},
|
||||||
|
},
|
||||||
expect(evaluate('={{eval}}')).toEqual({});
|
],
|
||||||
expect(evaluate('={{uneval}}')).toEqual({});
|
connections: {},
|
||||||
expect(evaluate('={{setTimeout}}')).toEqual({});
|
active: false,
|
||||||
expect(evaluate('={{setInterval}}')).toEqual({});
|
nodeTypes,
|
||||||
expect(evaluate('={{Function}}')).toEqual({});
|
|
||||||
|
|
||||||
expect(evaluate('={{fetch}}')).toEqual({});
|
|
||||||
expect(evaluate('={{XMLHttpRequest}}')).toEqual({});
|
|
||||||
|
|
||||||
expect(evaluate('={{Promise}}')).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('={{constructor}}')).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(),
|
|
||||||
);
|
|
||||||
expect(evaluate('={{Interval.after(new Date(), 100)}}')).toEqual(
|
|
||||||
Interval.after(new Date(), 100),
|
|
||||||
);
|
|
||||||
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());
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should not able to do arbitrary code execution', () => {
|
|
||||||
const testFn = jest.fn();
|
|
||||||
Object.assign(global, { testFn });
|
|
||||||
expect(() => evaluate("={{ Date['constructor']('testFn()')()}}")).toThrowError(
|
|
||||||
new ExpressionError('Arbitrary code execution detected'),
|
|
||||||
);
|
|
||||||
expect(testFn).not.toHaveBeenCalled();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('Test all expression value fixtures', () => {
|
|
||||||
const nodeTypes = Helpers.NodeTypes();
|
|
||||||
const workflow = new Workflow({
|
|
||||||
nodes: [
|
|
||||||
{
|
|
||||||
name: 'node',
|
|
||||||
typeVersion: 1,
|
|
||||||
type: 'test.set',
|
|
||||||
id: 'uuid-1234',
|
|
||||||
position: [0, 0],
|
|
||||||
parameters: {},
|
|
||||||
},
|
|
||||||
],
|
|
||||||
connections: {},
|
|
||||||
active: false,
|
|
||||||
nodeTypes,
|
|
||||||
});
|
|
||||||
|
|
||||||
const expression = new Expression(workflow);
|
|
||||||
|
|
||||||
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, () => {
|
|
||||||
for (const test of t.tests.filter(
|
|
||||||
(test) => test.type === 'evaluation',
|
|
||||||
) as ExpressionTestEvaluation[]) {
|
|
||||||
expect(evaluate(t.expression, test.input.map((d) => ({ json: d })) as any)).toStrictEqual(
|
|
||||||
test.output,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
}
|
const expression = new Expression(workflow);
|
||||||
});
|
|
||||||
|
|
||||||
describe('Test all expression transform fixtures', () => {
|
const evaluate = (value: string) =>
|
||||||
for (const t of baseFixtures) {
|
expression.getParameterValue(value, null, 0, 0, 'node', [], 'manual', '', {});
|
||||||
if (!t.tests.some((test) => test.type === 'transform')) {
|
|
||||||
continue;
|
it('should not be able to use global built-ins from denylist', () => {
|
||||||
}
|
expect(evaluate('={{document}}')).toEqual({});
|
||||||
test(t.expression, () => {
|
expect(evaluate('={{window}}')).toEqual({});
|
||||||
for (const test of t.tests.filter(
|
|
||||||
(test) => test.type === 'transform',
|
expect(evaluate('={{Window}}')).toEqual({});
|
||||||
) as ExpressionTestTransform[]) {
|
expect(evaluate('={{globalThis}}')).toEqual({});
|
||||||
const expr = t.expression;
|
expect(evaluate('={{self}}')).toEqual({});
|
||||||
expect(extendSyntax(expr, test.forceTransform)).toEqual(test.result ?? expr);
|
|
||||||
}
|
expect(evaluate('={{alert}}')).toEqual({});
|
||||||
|
expect(evaluate('={{prompt}}')).toEqual({});
|
||||||
|
expect(evaluate('={{confirm}}')).toEqual({});
|
||||||
|
|
||||||
|
expect(evaluate('={{eval}}')).toEqual({});
|
||||||
|
expect(evaluate('={{uneval}}')).toEqual({});
|
||||||
|
expect(evaluate('={{setTimeout}}')).toEqual({});
|
||||||
|
expect(evaluate('={{setInterval}}')).toEqual({});
|
||||||
|
expect(evaluate('={{Function}}')).toEqual({});
|
||||||
|
|
||||||
|
expect(evaluate('={{fetch}}')).toEqual({});
|
||||||
|
expect(evaluate('={{XMLHttpRequest}}')).toEqual({});
|
||||||
|
|
||||||
|
expect(evaluate('={{Promise}}')).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('={{constructor}}')).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(),
|
||||||
|
);
|
||||||
|
expect(evaluate('={{Interval.after(new Date(), 100)}}')).toEqual(
|
||||||
|
Interval.after(new Date(), 100),
|
||||||
|
);
|
||||||
|
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());
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should not able to do arbitrary code execution', () => {
|
||||||
|
const testFn = jest.fn();
|
||||||
|
Object.assign(global, { testFn });
|
||||||
|
expect(() => evaluate("={{ Date['constructor']('testFn()')()}}")).toThrowError(
|
||||||
|
new ExpressionError('Arbitrary code execution detected'),
|
||||||
|
);
|
||||||
|
expect(testFn).not.toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('Test all expression value fixtures', () => {
|
||||||
|
const nodeTypes = Helpers.NodeTypes();
|
||||||
|
const workflow = new Workflow({
|
||||||
|
nodes: [
|
||||||
|
{
|
||||||
|
name: 'node',
|
||||||
|
typeVersion: 1,
|
||||||
|
type: 'test.set',
|
||||||
|
id: 'uuid-1234',
|
||||||
|
position: [0, 0],
|
||||||
|
parameters: {},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
connections: {},
|
||||||
|
active: false,
|
||||||
|
nodeTypes,
|
||||||
|
});
|
||||||
|
|
||||||
|
const expression = new Expression(workflow);
|
||||||
|
|
||||||
|
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, () => {
|
||||||
|
for (const test of t.tests.filter(
|
||||||
|
(test) => test.type === 'evaluation',
|
||||||
|
) as ExpressionTestEvaluation[]) {
|
||||||
|
expect(
|
||||||
|
evaluate(t.expression, test.input.map((d) => ({ json: d })) as any),
|
||||||
|
).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.type === 'transform',
|
||||||
|
) as ExpressionTestTransform[]) {
|
||||||
|
const expr = t.expression;
|
||||||
|
expect(extendSyntax(expr, test.forceTransform)).toEqual(test.result ?? expr);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
}
|
||||||
|
|
|
@ -1277,6 +1277,9 @@ importers:
|
||||||
|
|
||||||
packages/workflow:
|
packages/workflow:
|
||||||
dependencies:
|
dependencies:
|
||||||
|
'@n8n/tournament':
|
||||||
|
specifier: ^1.0.2
|
||||||
|
version: 1.0.2
|
||||||
'@n8n_io/riot-tmpl':
|
'@n8n_io/riot-tmpl':
|
||||||
specifier: ^4.0.0
|
specifier: ^4.0.0
|
||||||
version: 4.0.0
|
version: 4.0.0
|
||||||
|
@ -4635,6 +4638,16 @@ packages:
|
||||||
- '@lezer/common'
|
- '@lezer/common'
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
|
/@n8n/tournament@1.0.2:
|
||||||
|
resolution: {integrity: sha512-fTpi7F8ra5flGSVfRzohPyG7czAAKCZPlLjdKdwbLJivLoI/Ekhgodov1jfVSCVFVbwQ06gRQRxLEDzl2jl8ig==}
|
||||||
|
engines: {node: '>=18.10', pnpm: '>=8.6'}
|
||||||
|
dependencies:
|
||||||
|
'@n8n_io/riot-tmpl': 4.0.1
|
||||||
|
ast-types: 0.16.1
|
||||||
|
esprima-next: 5.8.4
|
||||||
|
recast: 0.22.0
|
||||||
|
dev: false
|
||||||
|
|
||||||
/@n8n/vm2@3.9.20:
|
/@n8n/vm2@3.9.20:
|
||||||
resolution: {integrity: sha512-qk2oJYkuFRVSTxoro4obX/sv/wT1pViZjHh/isjOvFB93D52QIg3TCjMPsHOfHTmkxCKJffjLrUvjIwvWzSMCQ==}
|
resolution: {integrity: sha512-qk2oJYkuFRVSTxoro4obX/sv/wT1pViZjHh/isjOvFB93D52QIg3TCjMPsHOfHTmkxCKJffjLrUvjIwvWzSMCQ==}
|
||||||
engines: {node: '>=18.10', pnpm: '>=8.6.12'}
|
engines: {node: '>=18.10', pnpm: '>=8.6.12'}
|
||||||
|
@ -4660,6 +4673,12 @@ packages:
|
||||||
eslint-config-riot: 1.0.0
|
eslint-config-riot: 1.0.0
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
|
/@n8n_io/riot-tmpl@4.0.1:
|
||||||
|
resolution: {integrity: sha512-/zdRbEfTFjsm1NqnpPQHgZTkTdbp5v3VUxGeMA9098sps8jRCTraQkc3AQstJgHUm7ylBXJcIVhnVeLUMWAfwQ==}
|
||||||
|
dependencies:
|
||||||
|
eslint-config-riot: 1.0.0
|
||||||
|
dev: false
|
||||||
|
|
||||||
/@ndelangen/get-tarball@3.0.7:
|
/@ndelangen/get-tarball@3.0.7:
|
||||||
resolution: {integrity: sha512-NqGfTZIZpRFef1GoVaShSSRwDC3vde3ThtTeqFdcYd6ipKqnfEVhjK2hUeHjCQUcptyZr2TONqcloFXM+5QBrQ==}
|
resolution: {integrity: sha512-NqGfTZIZpRFef1GoVaShSSRwDC3vde3ThtTeqFdcYd6ipKqnfEVhjK2hUeHjCQUcptyZr2TONqcloFXM+5QBrQ==}
|
||||||
dependencies:
|
dependencies:
|
||||||
|
@ -8890,7 +8909,6 @@ packages:
|
||||||
is-nan: 1.3.2
|
is-nan: 1.3.2
|
||||||
object-is: 1.1.5
|
object-is: 1.1.5
|
||||||
util: 0.12.5
|
util: 0.12.5
|
||||||
dev: true
|
|
||||||
|
|
||||||
/assertion-error@1.1.0:
|
/assertion-error@1.1.0:
|
||||||
resolution: {integrity: sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==}
|
resolution: {integrity: sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==}
|
||||||
|
@ -8919,7 +8937,6 @@ packages:
|
||||||
engines: {node: '>=4'}
|
engines: {node: '>=4'}
|
||||||
dependencies:
|
dependencies:
|
||||||
tslib: 2.6.1
|
tslib: 2.6.1
|
||||||
dev: true
|
|
||||||
|
|
||||||
/astral-regex@2.0.0:
|
/astral-regex@2.0.0:
|
||||||
resolution: {integrity: sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ==}
|
resolution: {integrity: sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ==}
|
||||||
|
@ -11483,7 +11500,6 @@ packages:
|
||||||
|
|
||||||
/es6-object-assign@1.1.0:
|
/es6-object-assign@1.1.0:
|
||||||
resolution: {integrity: sha512-MEl9uirslVwqQU369iHNWZXsI8yaZYGg/D65aOgZkeyFJwHYSxilf7rQzXKI7DdDuBPrBXbfk3sl9hJhmd5AUw==}
|
resolution: {integrity: sha512-MEl9uirslVwqQU369iHNWZXsI8yaZYGg/D65aOgZkeyFJwHYSxilf7rQzXKI7DdDuBPrBXbfk3sl9hJhmd5AUw==}
|
||||||
dev: true
|
|
||||||
|
|
||||||
/es6-symbol@3.1.3:
|
/es6-symbol@3.1.3:
|
||||||
resolution: {integrity: sha512-NJ6Yn3FuDinBaBRWl/q5X/s4koRHBrgKAu+yGI6JCBeiu3qrcbJhwT2GeR/EXVfylRk8dpQVJoLEFhK+Mu31NA==}
|
resolution: {integrity: sha512-NJ6Yn3FuDinBaBRWl/q5X/s4koRHBrgKAu+yGI6JCBeiu3qrcbJhwT2GeR/EXVfylRk8dpQVJoLEFhK+Mu31NA==}
|
||||||
|
@ -18637,7 +18653,6 @@ packages:
|
||||||
esprima: 4.0.1
|
esprima: 4.0.1
|
||||||
source-map: 0.6.1
|
source-map: 0.6.1
|
||||||
tslib: 2.6.1
|
tslib: 2.6.1
|
||||||
dev: true
|
|
||||||
|
|
||||||
/recast@0.23.3:
|
/recast@0.23.3:
|
||||||
resolution: {integrity: sha512-HbCVFh2ANP6a09nzD4lx7XthsxMOJWKX5pIcUwtLrmeEIl3I0DwjCoVXDE0Aobk+7k/mS3H50FK4iuYArpcT6Q==}
|
resolution: {integrity: sha512-HbCVFh2ANP6a09nzD4lx7XthsxMOJWKX5pIcUwtLrmeEIl3I0DwjCoVXDE0Aobk+7k/mS3H50FK4iuYArpcT6Q==}
|
||||||
|
|
Loading…
Reference in a new issue