diff --git a/.prettierrc b/.prettierrc index bf49d1a..dbd95f7 100644 --- a/.prettierrc +++ b/.prettierrc @@ -5,5 +5,7 @@ "plugins": ["@trivago/prettier-plugin-sort-imports"], "importOrder": ["^@modou/(.*)$", "^[./]"], "importOrderSeparation": true, - "importOrderSortSpecifiers": true + "importOrderSortSpecifiers": true, + "spaced-comment": 2 + } diff --git a/auxiliaries/ast/index.ts b/auxiliaries/ast/index.ts new file mode 100644 index 0000000..410670d --- /dev/null +++ b/auxiliaries/ast/index.ts @@ -0,0 +1,52 @@ +import { + ObjectExpression, + PropertyNode, + isIdentifierNode, + isVariableDeclarator, + isObjectExpression, + isLiteralNode, + isPropertyNode, + isPropertyAFunctionNode, + getAST, + extractIdentifierInfoFromCode, + entityRefactorFromCode, + extractInvalidTopLevelMemberExpressionsFromCode, + getFunctionalParamsFromNode, + isTypeOfFunction, + MemberExpressionData, + IdentifierInfo, +} from "./src"; + +// constants +import { ECMA_VERSION, SourceType, NodeTypes } from "./src/constants"; + +// JSObjects +import { parseJSObjectWithAST, JsObjectProperty } from "./src/jsObject"; + +// types or interfaces should be exported with type keyword, while enums can be exported like normal functions +export type { + ObjectExpression, + PropertyNode, + MemberExpressionData, + IdentifierInfo, + JsObjectProperty, +}; + +export { + isIdentifierNode, + isVariableDeclarator, + isObjectExpression, + isLiteralNode, + isPropertyNode, + isPropertyAFunctionNode, + getAST, + extractIdentifierInfoFromCode, + entityRefactorFromCode, + extractInvalidTopLevelMemberExpressionsFromCode, + getFunctionalParamsFromNode, + isTypeOfFunction, + parseJSObjectWithAST, + ECMA_VERSION, + SourceType, + NodeTypes, +}; diff --git a/auxiliaries/ast/package.json b/auxiliaries/ast/package.json new file mode 100644 index 0000000..214dcdc --- /dev/null +++ b/auxiliaries/ast/package.json @@ -0,0 +1,41 @@ +{ + "name": "@modou/ast", + "private": true, + "version": "1.0.0", + "description": "", + "main": "index.ts", + "publishConfig": { + "directory": "build" + }, + "scripts": { + "test:unit": "$(npm bin)/jest -b --colors --no-cache --silent --coverage --collectCoverage=true --coverageDirectory='../' --coverageReporters='json-summary'", + "test:jest": "$(npm bin)/jest --watch", + "build": "rollup -c", + "start": "rollup -c", + "link-package": "yarn install && rollup -c && cd build && cp -R ../node_modules ./node_modules && yarn link" + }, + "dependencies": { + "acorn": "^8.8.0", + "acorn-walk": "^8.2.0", + "astring": "^1.7.5", + "lodash": "^4.17.21", + "rollup": "^2.77.0", + "typescript": "4.5.5", + "unescape-js": "^1.1.4" + }, + "devDependencies": { + "@babel/preset-typescript": "^7.17.12", + "@rollup/plugin-commonjs": "^22.0.0", + "@types/jest": "29.0.3", + "@types/lodash": "^4.14.120", + "@typescript-eslint/eslint-plugin": "^5.25.0", + "@typescript-eslint/parser": "^5.25.0", + "jest": "29.0.3", + "rollup-plugin-generate-package-json": "^3.2.0", + "rollup-plugin-peer-deps-external": "^2.2.4", + "rollup-plugin-typescript2": "^0.32.0", + "ts-jest": "29.0.1" + }, + "author": "", + "license": "ISC" +} diff --git a/auxiliaries/ast/rollup.config.js b/auxiliaries/ast/rollup.config.js new file mode 100644 index 0000000..99cd123 --- /dev/null +++ b/auxiliaries/ast/rollup.config.js @@ -0,0 +1,31 @@ +import peerDepsExternal from "rollup-plugin-peer-deps-external"; +import commonjs from "@rollup/plugin-commonjs"; +import typescript from "rollup-plugin-typescript2"; +import generatePackageJson from "rollup-plugin-generate-package-json"; + +const packageJson = require("./package.json"); + +export default { + // TODO: Figure out regex where each directory can be a separate module without having to manually add them + input: ["./index.ts"], + output: [ + { + file: packageJson.module, + format: "esm", + sourcemap: true, + }, + { + file: packageJson.main, + format: "cjs", + sourcemap: true, + }, + ], + plugins: [ + peerDepsExternal(), + commonjs(), + typescript({ useTsconfigDeclarationDir: true }), + generatePackageJson({ + baseContents: (pkg) => pkg, + }), + ], +}; diff --git a/auxiliaries/ast/src/constants/ast.ts b/auxiliaries/ast/src/constants/ast.ts new file mode 100644 index 0000000..891fdd3 --- /dev/null +++ b/auxiliaries/ast/src/constants/ast.ts @@ -0,0 +1,30 @@ +export const ECMA_VERSION = 11; + +/* Indicates the mode the code should be parsed in. +This influences global strict mode and parsing of import and export declarations. +*/ +export enum SourceType { + script = 'script', + module = 'module', +} + +// Each node has an attached type property which further defines +// what all properties can the node have. +// We will just define the ones we are working with +export enum NodeTypes { + Identifier = 'Identifier', + AssignmentPattern = 'AssignmentPattern', + Literal = 'Literal', + Property = 'Property', + // Declaration - https://github.com/estree/estree/blob/master/es5.md#declarations + FunctionDeclaration = 'FunctionDeclaration', + ExportDefaultDeclaration = 'ExportDefaultDeclaration', + VariableDeclarator = 'VariableDeclarator', + // Expression - https://github.com/estree/estree/blob/master/es5.md#expressions + MemberExpression = 'MemberExpression', + FunctionExpression = 'FunctionExpression', + ArrowFunctionExpression = 'ArrowFunctionExpression', + ObjectExpression = 'ObjectExpression', + ArrayExpression = 'ArrayExpression', + ThisExpression = 'ThisExpression', +} diff --git a/auxiliaries/ast/src/constants/index.ts b/auxiliaries/ast/src/constants/index.ts new file mode 100644 index 0000000..019647c --- /dev/null +++ b/auxiliaries/ast/src/constants/index.ts @@ -0,0 +1 @@ +export * from './ast'; diff --git a/auxiliaries/ast/src/index.test.ts b/auxiliaries/ast/src/index.test.ts new file mode 100644 index 0000000..1fef1e4 --- /dev/null +++ b/auxiliaries/ast/src/index.test.ts @@ -0,0 +1,496 @@ +import { extractIdentifierInfoFromCode } from "../src/index"; +import { parseJSObjectWithAST } from "../src/jsObject"; + +describe("getAllIdentifiers", () => { + it("works properly", () => { + const cases: Array<{ + script: string; + expectedResults: string[]; + invalidIdentifiers?: Record; + }> = [ + { + // Entity reference + script: "DirectTableReference", + expectedResults: ["DirectTableReference"], + }, + { + // One level nesting + script: "TableDataReference.data", + expectedResults: ["TableDataReference.data"], + }, + { + // Deep nesting + script: "TableDataDetailsReference.data.details", + expectedResults: ["TableDataDetailsReference.data.details"], + }, + { + // Deep nesting + script: "TableDataDetailsMoreReference.data.details.more", + expectedResults: ["TableDataDetailsMoreReference.data.details.more"], + }, + { + // Deep optional chaining + script: "TableDataOptionalReference.data?.details.more", + expectedResults: ["TableDataOptionalReference.data"], + }, + { + // Deep optional chaining with logical operator + script: + "TableDataOptionalWithLogical.data?.details.more || FallbackTableData.data", + expectedResults: [ + "TableDataOptionalWithLogical.data", + "FallbackTableData.data", + ], + }, + { + // null coalescing + script: "TableDataOptionalWithLogical.data ?? FallbackTableData.data", + expectedResults: [ + "TableDataOptionalWithLogical.data", + "FallbackTableData.data", + ], + }, + { + // Basic map function + script: "Table5.data.map(c => ({ name: c.name }))", + expectedResults: ["Table5.data.map"], + }, + { + // Literal property search + script: "Table6['data']", + expectedResults: ["Table6"], + }, + { + // Deep literal property search + script: "TableDataOptionalReference['data'].details", + expectedResults: ["TableDataOptionalReference"], + }, + { + // Array index search + script: "array[8]", + expectedResults: ["array[8]"], + }, + { + // Deep array index search + script: "Table7.data[4]", + expectedResults: ["Table7.data[4]"], + }, + { + // Deep array index search + script: "Table7.data[4].value", + expectedResults: ["Table7.data[4].value"], + }, + { + // string literal and array index search + script: "Table['data'][9]", + expectedResults: ["Table"], + }, + { + // array index and string literal search + script: "Array[9]['data']", + expectedResults: [], + invalidIdentifiers: { + Array: true, + }, + }, + { + // Index identifier search + script: "Table8.data[row][name]", + expectedResults: ["Table8.data", "row"], + // name is a global scoped variable + invalidIdentifiers: { + name: true, + }, + }, + { + // Index identifier search with global + script: "Table9.data[appsmith.store.row]", + expectedResults: ["Table9.data", "appsmith.store.row"], + }, + { + // Index literal with further nested lookups + script: "Table10.data[row].name", + expectedResults: ["Table10.data", "row"], + }, + { + // IIFE and if conditions + script: + "(function(){ if(Table11.isVisible) { return Api1.data } else { return Api2.data } })()", + expectedResults: ["Table11.isVisible", "Api1.data", "Api2.data"], + }, + { + // Functions and arguments + script: "JSObject1.run(Api1.data, Api2.data)", + expectedResults: ["JSObject1.run", "Api1.data", "Api2.data"], + }, + { + // IIFE - without braces + script: `function() { + const index = Input1.text + + const obj = { + "a": 123 + } + + return obj[index] + + }()`, + expectedResults: ["Input1.text"], + }, + { + // IIFE + script: `(function() { + const index = Input2.text + + const obj = { + "a": 123 + } + + return obj[index] + + })()`, + expectedResults: ["Input2.text"], + }, + { + // arrow IIFE - without braces - will fail + script: `() => { + const index = Input3.text + + const obj = { + "a": 123 + } + + return obj[index] + + }()`, + expectedResults: [], + }, + { + // arrow IIFE + script: `(() => { + const index = Input4.text + + const obj = { + "a": 123 + } + + return obj[index] + + })()`, + expectedResults: ["Input4.text"], + }, + { + // Direct object access + script: `{ "a": 123 }[Input5.text]`, + expectedResults: ["Input5.text"], + }, + { + // Function declaration and default arguments + script: `function run(apiData = Api1.data) { + return apiData; + }`, + expectedResults: ["Api1.data"], + }, + { + // Function declaration with arguments + script: `function run(data) { + return data; + }`, + expectedResults: [], + }, + { + // anonymous function with variables + script: `() => { + let row = 0; + const data = {}; + while(row < 10) { + data["test__" + row] = Table12.data[row]; + row = row += 1; + } + }`, + expectedResults: ["Table12.data"], + }, + { + // function with variables + script: `function myFunction() { + let row = 0; + const data = {}; + while(row < 10) { + data["test__" + row] = Table13.data[row]; + row = row += 1; + } + }`, + expectedResults: ["Table13.data"], + }, + { + // expression with arithmetic operations + script: `Table14.data + 15`, + expectedResults: ["Table14.data"], + }, + { + // expression with logical operations + script: `Table15.data || [{}]`, + expectedResults: ["Table15.data"], + }, + // JavaScript built in classes should not be valid identifiers + { + script: `function(){ + const firstApiRun = Api1.run(); + const secondApiRun = Api2.run(); + const randomNumber = Math.random(); + return Promise.all([firstApiRun, secondApiRun]) + }()`, + expectedResults: ["Api1.run", "Api2.run"], + invalidIdentifiers: { + Math: true, + Promise: true, + }, + }, + // Global dependencies should not be valid identifiers + { + script: `function(){ + const names = [["john","doe"],["Jane","dane"]]; + const flattenedNames = _.flatten(names); + return {flattenedNames, time: moment()} + }()`, + expectedResults: [], + invalidIdentifiers: { + _: true, + moment: true, + }, + }, + // browser Apis should not be valid identifiers + { + script: `function(){ + const names = { + firstName: "John", + lastName:"Doe" + }; + const joinedName = Object.values(names).join(" "); + console.log(joinedName) + return Api2.name + }()`, + expectedResults: ["Api2.name"], + invalidIdentifiers: { + Object: true, + console: true, + }, + }, + // identifiers and member expressions derived from params should not be valid identifiers + { + script: `function(a, b){ + return a.name + b.name + }()`, + expectedResults: [], + }, + // identifiers and member expressions derived from local variables should not be valid identifiers + { + script: `function(){ + const a = "variableA"; + const b = "variableB"; + return a.length + b.length + }()`, + expectedResults: [], + }, + // "appsmith" is an internal identifier and should be a valid reference + { + script: `function(){ + return appsmith.user + }()`, + expectedResults: ["appsmith.user"], + }, + ]; + + // commenting to trigger test shared workflow action + cases.forEach((perCase) => { + const { references } = extractIdentifierInfoFromCode( + perCase.script, + 2, + perCase.invalidIdentifiers + ); + expect(references).toStrictEqual(perCase.expectedResults); + }); + }); +}); + +describe("parseJSObjectWithAST", () => { + it("parse js object", () => { + const body = `{ + myVar1: [], + myVar2: {}, + myFun1: () => { + //write code here + }, + myFun2: async () => { + //use async-await or promises + } +}`; + const parsedObject = [ + { + key: "myVar1", + value: "[]", + type: "ArrayExpression", + }, + { + key: "myVar2", + value: "{}", + type: "ObjectExpression", + }, + { + key: "myFun1", + value: "() => {}", + type: "ArrowFunctionExpression", + arguments: [], + }, + { + key: "myFun2", + value: "async () => {}", + type: "ArrowFunctionExpression", + arguments: [], + }, + ]; + const resultParsedObject = parseJSObjectWithAST(body); + expect(resultParsedObject).toStrictEqual(parsedObject); + }); + + it("parse js object with literal", () => { + const body = `{ + myVar1: [], + myVar2: { + "a": "app", + }, + myFun1: () => { + //write code here + }, + myFun2: async () => { + //use async-await or promises + } +}`; + const parsedObject = [ + { + key: "myVar1", + value: "[]", + type: "ArrayExpression", + }, + { + key: "myVar2", + value: '{\n "a": "app"\n}', + type: "ObjectExpression", + }, + { + key: "myFun1", + value: "() => {}", + type: "ArrowFunctionExpression", + arguments: [], + }, + { + key: "myFun2", + value: "async () => {}", + type: "ArrowFunctionExpression", + arguments: [], + }, + ]; + const resultParsedObject = parseJSObjectWithAST(body); + expect(resultParsedObject).toStrictEqual(parsedObject); + }); + + it("parse js object with variable declaration inside function", () => { + const body = `{ + myFun1: () => { + const a = { + conditions: [], + requires: 1, + testFunc: () => {}, + testFunc2: function(){} + }; + }, + myFun2: async () => { + //use async-await or promises + } + }`; + const parsedObject = [ + { + key: "myFun1", + value: `() => { + const a = { + conditions: [], + requires: 1, + testFunc: () => {}, + testFunc2: function () {} + }; +}`, + type: "ArrowFunctionExpression", + arguments: [], + }, + { + key: "myFun2", + value: "async () => {}", + type: "ArrowFunctionExpression", + arguments: [], + }, + ]; + const resultParsedObject = parseJSObjectWithAST(body); + expect(resultParsedObject).toStrictEqual(parsedObject); + }); + + it("parse js object with params of all types", () => { + const body = `{ + myFun2: async (a,b = Array(1,2,3),c = "", d = [], e = this.myVar1, f = {}, g = function(){}, h = Object.assign({}), i = String(), j = storeValue()) => { + //use async-await or promises + }, + }`; + + const parsedObject = [ + { + key: "myFun2", + value: + 'async (a, b = Array(1, 2, 3), c = "", d = [], e = this.myVar1, f = {}, g = function () {}, h = Object.assign({}), i = String(), j = storeValue()) => {}', + type: "ArrowFunctionExpression", + arguments: [ + { + paramName: "a", + defaultValue: undefined, + }, + { + paramName: "b", + defaultValue: undefined, + }, + { + paramName: "c", + defaultValue: undefined, + }, + { + paramName: "d", + defaultValue: undefined, + }, + { + paramName: "e", + defaultValue: undefined, + }, + { + paramName: "f", + defaultValue: undefined, + }, + { + paramName: "g", + defaultValue: undefined, + }, + { + paramName: "h", + defaultValue: undefined, + }, + { + paramName: "i", + defaultValue: undefined, + }, + { + paramName: "j", + defaultValue: undefined, + }, + ], + }, + ]; + const resultParsedObject = parseJSObjectWithAST(body); + expect(resultParsedObject).toEqual(parsedObject); + }); +}); diff --git a/auxiliaries/ast/src/index.ts b/auxiliaries/ast/src/index.ts new file mode 100644 index 0000000..73fce5f --- /dev/null +++ b/auxiliaries/ast/src/index.ts @@ -0,0 +1,669 @@ +import { parse, Node, SourceLocation, Options, Comment } from "acorn"; +import { ancestor, simple } from "acorn-walk"; +import { ECMA_VERSION, NodeTypes } from "./constants/ast"; +import { has, isFinite, isString, memoize, toPath } from "lodash"; +import { isTrueObject, sanitizeScript } from "./utils"; +import { jsObjectDeclaration } from "./jsObject/index"; +/* + * Valuable links: + * + * * ESTree spec: Javascript AST is called ESTree. + * Each es version has its md file in the repo to find features + * implemented and their node type + * https://github.com/estree/estree + * + * * Acorn: The parser we use to get the AST + * https://github.com/acornjs/acorn + * + * * Acorn walk: The walker we use to traverse the AST + * https://github.com/acornjs/acorn/tree/master/acorn-walk + * + * * AST Explorer: Helpful web tool to see ASTs and its parts + * https://astexplorer.net/ + * + */ + +type Pattern = IdentifierNode | AssignmentPatternNode; +type Expression = Node; +// doc: https://github.com/estree/estree/blob/master/es5.md#memberexpression +interface MemberExpressionNode extends Node { + type: NodeTypes.MemberExpression; + object: MemberExpressionNode | IdentifierNode; + property: IdentifierNode | LiteralNode; + computed: boolean; + // doc: https://github.com/estree/estree/blob/master/es2020.md#chainexpression + optional?: boolean; +} + +// doc: https://github.com/estree/estree/blob/master/es5.md#identifier +interface IdentifierNode extends Node { + type: NodeTypes.Identifier; + name: string; +} + +//Using this to handle the Variable property refactor +interface RefactorIdentifierNode extends Node { + type: NodeTypes.Identifier; + name: string; + property?: IdentifierNode; +} + +// doc: https://github.com/estree/estree/blob/master/es5.md#variabledeclarator +interface VariableDeclaratorNode extends Node { + type: NodeTypes.VariableDeclarator; + id: IdentifierNode; + init: Expression | null; +} + +// doc: https://github.com/estree/estree/blob/master/es5.md#functions +interface Function extends Node { + id: IdentifierNode | null; + params: Pattern[]; +} + +// doc: https://github.com/estree/estree/blob/master/es5.md#functiondeclaration +interface FunctionDeclarationNode extends Node, Function { + type: NodeTypes.FunctionDeclaration; +} + +// doc: https://github.com/estree/estree/blob/master/es5.md#functionexpression +interface FunctionExpressionNode extends Expression, Function { + type: NodeTypes.FunctionExpression; +} + +interface ArrowFunctionExpressionNode extends Expression, Function { + type: NodeTypes.ArrowFunctionExpression; +} + +export interface ObjectExpression extends Expression { + type: NodeTypes.ObjectExpression; + properties: Array; +} + +// doc: https://github.com/estree/estree/blob/master/es2015.md#assignmentpattern +interface AssignmentPatternNode extends Node { + type: NodeTypes.AssignmentPattern; + left: Pattern; +} + +// doc: https://github.com/estree/estree/blob/master/es5.md#literal +interface LiteralNode extends Node { + type: NodeTypes.Literal; + value: string | boolean | null | number | RegExp; +} + +type NodeList = { + references: Set; + functionalParams: Set; + variableDeclarations: Set; + identifierList: Array; +}; + +// https://github.com/estree/estree/blob/master/es5.md#property +export interface PropertyNode extends Node { + type: NodeTypes.Property; + key: LiteralNode | IdentifierNode; + value: Node; + kind: "init" | "get" | "set"; +} + +// Node with location details +type NodeWithLocation = NodeType & { + loc: SourceLocation; +}; + +type AstOptions = Omit; + +type EntityRefactorResponse = { + isSuccess: boolean; + body: { script: string; refactorCount: number } | { error: string }; +}; + +/* We need these functions to typescript casts the nodes with the correct types */ +export const isIdentifierNode = (node: Node): node is IdentifierNode => { + return node.type === NodeTypes.Identifier; +}; + +const isMemberExpressionNode = (node: Node): node is MemberExpressionNode => { + return node.type === NodeTypes.MemberExpression; +}; + +export const isVariableDeclarator = ( + node: Node +): node is VariableDeclaratorNode => { + return node.type === NodeTypes.VariableDeclarator; +}; + +const isFunctionDeclaration = (node: Node): node is FunctionDeclarationNode => { + return node.type === NodeTypes.FunctionDeclaration; +}; + +const isFunctionExpression = (node: Node): node is FunctionExpressionNode => { + return node.type === NodeTypes.FunctionExpression; +}; +const isArrowFunctionExpression = ( + node: Node +): node is ArrowFunctionExpressionNode => { + return node.type === NodeTypes.ArrowFunctionExpression; +}; + +export const isObjectExpression = (node: Node): node is ObjectExpression => { + return node.type === NodeTypes.ObjectExpression; +}; + +const isAssignmentPatternNode = (node: Node): node is AssignmentPatternNode => { + return node.type === NodeTypes.AssignmentPattern; +}; + +export const isLiteralNode = (node: Node): node is LiteralNode => { + return node.type === NodeTypes.Literal; +}; + +export const isPropertyNode = (node: Node): node is PropertyNode => { + return node.type === NodeTypes.Property; +}; + +export const isPropertyAFunctionNode = ( + node: Node +): node is ArrowFunctionExpressionNode | FunctionExpressionNode => { + return ( + node.type === NodeTypes.ArrowFunctionExpression || + node.type === NodeTypes.FunctionExpression + ); +}; + +const isArrayAccessorNode = (node: Node): node is MemberExpressionNode => { + return ( + isMemberExpressionNode(node) && + node.computed && + isLiteralNode(node.property) && + isFinite(node.property.value) + ); +}; + +const wrapCode = (code: string) => { + return ` + (function() { + return ${code} + }) + `; +}; + +const getFunctionalParamNamesFromNode = ( + node: + | FunctionDeclarationNode + | FunctionExpressionNode + | ArrowFunctionExpressionNode +) => { + return Array.from(getFunctionalParamsFromNode(node)).map( + (functionalParam) => functionalParam.paramName + ); +}; + +// Memoize the ast generation code to improve performance. +// Since this will be used by both the server and the client, we want to prevent regeneration of ast +// for the the same code snippet +export const getAST = memoize((code: string, options?: AstOptions) => + parse(code, { ...options, ecmaVersion: ECMA_VERSION }) +); + +/** + * An AST based extractor that fetches all possible references in a given + * piece of code. We use this to get any references to the global entities in Appsmith + * and create dependencies on them. If the reference was updated, the given piece of code + * should run again. + * @param code: The piece of script where references need to be extracted from + */ + +export interface IdentifierInfo { + references: string[]; + functionalParams: string[]; + variables: string[]; +} +export const extractIdentifierInfoFromCode = ( + code: string, + evaluationVersion: number, + invalidIdentifiers?: Record +): IdentifierInfo => { + let ast: Node = { end: 0, start: 0, type: "" }; + try { + const sanitizedScript = sanitizeScript(code, evaluationVersion); + /* wrapCode - Wrapping code in a function, since all code/script get wrapped with a function during evaluation. + Some syntax won't be valid unless they're at the RHS of a statement. + Since we're assigning all code/script to RHS during evaluation, we do the same here. + So that during ast parse, those errors are neglected. + */ + /* e.g. IIFE without braces + function() { return 123; }() -> is invalid + let result = function() { return 123; }() -> is valid + */ + const wrappedCode = wrapCode(sanitizedScript); + ast = getAST(wrappedCode); + let { references, functionalParams, variableDeclarations }: NodeList = + ancestorWalk(ast); + const referencesArr = Array.from(references).filter((reference) => { + // To remove references derived from declared variables and function params, + // We extract the topLevelIdentifier Eg. Api1.name => Api1 + const topLevelIdentifier = toPath(reference)[0]; + return !( + functionalParams.has(topLevelIdentifier) || + variableDeclarations.has(topLevelIdentifier) || + has(invalidIdentifiers, topLevelIdentifier) + ); + }); + return { + references: referencesArr, + functionalParams: Array.from(functionalParams), + variables: Array.from(variableDeclarations), + }; + } catch (e) { + if (e instanceof SyntaxError) { + // Syntax error. Ignore and return empty list + return { + references: [], + functionalParams: [], + variables: [], + }; + } + throw e; + } +}; + +export const entityRefactorFromCode = ( + script: string, + oldName: string, + newName: string, + isJSObject: boolean, + evaluationVersion: number, + invalidIdentifiers?: Record +): EntityRefactorResponse => { + //Sanitizing leads to removal of special charater. + //Hence we are not sanatizing the script. Fix(#18492) + //If script is a JSObject then replace export default to decalartion. + if (isJSObject) script = jsObjectToCode(script); + let ast: Node = { end: 0, start: 0, type: "" }; + //Copy of script to refactor + let refactorScript = script; + //Difference in length of oldName and newName + const nameLengthDiff: number = newName.length - oldName.length; + //Offset index used for deciding location of oldName. + let refactorOffset: number = 0; + //Count of refactors on the script + let refactorCount: number = 0; + try { + ast = getAST(script); + let { + references, + functionalParams, + variableDeclarations, + identifierList, + }: NodeList = ancestorWalk(ast); + const identifierArray = Array.from( + identifierList + ) as Array; + //To handle if oldName has property ("JSObject.myfunc") + const oldNameArr = oldName.split("."); + const referencesArr = Array.from(references).filter((reference) => { + // To remove references derived from declared variables and function params, + // We extract the topLevelIdentifier Eg. Api1.name => Api1 + const topLevelIdentifier = toPath(reference)[0]; + return !( + functionalParams.has(topLevelIdentifier) || + variableDeclarations.has(topLevelIdentifier) || + has(invalidIdentifiers, topLevelIdentifier) + ); + }); + //Traverse through all identifiers in the script + identifierArray.forEach((identifier) => { + if (identifier.name === oldNameArr[0]) { + let index = 0; + while (index < referencesArr.length) { + if (identifier.name === referencesArr[index].split(".")[0]) { + //Replace the oldName by newName + //Get start index from node and get subarray from index 0 till start + //Append above with new name + //Append substring from end index from the node till end of string + //Offset variable is used to alter the position based on `refactorOffset` + //In case of nested JS action get end postion fro the property. + ///Default end index + let endIndex = identifier.end; + const propertyNode = identifier.property; + //Flag variable : true if property should be updated + //false if property should not be updated + let propertyCondFlag = + oldNameArr.length > 1 && + propertyNode && + oldNameArr[1] === propertyNode.name; + //Condition to validate if Identifier || Property should be updated?? + if (oldNameArr.length === 1 || propertyCondFlag) { + //Condition to extend end index in case of property match + if (propertyCondFlag && propertyNode) { + endIndex = propertyNode.end; + } + refactorScript = + refactorScript.substring(0, identifier.start + refactorOffset) + + newName + + refactorScript.substring(endIndex + refactorOffset); + refactorOffset += nameLengthDiff; + ++refactorCount; + //We are only looking for one match in refrence for the identifier name. + break; + } + } + index++; + } + } + }); + //If script is a JSObject then revert decalartion to export default. + if (isJSObject) refactorScript = jsCodeToObject(refactorScript); + return { + isSuccess: true, + body: { script: refactorScript, refactorCount }, + }; + } catch (e) { + if (e instanceof SyntaxError) { + // Syntax error. Ignore and return empty list + return { isSuccess: false, body: { error: "Syntax Error" } }; + } + throw e; + } +}; + +export type functionParam = { paramName: string; defaultValue: unknown }; + +export const getFunctionalParamsFromNode = ( + node: + | FunctionDeclarationNode + | FunctionExpressionNode + | ArrowFunctionExpressionNode, + needValue = false +): Set => { + const functionalParams = new Set(); + node.params.forEach((paramNode) => { + if (isIdentifierNode(paramNode)) { + functionalParams.add({ + paramName: paramNode.name, + defaultValue: undefined, + }); + } else if (isAssignmentPatternNode(paramNode)) { + if (isIdentifierNode(paramNode.left)) { + const paramName = paramNode.left.name; + if (!needValue) { + functionalParams.add({ paramName, defaultValue: undefined }); + } else { + // figure out how to get value of paramNode.right for each node type + // currently we don't use params value, hence skipping it + // functionalParams.add({ + // defaultValue: paramNode.right.value, + // }); + } + } + } + }); + return functionalParams; +}; + +const constructFinalMemberExpIdentifier = ( + node: MemberExpressionNode, + child = "" +): string => { + const propertyAccessor = getPropertyAccessor(node.property); + if (isIdentifierNode(node.object)) { + return `${node.object.name}${propertyAccessor}${child}`; + } else { + const propertyAccessor = getPropertyAccessor(node.property); + const nestedChild = `${propertyAccessor}${child}`; + return constructFinalMemberExpIdentifier(node.object, nestedChild); + } +}; + +const getPropertyAccessor = (propertyNode: IdentifierNode | LiteralNode) => { + if (isIdentifierNode(propertyNode)) { + return `.${propertyNode.name}`; + } else if (isLiteralNode(propertyNode) && isString(propertyNode.value)) { + // is string literal search a['b'] + return `.${propertyNode.value}`; + } else if (isLiteralNode(propertyNode) && isFinite(propertyNode.value)) { + // is array index search - a[9] + return `[${propertyNode.value}]`; + } +}; + +export const isTypeOfFunction = (type: string) => { + return ( + type === NodeTypes.ArrowFunctionExpression || + type === NodeTypes.FunctionExpression + ); +}; + +export interface MemberExpressionData { + property: NodeWithLocation; + object: NodeWithLocation; +} + +/** Function returns Invalid top-level member expressions from code + * @param code + * @param data + * @param evaluationVersion + * @returns information about all invalid property/method assessment in code + * @example Given data { + * JSObject1: { + * name:"JSObject", + * data:[] + * }, + * Api1:{ + * name: "Api1", + * data: [] + * } + * }, + * For code {{Api1.name + JSObject.unknownProperty}}, function returns information about "JSObject.unknownProperty" node. + */ +export const extractInvalidTopLevelMemberExpressionsFromCode = ( + code: string, + data: Record, + evaluationVersion: number +): MemberExpressionData[] => { + const invalidTopLevelMemberExpressions = new Set(); + const variableDeclarations = new Set(); + let functionalParams = new Set(); + let ast: Node = { end: 0, start: 0, type: "" }; + try { + const sanitizedScript = sanitizeScript(code, evaluationVersion); + const wrappedCode = wrapCode(sanitizedScript); + ast = getAST(wrappedCode, { locations: true }); + } catch (e) { + if (e instanceof SyntaxError) { + // Syntax error. Ignore and return empty list + return []; + } + throw e; + } + simple(ast, { + MemberExpression(node: Node) { + const { object, property } = node as MemberExpressionNode; + // We are only interested in top-level MemberExpression nodes + // Eg. for Api1.data.name, we are only interested in Api1.data + if (!isIdentifierNode(object)) return; + if (!(object.name in data) || !isTrueObject(data[object.name])) return; + // For computed member expressions (assessed via [], eg. JSObject1["name"] ), + // We are only interested in strings + if ( + isLiteralNode(property) && + isString(property.value) && + !(property.value in data[object.name]) + ) { + invalidTopLevelMemberExpressions.add({ + object, + property, + } as MemberExpressionData); + } + if (isIdentifierNode(property) && !(property.name in data[object.name])) { + invalidTopLevelMemberExpressions.add({ + object, + property, + } as MemberExpressionData); + } + }, + VariableDeclarator(node: Node) { + if (isVariableDeclarator(node)) { + variableDeclarations.add(node.id.name); + } + }, + FunctionDeclaration(node: Node) { + if (!isFunctionDeclaration(node)) return; + functionalParams = new Set([ + ...functionalParams, + ...getFunctionalParamNamesFromNode(node), + ]); + }, + FunctionExpression(node: Node) { + if (!isFunctionExpression(node)) return; + functionalParams = new Set([ + ...functionalParams, + ...getFunctionalParamNamesFromNode(node), + ]); + }, + ArrowFunctionExpression(node: Node) { + if (!isArrowFunctionExpression(node)) return; + functionalParams = new Set([ + ...functionalParams, + ...getFunctionalParamNamesFromNode(node), + ]); + }, + }); + + const invalidTopLevelMemberExpressionsArray = Array.from( + invalidTopLevelMemberExpressions + ).filter((MemberExpression) => { + return !( + variableDeclarations.has(MemberExpression.object.name) || + functionalParams.has(MemberExpression.object.name) + ); + }); + + return invalidTopLevelMemberExpressionsArray; +}; + +const ancestorWalk = (ast: Node): NodeList => { + //List of all Identifier nodes with their property(if exists). + const identifierList = new Array(); + // List of all references found + const references = new Set(); + // List of variables declared within the script. All identifiers and member expressions derived from declared variables will be removed + const variableDeclarations = new Set(); + // List of functional params declared within the script. All identifiers and member expressions derived from functional params will be removed + let functionalParams = new Set(); + + /* + * We do an ancestor walk on the AST in order to extract all references. For example, for member expressions and identifiers, we need to know + * what surrounds the identifier (its parent and ancestors), ancestor walk will give that information in the callback + * doc: https://github.com/acornjs/acorn/tree/master/acorn-walk + */ + ancestor(ast, { + Identifier(node: Node, ancestors: Node[]) { + /* + * We are interested in identifiers. Due to the nature of AST, Identifier nodes can + * also be nested inside MemberExpressions. For deeply nested object references, there + * could be nesting of many MemberExpressions. To find the final reference, we will + * try to find the top level MemberExpression that does not have a MemberExpression parent. + * */ + let candidateTopLevelNode: IdentifierNode | MemberExpressionNode = + node as IdentifierNode; + let depth = ancestors.length - 2; // start "depth" with first parent + while (depth > 0) { + const parent = ancestors[depth]; + if ( + isMemberExpressionNode(parent) && + /* Member expressions that are "computed" (with [ ] search) + and the ones that have optional chaining ( a.b?.c ) + will be considered top level node. + We will stop looking for further parents */ + /* "computed" exception - isArrayAccessorNode + Member expressions that are array accessors with static index - [9] + will not be considered top level. + We will continue looking further. */ + (!parent.computed || isArrayAccessorNode(parent)) && + !parent.optional + ) { + candidateTopLevelNode = parent; + depth = depth - 1; + } else { + // Top level found + break; + } + } + //If parent is a Member expression then attach property to the Node. + //else push Identifier Node. + const parentNode = ancestors[ancestors.length - 2]; + if (isMemberExpressionNode(parentNode)) { + identifierList.push({ + ...(node as IdentifierNode), + property: parentNode.property as IdentifierNode, + }); + } else identifierList.push(node as RefactorIdentifierNode); + if (isIdentifierNode(candidateTopLevelNode)) { + // If the node is an Identifier, just save that + references.add(candidateTopLevelNode.name); + } else { + // For MemberExpression Nodes, we will construct a final reference string and then add + // it to the references list + const memberExpIdentifier = constructFinalMemberExpIdentifier( + candidateTopLevelNode + ); + references.add(memberExpIdentifier); + } + }, + VariableDeclarator(node: Node) { + // keep a track of declared variables so they can be + // removed from the final list of references + if (isVariableDeclarator(node)) { + variableDeclarations.add(node.id.name); + } + }, + FunctionDeclaration(node: Node) { + // params in function declarations are also counted as references so we keep + // track of them and remove them from the final list of references + if (!isFunctionDeclaration(node)) return; + functionalParams = new Set([ + ...functionalParams, + ...getFunctionalParamNamesFromNode(node), + ]); + }, + FunctionExpression(node: Node) { + // params in function expressions are also counted as references so we keep + // track of them and remove them from the final list of references + if (!isFunctionExpression(node)) return; + functionalParams = new Set([ + ...functionalParams, + ...getFunctionalParamNamesFromNode(node), + ]); + }, + ArrowFunctionExpression(node: Node) { + // params in arrow function expressions are also counted as references so we keep + // track of them and remove them from the final list of references + if (!isArrowFunctionExpression(node)) return; + functionalParams = new Set([ + ...functionalParams, + ...getFunctionalParamNamesFromNode(node), + ]); + }, + }); + return { + references, + functionalParams, + variableDeclarations, + identifierList, + }; +}; + +//Replace export default by a variable declaration. +//This is required for acorn to parse code into AST. +const jsObjectToCode = (script: string) => { + return script.replace(/export default/g, jsObjectDeclaration); +}; + +//Revert the string replacement from 'jsObjectToCode'. +//variable declaration is replaced back by export default. +const jsCodeToObject = (script: string) => { + return script.replace(jsObjectDeclaration, "export default"); +}; diff --git a/auxiliaries/ast/src/jsObject/index.ts b/auxiliaries/ast/src/jsObject/index.ts new file mode 100644 index 0000000..bb3c962 --- /dev/null +++ b/auxiliaries/ast/src/jsObject/index.ts @@ -0,0 +1,78 @@ +import { Node } from "acorn"; +import { getAST } from "../index"; +import { generate } from "astring"; +import { simple } from "acorn-walk"; +import { + getFunctionalParamsFromNode, + isPropertyAFunctionNode, + isVariableDeclarator, + isObjectExpression, + PropertyNode, + functionParam, +} from "../index"; + +export type JsObjectProperty = { + key: string; + value: string; + type: string; + arguments?: Array; +}; + +const jsObjectVariableName = + "____INTERNAL_JS_OBJECT_NAME_USED_FOR_PARSING_____"; + +export const jsObjectDeclaration = `var ${jsObjectVariableName} =`; + +export const parseJSObjectWithAST = ( + jsObjectBody: string +): Array => { + /* + jsObjectVariableName value is added such actual js code would never name same variable name. + if the variable name will be same then also we won't have problem here as jsObjectVariableName will be last node in VariableDeclarator hence overriding the previous JSObjectProperties. + Keeping this just for sanity check if any caveat was missed. + */ + const jsCode = `${jsObjectDeclaration} ${jsObjectBody}`; + + const ast = getAST(jsCode); + + const parsedObjectProperties = new Set(); + let JSObjectProperties: Array = []; + + simple(ast, { + VariableDeclarator(node: Node) { + if ( + isVariableDeclarator(node) && + node.id.name === jsObjectVariableName && + node.init && + isObjectExpression(node.init) + ) { + JSObjectProperties = node.init.properties; + } + }, + }); + + JSObjectProperties.forEach((node) => { + let params = new Set(); + const propertyNode = node; + let property: JsObjectProperty = { + key: generate(propertyNode.key), + value: generate(propertyNode.value), + type: propertyNode.value.type, + }; + + if (isPropertyAFunctionNode(propertyNode.value)) { + // if in future we need default values of each param, we could implement that in getFunctionalParamsFromNode + // currently we don't consume it anywhere hence avoiding to calculate that. + params = getFunctionalParamsFromNode(propertyNode.value); + property = { + ...property, + arguments: [...params], + }; + } + + // here we use `generate` function to convert our AST Node to JSCode + parsedObjectProperties.add(property); + }); + + return [...parsedObjectProperties]; +}; diff --git a/auxiliaries/ast/src/typings/unescape-js/index.d.ts b/auxiliaries/ast/src/typings/unescape-js/index.d.ts new file mode 100644 index 0000000..cfd13f9 --- /dev/null +++ b/auxiliaries/ast/src/typings/unescape-js/index.d.ts @@ -0,0 +1 @@ +declare module "unescape-js"; diff --git a/auxiliaries/ast/src/utils.ts b/auxiliaries/ast/src/utils.ts new file mode 100644 index 0000000..c3da778 --- /dev/null +++ b/auxiliaries/ast/src/utils.ts @@ -0,0 +1,21 @@ +import unescapeJS from 'unescape-js'; + +const beginsWithLineBreakRegex = /^\s+|\s+$/; + +export function sanitizeScript(js: string, evaluationVersion: number) { + // We remove any line breaks from the beginning of the script because that + // makes the final function invalid. We also unescape any escaped characters + // so that eval can happen + //default value of evalutaion version is 2 + evaluationVersion = evaluationVersion ? evaluationVersion : 2; + const trimmedJS = js.replace(beginsWithLineBreakRegex, ''); + return evaluationVersion > 1 ? trimmedJS : unescapeJS(trimmedJS); +} + +// For the times when you need to know if something truly an object like { a: 1, b: 2} +// typeof, lodash.isObject and others will return false positives for things like array, null, etc +export const isTrueObject = ( + item: unknown +): item is Record => { + return Object.prototype.toString.call(item) === '[object Object]'; +}; diff --git a/auxiliaries/ast/tsconfig.json b/auxiliaries/ast/tsconfig.json new file mode 100644 index 0000000..5c0f356 --- /dev/null +++ b/auxiliaries/ast/tsconfig.json @@ -0,0 +1,37 @@ +{ + "compilerOptions": { + "target": "ES6", + "lib": [ + "DOM", + "ES6", + "DOM.Iterable", + "ScriptHost", + "ES2016.Array.Include", + "es2020.string", + "esnext" + ], + "strict": true, + "declaration": true, + "declarationDir": "build", + "allowJs": true, + "skipLibCheck": true, + "esModuleInterop": true, + "allowSyntheticDefaultImports": true, + "forceConsistentCasingInFileNames": true, + "module": "esnext", + "moduleResolution": "node", + "resolveJsonModule": true, + "isolatedModules": true, + "noEmit": true, + "jsx": "react", + "downlevelIteration": true, + "experimentalDecorators": true, + "importHelpers": true, + "typeRoots": ["./typings", "./node_modules/@types"], + "sourceMap": true, + "baseUrl": "./src", + "noFallthroughCasesInSwitch": true + }, + "include": ["./src/**/*", "index.d.ts", "index.ts"], + "exclude": ["node_modules", "build"] +} diff --git a/auxiliaries/code-editor/package.json b/auxiliaries/code-editor/package.json index 5f8005e..a83e347 100644 --- a/auxiliaries/code-editor/package.json +++ b/auxiliaries/code-editor/package.json @@ -56,7 +56,11 @@ }, "devDependencies": { "@types/codemirror": "^5.60.5", + "@types/deep-diff": "^1.0.2", + "@types/node-forge": "^1.3.1", "@types/tern": "^0.23.4", + "@types/toposort": "^2.0.3", + "@types/unescape-js": "^1.0.0", "@umijs/lint": "^4.0.40", "dumi": "^2.0.16", "father": "^4.1.0", @@ -67,9 +71,21 @@ "webpack": "^5.75.0" }, "dependencies": { + "@modou/ast": "workspace:^1.0.0", + "@sentry/react": "^7.28.1", "codemirror": "^5.65.10", + "deep-diff": "^1.0.2", "fast-deep-equal": "^3.1.3", + "fast-xml-parser": "^4.0.12", + "klona": "^2.0.5", "loglevel": "^1.8.1", - "tern": "^0.24.3" + "moment": "^2.29.4", + "moment-timezone": "^0.5.40", + "node-forge": "^1.3.1", + "react-toastify": "^9.1.1", + "tern": "^0.24.3", + "toposort": "^2.0.2", + "unescape-js": "^1.1.4", + "yjs": "^13.5.43" } } diff --git a/auxiliaries/code-editor/shim.d.ts b/auxiliaries/code-editor/shim.d.ts new file mode 100644 index 0000000..5f75889 --- /dev/null +++ b/auxiliaries/code-editor/shim.d.ts @@ -0,0 +1,15 @@ +import { ActionDescription } from '@modou/code-editor/CodeEditor/entities/DataTree/actionTriggers' + +declare global { + /** All identifiers added to the worker global scope should also + * be included in the DEDICATED_WORKER_GLOBAL_SCOPE_IDENTIFIERS in + * app/client/src/constants/WidgetValidation.ts + * */ + + interface Window { + ALLOW_ASYNC?: boolean + IS_ASYNC?: boolean + TRIGGER_COLLECTOR: ActionDescription[] + evaluationVersion: number + } +} diff --git a/auxiliaries/code-editor/src/CodeEditor/api/APIResponses.ts b/auxiliaries/code-editor/src/CodeEditor/api/APIResponses.ts new file mode 100644 index 0000000..b785afa --- /dev/null +++ b/auxiliaries/code-editor/src/CodeEditor/api/APIResponses.ts @@ -0,0 +1,27 @@ +export interface APIResponseError { + code: number + message: string +} + +export interface ResponseMeta { + status: number + success: boolean + error?: APIResponseError +} + +export type ApiResponse = { + responseMeta: ResponseMeta + data: T + code?: string +} + +// NO_DATASOURCES_FOUND, 1000, "Unable to find {0} with id {1}" +// INVALID_PARAMTER, 4000, "Invalid parameter {0} provided in the input" +// PLUGIN_NOT_INSTALLED, 4001, "Plugin {0} not installed" +// MISSING_PLUGIN_ID, 4002, "Missing plugin id. Please input correct plugin id" +// MISSING_DATASOURCES_ID, 4003, "Missing datasource id. Please input correct datasource id" +// MISSING_PAGE_ID, 4004, "Missing page id. Pleaes input correct page id" +// PAGE_DOES_NOT_EXIST_IN_WORKSPACE, 4006, "Page {0} does not belong to the current user {1} workspace." +// UNAUTHORIZED_DOMAIN, 4001, "Invalid email domain provided. Please sign in with a valid work email ID" +// INTERNAL_SERVER_ERROR, 5000, "Internal server error while processing request" +// REPOSITORY_SAVE_FAILED, 5001, "Repository save failed." diff --git a/auxiliaries/code-editor/src/CodeEditor/api/ActionAPI.tsx b/auxiliaries/code-editor/src/CodeEditor/api/ActionAPI.tsx new file mode 100644 index 0000000..aab3a6a --- /dev/null +++ b/auxiliaries/code-editor/src/CodeEditor/api/ActionAPI.tsx @@ -0,0 +1,28 @@ +import { HttpMethod } from '@modou/code-editor/CodeEditor/api/Api' +import { WidgetType } from '@modou/code-editor/CodeEditor/constants/WidgetConstants' + +export interface SuggestedWidget { + type: WidgetType + bindingQuery: string +} +export interface ActionApiResponseReq { + headers: Record + body: Record | null + httpMethod: HttpMethod | '' + url: string +} +export interface ActionResponse { + body: unknown + headers: Record + request?: ActionApiResponseReq + statusCode: string + dataTypes: Array> + duration: string + size: string + isExecutionSuccess?: boolean + suggestedWidgets?: SuggestedWidget[] + messages?: string[] + errorType?: string + readableError?: string + responseDisplayFormat?: string +} diff --git a/auxiliaries/code-editor/src/CodeEditor/api/Api.ts b/auxiliaries/code-editor/src/CodeEditor/api/Api.ts new file mode 100644 index 0000000..aff8492 --- /dev/null +++ b/auxiliaries/code-editor/src/CodeEditor/api/Api.ts @@ -0,0 +1 @@ +export type HttpMethod = 'GET' | 'POST' | 'PUT' | 'DELETE' | 'PATCH' diff --git a/auxiliaries/code-editor/src/CodeEditor/api/PluginApi.ts b/auxiliaries/code-editor/src/CodeEditor/api/PluginApi.ts new file mode 100644 index 0000000..e7dae92 --- /dev/null +++ b/auxiliaries/code-editor/src/CodeEditor/api/PluginApi.ts @@ -0,0 +1,50 @@ +import { + PluginPackageName, + PluginType, +} from '@modou/code-editor/CodeEditor/entities/Action' +import { DependencyMap } from '@modou/code-editor/CodeEditor/utils/DynamicBindingUtils' + +export type PluginId = string +export enum UIComponentTypes { + DbEditorForm = 'DbEditorForm', + UQIDbEditorForm = 'UQIDbEditorForm', + ApiEditorForm = 'ApiEditorForm', + RapidApiEditorForm = 'RapidApiEditorForm', + JsEditorForm = 'JsEditorForm', +} + +export enum DatasourceComponentTypes { + RestAPIDatasourceForm = 'RestAPIDatasourceForm', + AutoForm = 'AutoForm', +} + +export interface Plugin { + id: string + name: string + type: PluginType + packageName: PluginPackageName + iconLocation?: string + uiComponent: UIComponentTypes + datasourceComponent: DatasourceComponentTypes + allowUserDatasources?: boolean + templates: Record + responseType?: 'TABLE' | 'JSON' + documentationLink?: string + generateCRUDPageComponent?: string +} + +export interface PluginFormPayload { + form: any[] + editor: any[] + setting: any[] + dependencies: DependencyMap + formButton: string[] +} + +export interface DefaultPlugin { + id: string + name: string + packageName: string + iconLocation?: string + allowUserDatasources?: boolean +} diff --git a/auxiliaries/code-editor/src/CodeEditor/autocomplete/CodeMirrorTernService.ts b/auxiliaries/code-editor/src/CodeEditor/autocomplete/CodeMirrorTernService.ts index 5425474..ac9f2f5 100644 --- a/auxiliaries/code-editor/src/CodeEditor/autocomplete/CodeMirrorTernService.ts +++ b/auxiliaries/code-editor/src/CodeEditor/autocomplete/CodeMirrorTernService.ts @@ -4,10 +4,8 @@ import { Def, Document } from 'tern' import { AutocompleteSorter } from '@modou/code-editor/CodeEditor/autocomplete/AutocompleteSortRules' import { TernWorkerServer } from '@modou/code-editor/CodeEditor/autocomplete/TernWorkerServer' import { getCompletionsForKeyword } from '@modou/code-editor/CodeEditor/autocomplete/keywordCompletion' -import { - ENTITY_TYPE, - FieldEntityInformation, -} from '@modou/code-editor/CodeEditor/common/editor-config' +import { FieldEntityInformation } from '@modou/code-editor/CodeEditor/common/editor-config' +import { ENTITY_TYPE } from '@modou/code-editor/CodeEditor/entities/DataTree/types' import { DEFS } from '@modou/code-editor/CodeEditor/tern/defs' import { getDynamicStringSegments, @@ -117,7 +115,7 @@ class CodeMirrorTernService { >() resetServer() { - // this.server = new tern.Server({ + // this.server = new Tern.Server({ // async: true, // defs: DEFS, // }) @@ -472,7 +470,7 @@ class CodeMirrorTernService { } } else { query.file = doc.name - // this code is different from tern.js code + // this code is different from Tern.js code // we noticed error `TernError: file doesn't contain line x` // which was due to file not being present for the case when a codeEditor is opened and 1st character is typed files.push({ @@ -529,7 +527,7 @@ class CodeMirrorTernService { if (doc.lineCount() > bigDoc && changed.to - changed.from > 100) setTimeout(() => { if (data.changed && data.changed.to - data.changed.from > 100) - this.sendDoc(data) + void this.sendDoc(data) }, 200) } diff --git a/auxiliaries/code-editor/src/CodeEditor/autocomplete/TernWorkerServer.ts b/auxiliaries/code-editor/src/CodeEditor/autocomplete/TernWorkerServer.ts index ffba208..b328413 100644 --- a/auxiliaries/code-editor/src/CodeEditor/autocomplete/TernWorkerServer.ts +++ b/auxiliaries/code-editor/src/CodeEditor/autocomplete/TernWorkerServer.ts @@ -6,7 +6,7 @@ import { } from '@modou/code-editor/CodeEditor/autocomplete/types' const ternWorker = new Worker( - new URL('../works/tern/tern.worker.ts', import.meta.url), + new URL('../works/Tern/tern.worker.ts', import.meta.url), { name: 'tern.worker', type: 'module', diff --git a/auxiliaries/code-editor/src/CodeEditor/common/data-tree.ts b/auxiliaries/code-editor/src/CodeEditor/common/data-tree.ts deleted file mode 100644 index db556ec..0000000 --- a/auxiliaries/code-editor/src/CodeEditor/common/data-tree.ts +++ /dev/null @@ -1,33 +0,0 @@ -import { WidgetBaseProps } from '@modou/core' - -import { ENTITY_TYPE } from './editor-config' - -interface Page { - pageName: string - pageId: string - isDefault: boolean - latest?: boolean - isHidden?: boolean - slug: string - customSlug?: string - userPermissions?: string[] -} - -interface DataTreeAppsmith { - ENTITY_TYPE: ENTITY_TYPE.APPSMITH - user: { - name: string - age: number - } -} - -export interface WidgetEvalTree extends WidgetBaseProps { - meta: Record - ENTITY_TYPE: ENTITY_TYPE.WIDGET -} - -interface DataTreeWidget extends WidgetEvalTree {} - -export type DataTreeObjectEntity = DataTreeWidget | DataTreeAppsmith - -export type DataTreeEntity = DataTreeObjectEntity | Page[] diff --git a/auxiliaries/code-editor/src/CodeEditor/common/editor-config.ts b/auxiliaries/code-editor/src/CodeEditor/common/editor-config.ts index 9e00e5c..308de5f 100644 --- a/auxiliaries/code-editor/src/CodeEditor/common/editor-config.ts +++ b/auxiliaries/code-editor/src/CodeEditor/common/editor-config.ts @@ -1,7 +1,8 @@ import CodeMirror from 'codemirror' import { AutocompleteDataType } from '@modou/code-editor/CodeEditor/autocomplete/CodeMirrorTernService' -import { DataTreeEntity } from '@modou/code-editor/CodeEditor/common/data-tree' +import { DataTreeEntity } from '@modou/code-editor/CodeEditor/entities/DataTree/dataTreeFactory' +import { ENTITY_TYPE } from '@modou/code-editor/CodeEditor/entities/DataTree/types' import { TruthyPrimitiveTypes } from '@modou/code-editor/CodeEditor/utils/TypeHelpers' export enum CodeEditorModeEnum { @@ -58,13 +59,6 @@ export enum AutocompleteCloseKeyEnum { export type MarkHelper = (editor: CodeMirror.Editor) => void -export enum ENTITY_TYPE { - ACTION = 'ACTION', - WIDGET = 'WIDGET', - APPSMITH = 'APPSMITH', - JSACTION = 'JSACTION', -} - export interface FieldEntityInformation { entityName?: string expectedType?: AutocompleteDataType diff --git a/auxiliaries/code-editor/src/CodeEditor/common/hintHelpers.ts b/auxiliaries/code-editor/src/CodeEditor/common/hintHelpers.ts index f04bd4d..ef68a17 100644 --- a/auxiliaries/code-editor/src/CodeEditor/common/hintHelpers.ts +++ b/auxiliaries/code-editor/src/CodeEditor/common/hintHelpers.ts @@ -2,11 +2,9 @@ import CodeMirror from 'codemirror' import { CodeMirrorTernServiceInstance } from '@modou/code-editor/CodeEditor/autocomplete/CodeMirrorTernService' import { checkIfCursorInsideBinding } from '@modou/code-editor/CodeEditor/common/codeEditorUtils' -import { - ENTITY_TYPE, - HintHelper, -} from '@modou/code-editor/CodeEditor/common/editor-config' +import { HintHelper } from '@modou/code-editor/CodeEditor/common/editor-config' import { KeyboardShortcuts } from '@modou/code-editor/CodeEditor/constants/KeyboardShortcuts' +import { ENTITY_TYPE } from '@modou/code-editor/CodeEditor/entities/DataTree/types' export const bindingHint: HintHelper = (editor) => { editor.setOption('extraKeys', { diff --git a/auxiliaries/code-editor/src/CodeEditor/components/editorComponents/ActionCreator/constants.ts b/auxiliaries/code-editor/src/CodeEditor/components/editorComponents/ActionCreator/constants.ts new file mode 100644 index 0000000..448af38 --- /dev/null +++ b/auxiliaries/code-editor/src/CodeEditor/components/editorComponents/ActionCreator/constants.ts @@ -0,0 +1,15 @@ +export const APPSMITH_GLOBAL_FUNCTIONS = { + navigateTo: 'navigateTo', + showAlert: 'showAlert', + showModal: 'showModal', + closeModal: 'closeModal', + storeValue: 'storeValue', + removeValue: 'removeValue', + clearStore: 'clearStore', + download: 'download', + copyToClipboard: 'copyToClipboard', + resetWidget: 'resetWidget', + setInterval: 'setInterval', + clearInterval: 'clearInterval', + postMessage: 'postWindowMessage', +} diff --git a/auxiliaries/code-editor/src/CodeEditor/components/formControls/BaseControl.tsx b/auxiliaries/code-editor/src/CodeEditor/components/formControls/BaseControl.tsx new file mode 100644 index 0000000..714fafe --- /dev/null +++ b/auxiliaries/code-editor/src/CodeEditor/components/formControls/BaseControl.tsx @@ -0,0 +1,51 @@ +import { ViewTypes } from './utils' + +export type FormConfigType = Omit & { + configProperty?: string + children?: FormConfigType[] + options?: DropdownOption[] + fetchOptionsConditionally?: boolean +} + +export interface ControlData { + id: string + label: string + alternateViewTypes?: ViewTypes[] + tooltipText?: string | Record + configProperty: string + controlType: ControlType + propertyValue?: any + isValid: boolean + validationMessage?: string + validationRegex?: string + dataType?: InputType + initialValue?: + | string + | boolean + | number + | Record + | Array + info?: string //helper text + isRequired?: boolean + conditionals?: ConditonalObject // Object that contains the conditionals config + hidden?: HiddenType + placeholderText?: string | Record + schema?: any + errorText?: string + showError?: boolean + encrypted?: boolean + subtitle?: string + showLineNumbers?: boolean + url?: string + urlText?: string + logicalTypes?: string[] + comparisonTypes?: string[] + nestedLevels?: number + customStyles?: any + propertyName?: string + identifier?: string + sectionName?: string + disabled?: boolean + staticDependencyPathList?: string[] + validator?: (value: string) => { isValid: boolean; message: string } +} diff --git a/auxiliaries/code-editor/src/CodeEditor/components/formControls/utils.ts b/auxiliaries/code-editor/src/CodeEditor/components/formControls/utils.ts new file mode 100644 index 0000000..980b32f --- /dev/null +++ b/auxiliaries/code-editor/src/CodeEditor/components/formControls/utils.ts @@ -0,0 +1,33 @@ +export enum ViewTypes { + JSON = 'json', + COMPONENT = 'component', +} +export const extractEvalConfigFromFormConfig = ( + formConfig: FormConfigType, + paths: string[], + parentPath = '', + bindingsFound: FormConfigEvalObject = {}, +) => { + paths.forEach((path: string) => { + if (!(path in formConfig)) return + const config = get(formConfig, path, '') + if (typeof config === 'string') { + bindingsFound = { + ...bindingsFound, + ...extractExpressionObject(config, path, parentPath), + } + } else if (typeof config === 'object') { + bindingsFound = { + ...bindingsFound, + ...extractEvalConfigFromFormConfig( + config, + Object.keys(config), + parentPath.length > 0 ? `${parentPath}.${path}` : path, + bindingsFound, + ), + } + } + }) + + return bindingsFound +} diff --git a/auxiliaries/code-editor/src/CodeEditor/components/propertyControls/BaseControl.tsx b/auxiliaries/code-editor/src/CodeEditor/components/propertyControls/BaseControl.tsx new file mode 100644 index 0000000..c9f815e --- /dev/null +++ b/auxiliaries/code-editor/src/CodeEditor/components/propertyControls/BaseControl.tsx @@ -0,0 +1,86 @@ +import _ from 'lodash' +import { Component } from 'react' + +export interface ControlProps extends ControlData, ControlFunctions { + key?: string + additionalAutoComplete?: Record> +} +export interface ControlData + extends Omit { + propertyValue?: any + defaultValue?: any + errorMessage?: string + expected?: CodeEditorExpected + evaluatedValue: any + widgetProperties: any + useValidationMessage?: boolean + parentPropertyName: string + parentPropertyValue: unknown + additionalDynamicData: Record> + label: string + additionalControlData?: Record +} +export interface ControlFunctions { + onPropertyChange?: ( + propertyName: string, + propertyValue: string, + isUpdatedViaKeyboard?: boolean, + ) => void + onBatchUpdateProperties?: (updates: Record) => void + openNextPanel: (props: any) => void + deleteProperties: (propertyPaths: string[]) => void + theme: EditorTheme + hideEvaluatedValue?: boolean +} +export class BaseControl

extends Component< + P, + S +> { + updateProperty( + propertyName: string, + propertyValue: any, + isUpdatedViaKeyboard?: boolean, + ) { + if ( + this.props.propertyValue === undefined && + propertyValue === this.props.defaultValue + ) { + return + } + if ( + !_.isNil(this.props.onPropertyChange) && + this.props.propertyValue !== propertyValue + ) { + this.props.onPropertyChange( + propertyName, + propertyValue, + isUpdatedViaKeyboard, + ) + } + } + deleteProperties(propertyPaths: string[]) { + if (this.props.deleteProperties) { + this.props.deleteProperties(propertyPaths) + } + } + batchUpdateProperties = (updates: Record) => { + if (this.props.onBatchUpdateProperties) { + this.props.onBatchUpdateProperties(updates) + } + } + static getControlType() { + return 'BASE_CONTROL' + } + + // Checks whether a particular value can be displayed UI from JS edit mode + // eslint-disable-next-line @typescript-eslint/no-unused-vars + static canDisplayValueInUI(config: ControlData, value: any): boolean { + return false + } + + // Only applicable for JSONFormComputeControl & ComputeTablePropertyControl + // eslint-disable-next-line @typescript-eslint/no-unused-vars + static getInputComputedValue(value: string, widgetName: string): string { + return '' + } +} diff --git a/auxiliaries/code-editor/src/CodeEditor/components/propertyControls/index.ts b/auxiliaries/code-editor/src/CodeEditor/components/propertyControls/index.ts new file mode 100644 index 0000000..cb62d69 --- /dev/null +++ b/auxiliaries/code-editor/src/CodeEditor/components/propertyControls/index.ts @@ -0,0 +1,12 @@ +// TODO:(LiuLei) 完善 PropertyControls +const PropertyControls = {} +export const getPropertyControlTypes = (): { [key: string]: string } => { + const _types: { [key: string]: string } = {} + Object.values(PropertyControls).forEach( + (Control: typeof BaseControl & { getControlType: () => string }) => { + const controlType = Control.getControlType() + _types[controlType] = controlType + }, + ) + return _types +} diff --git a/auxiliaries/code-editor/src/CodeEditor/constants/AppsmithActionConstants/ActionConstants.tsx b/auxiliaries/code-editor/src/CodeEditor/constants/AppsmithActionConstants/ActionConstants.tsx new file mode 100644 index 0000000..a1093ae --- /dev/null +++ b/auxiliaries/code-editor/src/CodeEditor/constants/AppsmithActionConstants/ActionConstants.tsx @@ -0,0 +1,87 @@ +export interface LayoutOnLoadActionErrors { + errorType: string + code: number + message: string +} + +export const EXECUTION_PARAM_KEY = 'executionParams' + +export enum EventType { + ON_RESET = 'ON_RESET', + ON_PAGE_LOAD = 'ON_PAGE_LOAD', + ON_PREV_PAGE = 'ON_PREV_PAGE', + ON_NEXT_PAGE = 'ON_NEXT_PAGE', + ON_PAGE_SIZE_CHANGE = 'ON_PAGE_SIZE_CHANGE', + ON_ERROR = 'ON_ERROR', + ON_SUCCESS = 'ON_SUCCESS', + ON_ROW_SELECTED = 'ON_ROW_SELECTED', + ON_SEARCH = 'ON_SEARCH', + ON_CLICK = 'ON_CLICK', + ON_DATA_POINT_CLICK = 'ON_DATA_POINT_CLICK', + ON_FILES_SELECTED = 'ON_FILES_SELECTED', + ON_HOVER = 'ON_HOVER', + ON_TOGGLE = 'ON_TOGGLE', + ON_LOAD = 'ON_LOAD', + ON_MODAL_CLOSE = 'ON_MODAL_CLOSE', + ON_TEXT_CHANGE = 'ON_TEXT_CHANGE', + ON_SUBMIT = 'ON_SUBMIT', + ON_CHECK_CHANGE = 'ON_CHECK_CHANGE', + ON_SWITCH_CHANGE = 'ON_SWITCH_CHANGE', + ON_SELECT = 'ON_SELECT', + ON_DATE_SELECTED = 'ON_DATE_SELECTED', + ON_DATE_RANGE_SELECTED = 'ON_DATE_RANGE_SELECTED', + ON_DROPDOWN_OPEN = 'ON_DROPDOWN_OPEN', + ON_DROPDOWN_CLOSE = 'ON_DROPDOWN_CLOSE', + ON_OPTION_CHANGE = 'ON_OPTION_CHANGE', + ON_FILTER_CHANGE = 'ON_FILTER_CHANGE', + ON_FILTER_UPDATE = 'ON_FILTER_UPDATE', + ON_MARKER_CLICK = 'ON_MARKER_CLICK', + ON_CREATE_MARKER = 'ON_CREATE_MARKER', + ON_TAB_CHANGE = 'ON_TAB_CHANGE', + ON_VIDEO_START = 'ON_VIDEO_START', + ON_VIDEO_END = 'ON_VIDEO_END', + ON_VIDEO_PLAY = 'ON_VIDEO_PLAY', + ON_VIDEO_PAUSE = 'ON_VIDEO_PAUSE', + ON_AUDIO_START = 'ON_AUDIO_START', + ON_AUDIO_END = 'ON_AUDIO_END', + ON_AUDIO_PLAY = 'ON_AUDIO_PLAY', + ON_AUDIO_PAUSE = 'ON_AUDIO_PAUSE', + ON_RATE_CHANGED = 'ON_RATE_CHANGED', + ON_IFRAME_URL_CHANGED = 'ON_IFRAME_URL_CHANGED', + ON_IFRAME_SRC_DOC_CHANGED = 'ON_IFRAME_SRC_DOC_CHANGED', + ON_IFRAME_MESSAGE_RECEIVED = 'ON_IFRAME_MESSAGE_RECEIVED', + ON_SNIPPET_EXECUTE = 'ON_SNIPPET_EXECUTE', + ON_SORT = 'ON_SORT', + ON_CHECKBOX_GROUP_SELECTION_CHANGE = 'ON_CHECKBOX_GROUP_SELECTION_CHANGE', + ON_LIST_PAGE_CHANGE = 'ON_LIST_PAGE_CHANGE', + ON_RECORDING_START = 'ON_RECORDING_START', + ON_RECORDING_COMPLETE = 'ON_RECORDING_COMPLETE', + ON_SWITCH_GROUP_SELECTION_CHANGE = 'ON_SWITCH_GROUP_SELECTION_CHANGE', + ON_JS_FUNCTION_EXECUTE = 'ON_JS_FUNCTION_EXECUTE', + ON_CAMERA_IMAGE_CAPTURE = 'ON_CAMERA_IMAGE_CAPTURE', + ON_CAMERA_IMAGE_SAVE = 'ON_CAMERA_IMAGE_SAVE', + ON_CAMERA_VIDEO_RECORDING_START = 'ON_CAMERA_VIDEO_RECORDING_START', + ON_CAMERA_VIDEO_RECORDING_STOP = 'ON_CAMERA_VIDEO_RECORDING_STOP', + ON_CAMERA_VIDEO_RECORDING_SAVE = 'ON_CAMERA_VIDEO_RECORDING_SAVE', + ON_ENTER_KEY_PRESS = 'ON_ENTER_KEY_PRESS', + ON_BLUR = 'ON_BLUR', + ON_FOCUS = 'ON_FOCUS', + ON_BULK_SAVE = 'ON_BULK_SAVE', + ON_BULK_DISCARD = 'ON_BULK_DISCARD', + ON_ROW_SAVE = 'ON_ROW_SAVE', + ON_ROW_DISCARD = 'ON_ROW_DISCARD', + ON_CODE_DETECTED = 'ON_CODE_DETECTED', + ON_ADD_NEW_ROW_SAVE = 'ON_ADD_NEW_ROW_SAVE', + ON_ADD_NEW_ROW_DISCARD = 'ON_ADD_NEW_ROW_DISCARD', +} + +export interface TriggerSource { + id: string + name: string + collectionId?: string + isJSAction?: boolean + actionId?: string +} + +export const THIS_DOT_PARAMS_KEY = 'params' +export const EXECUTION_PARAM_REFERENCE_REGEX = /this.params|this\?.params/g diff --git a/auxiliaries/code-editor/src/CodeEditor/constants/PropertyControlConstants.tsx b/auxiliaries/code-editor/src/CodeEditor/constants/PropertyControlConstants.tsx new file mode 100644 index 0000000..504888c --- /dev/null +++ b/auxiliaries/code-editor/src/CodeEditor/constants/PropertyControlConstants.tsx @@ -0,0 +1,102 @@ +import { CodeEditorExpected } from '@modou/code-editor/CodeEditor' +import { + ValidationResponse, + ValidationTypes, +} from '@modou/code-editor/CodeEditor/constants/WidgetValidation' + +const ControlTypes = getPropertyControlTypes() + +export type ControlType = typeof ControlTypes[keyof typeof ControlTypes] + +interface ValidationConfigParams { + min?: number // min allowed for a number + max?: number // max allowed for a number + natural?: boolean // is a positive integer + default?: unknown // default for any type + unique?: boolean | string[] // unique in an array (string if a particular path is unique) + required?: boolean // required type + // required is now used to check if value is an empty string. + requiredKey?: boolean // required key + regex?: RegExp // validator regex for text type + allowedKeys?: Array<{ + // Allowed keys in an object type + name: string + type: ValidationTypes + params?: ValidationConfigParams + }> + allowedValues?: unknown[] // Allowed values in a string and array type + children?: ValidationConfig // Children configurations in an ARRAY or OBJECT_ARRAY type + fn?: (value: unknown, props: any, _?: any, moment?: any) => ValidationResponse // Function in a FUNCTION type + fnString?: string // AUTO GENERATED, SHOULD NOT BE SET BY WIDGET DEVELOPER + expected?: CodeEditorExpected // FUNCTION type expected type and example + strict?: boolean // for strict string validation of TEXT type + ignoreCase?: boolean // to ignore the case of key + type?: ValidationTypes // Used for ValidationType.ARRAY_OF_TYPE_OR_TYPE to define sub type + params?: ValidationConfigParams // Used for ValidationType.ARRAY_OF_TYPE_OR_TYPE to define sub type params + passThroughOnZero?: boolean // Used for ValidationType.NUMBER to allow 0 to be passed through. Deafults value is true + limitLineBreaks?: boolean // Used for ValidationType.TEXT to limit line breaks in a large json object. +} + +export interface ValidationConfig { + type: ValidationTypes + params?: ValidationConfigParams + dependentPaths?: string[] +} +export interface ActionValidationConfigMap { + [configPropety: string]: ValidationConfig +} + +export interface PropertyPaneControlConfig { + id?: string + label: string + propertyName: string + // Serves in the tooltip + helpText?: string + // Dynamic text serves below the property pane inputs + helperText?: ((props: any) => string) | string + isJSConvertible?: boolean + customJSControl?: string + controlType: ControlType + validationMessage?: string + dataTreePath?: string + children?: PropertyPaneConfig[] + panelConfig?: PanelConfig + updateRelatedWidgetProperties?: ( + propertyName: string, + propertyValue: any, + props: any, + ) => UpdateWidgetPropertyPayload[] + updateHook?: ( + props: any, + propertyName: string, + propertyValue: any, + ) => Array | undefined + hidden?: (props: any, propertyPath: string) => boolean + invisible?: boolean + isBindProperty: boolean + isTriggerProperty: boolean + validation?: ValidationConfig + useValidationMessage?: boolean + additionalAutoComplete?: ( + props: any, + ) => Record> + evaluationSubstitutionType?: EvaluationSubstitutionType + dependencies?: string[] + evaluatedDependencies?: string[] // dependencies to be picked from the __evaluated__ object + expected?: CodeEditorExpected + getStylesheetValue?: ( + props: any, + propertyPath: string, + stylesheet?: Stylesheet, + ) => Stylesheet[string] + // TODO(abhinav): To fix this, rename the options property of the controls which use this + // Alternatively, create a new structure + options?: any + // The following should ideally be used internally + postUpdateAction?: ReduxActionType + onBlur?: () => void + onFocus?: () => void + isPanelProperty?: boolean + // Numeric Input Control + min?: number +} diff --git a/auxiliaries/code-editor/src/CodeEditor/constants/WidgetConstants.ts b/auxiliaries/code-editor/src/CodeEditor/constants/WidgetConstants.ts new file mode 100644 index 0000000..543dfac --- /dev/null +++ b/auxiliaries/code-editor/src/CodeEditor/constants/WidgetConstants.ts @@ -0,0 +1,7 @@ +// TODO 完善 ts type +export type WidgetType = string +export type RenderMode = + | 'COMPONENT_PANE' + | 'CANVAS' + | 'PAGE' + | 'CANVAS_SELECTED' diff --git a/auxiliaries/code-editor/src/CodeEditor/constants/WidgetValidation.ts b/auxiliaries/code-editor/src/CodeEditor/constants/WidgetValidation.ts new file mode 100644 index 0000000..123e481 --- /dev/null +++ b/auxiliaries/code-editor/src/CodeEditor/constants/WidgetValidation.ts @@ -0,0 +1,412 @@ +// Always add a validator function in ./worker/validation for these types +import { EXECUTION_PARAM_KEY } from '@modou/code-editor/CodeEditor/constants/AppsmithActionConstants/ActionConstants' +import { ValidationConfig } from '@modou/code-editor/CodeEditor/constants/PropertyControlConstants' + +export enum ValidationTypes { + TEXT = 'TEXT', + REGEX = 'REGEX', + NUMBER = 'NUMBER', + BOOLEAN = 'BOOLEAN', + OBJECT = 'OBJECT', + ARRAY = 'ARRAY', + OBJECT_ARRAY = 'OBJECT_ARRAY', + NESTED_OBJECT_ARRAY = 'NESTED_OBJECT_ARRAY', + DATE_ISO_STRING = 'DATE_ISO_STRING', + IMAGE_URL = 'IMAGE_URL', + FUNCTION = 'FUNCTION', + SAFE_URL = 'SAFE_URL', + ARRAY_OF_TYPE_OR_TYPE = 'ARRAY_OF_TYPE_OR_TYPE', +} + +export interface ValidationResponse { + isValid: boolean + parsed: any + messages?: string[] + transformed?: any +} + +export type Validator = ( + config: ValidationConfig, + value: unknown, + props: Record, + propertyPath: string, +) => ValidationResponse + +export const ISO_DATE_FORMAT = 'YYYY-MM-DDTHH:mm:ss.sssZ' + +export const DATA_TREE_KEYWORDS = { + actionPaths: 'actionPaths', + appsmith: 'appsmith', + pageList: 'pageList', + [EXECUTION_PARAM_KEY]: EXECUTION_PARAM_KEY, +} + +export const JAVASCRIPT_KEYWORDS = { + abstract: 'abstract', + arguments: 'arguments', + await: 'await', + boolean: 'boolean', + break: 'break', + byte: 'byte', + case: 'case', + catch: 'catch', + char: 'char', + class: 'class', + const: 'const', + continue: 'continue', + debugger: 'debugger', + default: 'default', + delete: 'delete', + do: 'do', + double: 'double', + else: 'else', + enum: 'enum', + eval: 'eval', + export: 'export', + extends: 'extends', + false: 'false', + final: 'final', + finally: 'finally', + float: 'float', + for: 'for', + function: 'function', + goto: 'goto', + if: 'if', + implements: 'implements', + import: 'import', + in: 'in', + instanceof: 'instanceof', + int: 'int', + interface: 'interface', + let: 'let', + long: 'long', + native: 'native', + new: 'new', + null: 'null', + package: 'package', + private: 'private', + protected: 'protected', + public: 'public', + return: 'return', + self: 'self', + short: 'short', + static: 'static', + super: 'super', + switch: 'switch', + synchronized: 'synchronized', + this: 'this', + throw: 'throw', + throws: 'throws', + transient: 'transient', + true: 'true', + try: 'try', + typeof: 'typeof', + var: 'var', + void: 'void', + volatile: 'volatile', + while: 'while', + with: 'with', + yield: 'yield', +} + +/** + * Global scope Identifiers in the worker context, accessible via the "self" keyword. + * These identifiers are already present in the worker context and shouldn't + * represent any valid identifier within Appsmith, as no entity should have + * same name as them to prevent unexpected behaviour during evaluation(which happens on + * the worker thread) in the worker. + * Check if an identifier (or window object/property) is available in the worker context + * here => https://worker-playground.glitch.me/ + */ +export const DEDICATED_WORKER_GLOBAL_SCOPE_IDENTIFIERS = { + AbortController: 'AbortController', + AbortSignal: 'AbortSignal', + AggregateError: 'AggregateError', + Array: 'Array', + ArrayBuffer: 'ArrayBuffer', + atob: 'atob', + Atomics: 'Atomics', + AudioData: 'AudioData', + AudioDecoder: 'AudioDecoder', + AudioEncoder: 'AudioEncoder', + BackgroundFetchManager: 'BackgroundFetchManager', + BackgroundFetchRecord: 'BackgroundFetchRecord', + BackgroundFetchRegistration: 'BackgroundFetchRegistration', + BarcodeDetector: 'BarcodeDetector', + BigInt: 'BigInt', + BigInt64Array: 'BigInt64Array', + BigUint64Array: 'BigUint64Array', + Blob: 'Blob', + Boolean: 'Boolean', + btoa: 'btoa', + BroadcastChannel: 'BroadcastChannel', + ByteLengthQueuingStrategy: 'ByteLengthQueuingStrategy', + caches: 'caches', + CSSSkewX: 'CSSSkewX', + CSSSkewY: 'CSSSkewY', + Cache: 'Cache', + CacheStorage: 'CacheStorage', + cancelAnimationFrame: 'cancelAnimationFrame', + CanvasFilter: 'CanvasFilter', + CanvasGradient: 'CanvasGradient', + CanvasPattern: 'CanvasPattern', + clearInterval: 'clearInterval', + clearTimeout: 'clearTimeout', + close: 'close', + CloseEvent: 'CloseEvent', + CompressionStream: 'CompressionStream', + console: 'console', + CountQueuingStrategy: 'CountQueuingStrategy', + createImageBitmap: 'createImageBitmap', + CropTarget: 'CropTarget', + crossOriginIsolated: 'crossOriginIsolated', + Crypto: 'Crypto', + CryptoKey: 'CryptoKey', + CustomEvent: 'CustomEvent', + decodeURI: 'decodeURI', + decodeURIComponent: 'decodeURIComponent', + DOMException: 'DOMException', + DOMMatrix: 'DOMMatrix', + DOMMatrixReadOnly: 'DOMMatrixReadOnly', + DOMPoint: 'DOMPoint', + DOMPointReadOnly: 'DOMPointReadOnly', + DOMQuad: 'DOMQuad', + DOMRect: 'DOMRect', + DOMRectReadOnly: 'DOMRectReadOnly', + DOMStringList: 'DOMStringList', + DataView: 'DataView', + Date: 'Date', + DecompressionStream: 'DecompressionStream', + DedicatedWorkerGlobalScope: 'DedicatedWorkerGlobalScope', + encodeURI: 'encodeURI', + encodeURIComponent: 'encodeURIComponent', + EncodedAudioChunk: 'EncodedAudioChunk', + EncodedVideoChunk: 'EncodedVideoChunk', + Error: 'Error', + ErrorEvent: 'ErrorEvent', + escape: 'escape', + eval: 'eval', + EvalError: 'EvalError', + Event: 'Event', + EventSource: 'EventSource', + EventTarget: 'EventTarget', + fetch: 'fetch', + File: 'File', + FileList: 'FileList', + FileReader: 'FileReader', + FileReaderSync: 'FileReaderSync', + FileSystemDirectoryHandle: 'FileSystemDirectoryHandle', + FileSystemFileHandle: 'FileSystemFileHandle', + FileSystemHandle: 'FileSystemHandle', + FileSystemSyncAccessHandle: 'FileSystemSyncAccessHandle', + FileSystemWritableFileStream: 'FileSystemWritableFileStream', + FinalizationRegistry: 'FinalizationRegistry', + Float32Array: 'Float32Array', + Float64Array: 'Float64Array', + FontFace: 'FontFace', + FormData: 'FormData', + Function: 'Function', + globalThis: 'globalThis', + hasOwnProperty: 'hasOwnProperty', + Headers: 'Headers', + IDBCursor: 'IDBCursor', + IDBCursorWithValue: 'IDBCursorWithValue', + IDBDatabase: 'IDBDatabase', + IDBFactory: 'IDBFactory', + IDBIndex: 'IDBIndex', + IDBKeyRange: 'IDBKeyRange', + IDBObjectStore: 'IDBObjectStore', + IDBOpenDBRequest: 'IDBOpenDBRequest', + IDBRequest: 'IDBRequest', + IDBTransaction: 'IDBTransaction', + IDBVersionChangeEvent: 'IDBVersionChangeEvent', + IdleDetector: 'IdleDetector', + ImageBitmap: 'ImageBitmap', + ImageBitmapRenderingContext: 'ImageBitmapRenderingContext', + ImageData: 'ImageData', + ImageDecoder: 'ImageDecoder', + ImageTrack: 'ImageTrack', + ImageTrackList: 'ImageTrackList', + importScripts: 'importScripts', + indexedDB: 'indexedDB', + Infinity: 'Infinity', + Int8Array: 'Int8Array', + Int16Array: 'Int16Array', + Int32Array: 'Int32Array', + Intl: 'Intl', + isFinite: 'isFinite', + isNaN: 'isNaN', + isPrototypeOf: 'isPrototypeOf', + isSecureContext: 'isSecureContext', + JSON: 'JSON', + Lock: 'Lock', + LockManager: 'LockManager', + location: 'location', + Map: 'Map', + Math: 'Math', + MediaCapabilities: 'MediaCapabilities', + MessageChannel: 'MessageChannel', + MessageEvent: 'MessageEvent', + MessagePort: 'MessagePort', + NaN: 'NaN', + name: 'name', + navigator: 'navigator', + NavigationPreloadManager: 'NavigationPreloadManager', + NavigatorUAData: 'NavigatorUAData', + NetworkInformation: 'NetworkInformation', + Notification: 'Notification', + Number: 'Number', + onmessage: 'onmessage', + onmessageerror: 'onmessageerror', + origin: 'origin', + Object: 'Object', + OffscreenCanvas: 'OffscreenCanvas', + OffscreenCanvasRenderingContext2D: 'OffscreenCanvasRenderingContext2D', + parseFloat: 'parseFloat', + parseInt: 'parseInt', + Path2D: 'Path2D', + PaymentInstruments: 'PaymentInstruments', + Performance: 'Performance', + PerformanceEntry: 'PerformanceEntry', + PerformanceMark: 'PerformanceMark', + PerformanceMeasure: 'PerformanceMeasure', + PerformanceObserver: 'PerformanceObserver', + PerformanceObserverEntryList: 'PerformanceObserverEntryList', + PerformanceResourceTiming: 'PerformanceResourceTiming', + PerformanceServerTiming: 'PerformanceServerTiming', + PeriodicSyncManager: 'PeriodicSyncManager', + PermissionStatus: 'PermissionStatus', + Permissions: 'Permissions', + postMessage: 'postMessage', + ProgressEvent: 'ProgressEvent', + Promise: 'Promise', + PromiseRejectionEvent: 'PromiseRejectionEvent', + Proxy: 'Proxy', + PushManager: 'PushManager', + PushSubscription: 'PushSubscription', + PushSubscriptionOptions: 'PushSubscriptionOptions', + queueMicrotask: 'queueMicrotask', + RTCEncodedAudioFrame: 'RTCEncodedAudioFrame', + RTCEncodedVideoFrame: 'RTCEncodedVideoFrame', + RangeError: 'RangeError', + ReadableByteStreamController: 'ReadableByteStreamController', + ReadableStream: 'ReadableStream', + ReadableStreamBYOBReader: 'ReadableStreamBYOBReader', + ReadableStreamBYOBRequest: 'ReadableStreamBYOBRequest', + ReadableStreamDefaultController: 'ReadableStreamDefaultController', + ReadableStreamDefaultReader: 'ReadableStreamDefaultReader', + ReferenceError: 'ReferenceError', + Reflect: 'Reflect', + RegExp: 'RegExp', + reportError: 'reportError', + ReportingObserver: 'ReportingObserver', + Request: 'Request', + requestAnimationFrame: 'requestAnimationFrame', + Response: 'Response', + Scheduler: 'Scheduler', + SecurityPolicyViolationEvent: 'SecurityPolicyViolationEvent', + Serial: 'Serial', + SerialPort: 'SerialPort', + ServiceWorkerRegistration: 'ServiceWorkerRegistration', + Set: 'Set', + setInterval: 'setInterval', + setTimeout: 'setTimeout', + StorageManager: 'StorageManager', + String: 'String', + structuredClone: 'structuredClone', + SubtleCrypto: 'SubtleCrypto', + Symbol: 'Symbol', + SyncManager: 'SyncManager', + SyntaxError: 'SyntaxError', + TaskController: 'TaskController', + TaskPriorityChangeEvent: 'TaskPriorityChangeEvent', + TaskSignal: 'TaskSignal', + TextDecoder: 'TextDecoder', + TextDecoderStream: 'TextDecoderStream', + TextEncoder: 'TextEncoder', + TextEncoderStream: 'TextEncoderStream', + TextMetrics: 'TextMetrics', + toString: 'toString', + TransformStream: 'TransformStream', + TransformStreamDefaultController: 'TransformStreamDefaultController', + TrustedHTML: 'TrustedHTML', + TrustedScript: 'TrustedScript', + TrustedScriptURL: 'TrustedScriptURL', + trustedTypes: 'trustedTypes', + TrustedTypePolicy: 'TrustedTypePolicy', + TrustedTypePolicyFactory: 'TrustedTypePolicyFactory', + TypeError: 'TypeError', + undefined: 'undefined', + unescape: 'unescape', + URIError: 'URIError', + URL: 'URL', + URLPattern: 'URLPattern', + URLSearchParams: 'URLSearchParams', + USB: 'USB', + USBAlternateInterface: 'USBAlternateInterface', + USBConfiguration: 'USBConfiguration', + USBConnectionEvent: 'USBConnectionEvent', + USBDevice: 'USBDevice', + USBEndpoint: 'USBEndpoint', + USBInTransferResult: 'USBInTransferResult', + USBInterface: 'USBInterface', + USBIsochronousInTransferPacket: 'USBIsochronousInTransferPacket', + USBIsochronousInTransferResult: 'USBIsochronousInTransferResult', + USBIsochronousOutTransferPacket: 'USBIsochronousOutTransferPacket', + USBIsochronousOutTransferResult: 'USBIsochronousOutTransferResult', + USBOutTransferResult: 'USBOutTransferResult', + Uint8Array: 'Uint8Array', + Uint8ClampedArray: 'Uint8ClampedArray', + Uint16Array: 'Uint16Array', + Uint32Array: 'Uint32Array', + UserActivation: 'UserActivation', + VideoColorSpace: 'VideoColorSpace', + VideoDecoder: 'VideoDecoder', + VideoEncoder: 'VideoEncoder', + VideoFrame: 'VideoFrame', + WeakMap: 'WeakMap', + WeakRef: 'WeakRef', + WeakSet: 'WeakSet', + WebAssembly: 'WebAssembly', + WebGL2RenderingContext: 'WebGL2RenderingContext', + WebGLActiveInfo: 'WebGLActiveInfo', + WebGLBuffer: 'WebGLBuffer', + WebGLFramebuffer: 'WebGLFramebuffer', + WebGLProgram: 'WebGLProgram', + WebGLQuery: 'WebGLQuery', + WebGLRenderbuffer: 'WebGLRenderbuffer', + WebGLRenderingContext: 'WebGLRenderingContext', + WebGLSampler: 'WebGLSampler', + WebGLShader: 'WebGLShader', + WebGLShaderPrecisionFormat: 'WebGLShaderPrecisionFormat', + WebGLSync: 'WebGLSync', + WebGLTexture: 'WebGLTexture', + WebGLTransformFeedback: 'WebGLTransformFeedback', + WebGLUniformLocation: 'WebGLUniformLocation', + WebGLVertexArrayObject: 'WebGLVertexArrayObject', + webkitRequestFileSystem: 'webkitRequestFileSystem', + webkitRequestFileSystemSync: 'webkitRequestFileSystemSync', + webkitResolveLocalFileSystemSyncURL: 'webkitResolveLocalFileSystemSyncURL', + webkitResolveLocalFileSystemURL: 'webkitResolveLocalFileSystemURL', + WebSocket: 'WebSocket', + WebTransport: 'WebTransport', + WebTransportBidirectionalStream: 'WebTransportBidirectionalStream', + WebTransportDatagramDuplexStream: 'WebTransportDatagramDuplexStream', + WebTransportError: 'WebTransportError', + Worker: 'Worker', + WorkerGlobalScope: 'WorkerGlobalScope', + WorkerLocation: 'WorkerLocation', + WorkerNavigator: 'WorkerNavigator', + WritableStream: 'WritableStream', + WritableStreamDefaultController: 'WritableStreamDefaultController', + WritableStreamDefaultWriter: 'WritableStreamDefaultWriter', + XMLHttpRequest: 'XMLHttpRequest', + XMLHttpRequestEventTarget: 'XMLHttpRequestEventTarget', + XMLHttpRequestUpload: 'XMLHttpRequestUpload', + + // Identifiers added to worker scope by Appsmith + evaluationVersion: 'evaluationVersion', + ALLOW_ASYNC: 'ALLOW_ASYNC', + IS_ASYNC: 'IS_ASYNC', + TRIGGER_COLLECTOR: 'TRIGGER_COLLECTOR', +} diff --git a/auxiliaries/code-editor/src/CodeEditor/entities/Action/index.ts b/auxiliaries/code-editor/src/CodeEditor/entities/Action/index.ts new file mode 100644 index 0000000..74853bd --- /dev/null +++ b/auxiliaries/code-editor/src/CodeEditor/entities/Action/index.ts @@ -0,0 +1,152 @@ +import { LayoutOnLoadActionErrors } from '@modou/code-editor/CodeEditor/constants/AppsmithActionConstants/ActionConstants' +import { EmbeddedRestDatasource } from '@modou/code-editor/CodeEditor/entities/Datasource' +import { DynamicPath } from '@modou/code-editor/CodeEditor/utils/DynamicBindingUtils' + +export enum PluginType { + API = 'API', + DB = 'DB', + SAAS = 'SAAS', + JS = 'JS', + REMOTE = 'REMOTE', +} + +export enum PluginName { + MONGO = 'MongoDB', +} + +export enum PluginPackageName { + POSTGRES = 'postgres-plugin', + MONGO = 'mongo-plugin', + S3 = 'amazons3-plugin', + GOOGLE_SHEETS = 'google-sheets-plugin', + FIRESTORE = 'firestore-plugin', + REST_API = 'restapi-plugin', + GRAPHQL = 'graphql-plugin', + JS = 'js-plugin', +} + +export interface Property { + key: string + value: string +} + +export interface BodyFormData { + editable: boolean + mandatory: boolean + description: string + key: string + value?: string + type: string +} + +export interface ApiActionConfig extends Omit { + headers: Property[] + httpMethod: string + path?: string + body?: JSON | string | Record | null + encodeParamsToggle: boolean + queryParameters?: Property[] + bodyFormData?: BodyFormData[] + formData: Record + query?: string | null + variable?: string | null +} + +export interface LimitOffset { + limit: Record + offset: Record +} + +export interface SelfReferencingData { + limitBased?: LimitOffset + curserBased?: { + previous?: LimitOffset + next?: LimitOffset + } +} + +export enum PaginationType { + NONE = 'NONE', + PAGE_NO = 'PAGE_NO', + URL = 'URL', + CURSOR = 'CURSOR', +} + +export interface KeyValuePair { + key?: string + value?: unknown +} + +export interface ActionConfig { + timeoutInMillisecond?: number + paginationType?: PaginationType + formData?: Record + pluginSpecifiedTemplates?: KeyValuePair[] + path?: string + queryParameters?: KeyValuePair[] + selfReferencingData?: SelfReferencingData +} + +export interface BaseAction { + id: string + name: string + workspaceId: string + pageId: string + collectionId?: string + pluginId: string + executeOnLoad: boolean + dynamicBindingPathList: DynamicPath[] + isValid: boolean + invalids: string[] + jsonPathKeys: string[] + cacheResponse: string + confirmBeforeExecute?: boolean + eventData?: any + messages: string[] + userPermissions?: string[] + errorReports?: LayoutOnLoadActionErrors[] +} + +interface BaseApiAction extends BaseAction { + pluginType: PluginType.API + actionConfiguration: ApiActionConfig +} + +export interface EmbeddedApiAction extends BaseApiAction { + datasource: EmbeddedRestDatasource +} + +export interface StoredDatasource { + id: string + pluginId?: string +} + +export interface StoredDatasourceApiAction extends BaseApiAction { + datasource: StoredDatasource +} + +export interface QueryAction extends BaseAction { + pluginType: PluginType.DB + pluginName?: PluginName + actionConfiguration: QueryActionConfig + datasource: StoredDatasource +} + +export interface QueryActionConfig extends ActionConfig { + body?: string +} + +export interface SaaSAction extends BaseAction { + pluginType: PluginType.SAAS + actionConfiguration: any + datasource: StoredDatasource +} + +export interface RemoteAction extends BaseAction { + pluginType: PluginType.REMOTE + actionConfiguration: any + datasource: StoredDatasource +} + +export type ApiAction = EmbeddedApiAction | StoredDatasourceApiAction +export type Action = ApiAction | QueryAction | SaaSAction | RemoteAction diff --git a/auxiliaries/code-editor/src/CodeEditor/entities/App.ts b/auxiliaries/code-editor/src/CodeEditor/entities/App.ts new file mode 100644 index 0000000..03f9627 --- /dev/null +++ b/auxiliaries/code-editor/src/CodeEditor/entities/App.ts @@ -0,0 +1,4 @@ +export enum APP_MODE { + EDIT = "EDIT", + PUBLISHED = "PUBLISHED", +} diff --git a/auxiliaries/code-editor/src/CodeEditor/entities/AppTheming/index.ts b/auxiliaries/code-editor/src/CodeEditor/entities/AppTheming/index.ts new file mode 100644 index 0000000..31727a5 --- /dev/null +++ b/auxiliaries/code-editor/src/CodeEditor/entities/AppTheming/index.ts @@ -0,0 +1,77 @@ +type DefaultStylesheet = { + [key: string]: string | DefaultStylesheet; +} & { + childStylesheet?: AppThemeStylesheet; +}; + +export type Stylesheet = T extends void + ? DefaultStylesheet + : T & DefaultStylesheet; + +export type AppThemeStylesheet = { + [key: string]: Stylesheet; +}; + +export type ButtonStyles = { + resetButtonStyles: { + [key: string]: string; + }; + submitButtonStyles: { + [key: string]: string; + }; +}; + +export type ChildStylesheet = { + childStylesheet: AppThemeStylesheet; +}; + +export type AppTheme = { + id: string; + name: string; + displayName: string; + created_by: string; + created_at: string; + isSystemTheme?: boolean; + // available values for particular type + // NOTE: config represents options available and + // properties represents the selected option + config: { + colors: { + primaryColor: string; + backgroundColor: string; + [key: string]: string; + }; + borderRadius: { + [key: string]: { + [key: string]: string; + }; + }; + boxShadow: { + [key: string]: { + [key: string]: string; + }; + }; + fontFamily: { + [key: string]: string[]; + }; + }; + // styles for specific widgets + stylesheet: AppThemeStylesheet; + // current values for the theme + properties: { + colors: { + primaryColor: string; + backgroundColor: string; + [key: string]: string; + }; + borderRadius: { + [key: string]: string; + }; + boxShadow: { + [key: string]: string; + }; + fontFamily: { + [key: string]: string; + }; + }; +}; diff --git a/auxiliaries/code-editor/src/CodeEditor/entities/AppsmithConsole/index.ts b/auxiliaries/code-editor/src/CodeEditor/entities/AppsmithConsole/index.ts new file mode 100644 index 0000000..c0c5ff0 --- /dev/null +++ b/auxiliaries/code-editor/src/CodeEditor/entities/AppsmithConsole/index.ts @@ -0,0 +1,48 @@ +import { ENTITY_TYPE } from '@modou/code-editor/CodeEditor/entities/DataTree/types' + +export type Methods = + | 'log' + | 'debug' + | 'info' + | 'warn' + | 'error' + | 'table' + | 'clear' + | 'time' + | 'timeEnd' + | 'count' + | 'assert' +export interface LogObject { + method: Methods | 'result' + data: any[] + timestamp: string + id: string + severity: Severity +} + +export interface SourceEntity { + type: ENTITY_TYPE + // Widget or action name + name: string + // Id of the widget or action + id: string + // property path of the child + propertyPath?: string +} +export interface UserLogObject { + logObject: LogObject[] + source: SourceEntity +} + +export enum Severity { + // Everything, irrespective of what the user should see or not + // DEBUG = "debug", + // Something the dev user should probably know about + INFO = 'info', + // Doesn't break the app, but can cause slowdowns / ux issues/ unexpected behaviour + WARNING = 'warning', + // Can cause an error in some cases/ single widget, app will work in other cases + ERROR = 'error', + // Makes the app unusable, can't progress without fixing this. + // CRITICAL = "critical", +} diff --git a/auxiliaries/code-editor/src/CodeEditor/entities/DataTree/actionTriggers.ts b/auxiliaries/code-editor/src/CodeEditor/entities/DataTree/actionTriggers.ts new file mode 100644 index 0000000..8d8b863 --- /dev/null +++ b/auxiliaries/code-editor/src/CodeEditor/entities/DataTree/actionTriggers.ts @@ -0,0 +1,216 @@ +import { TypeOptions } from 'react-toastify' + +import { NavigationTargetType } from '@modou/code-editor/CodeEditor/sagas/ActionExecution/NavigateActionSaga' + +export enum ActionTriggerType { + RUN_PLUGIN_ACTION = 'RUN_PLUGIN_ACTION', + CLEAR_PLUGIN_ACTION = 'CLEAR_PLUGIN_ACTION', + NAVIGATE_TO = 'NAVIGATE_TO', + SHOW_ALERT = 'SHOW_ALERT', + SHOW_MODAL_BY_NAME = 'SHOW_MODAL_BY_NAME', + CLOSE_MODAL = 'CLOSE_MODAL', + STORE_VALUE = 'STORE_VALUE', + REMOVE_VALUE = 'REMOVE_VALUE', + CLEAR_STORE = 'CLEAR_STORE', + DOWNLOAD = 'DOWNLOAD', + COPY_TO_CLIPBOARD = 'COPY_TO_CLIPBOARD', + RESET_WIDGET_META_RECURSIVE_BY_NAME = 'RESET_WIDGET_META_RECURSIVE_BY_NAME', + SET_INTERVAL = 'SET_INTERVAL', + CLEAR_INTERVAL = 'CLEAR_INTERVAL', + GET_CURRENT_LOCATION = 'GET_CURRENT_LOCATION', + WATCH_CURRENT_LOCATION = 'WATCH_CURRENT_LOCATION', + STOP_WATCHING_CURRENT_LOCATION = 'STOP_WATCHING_CURRENT_LOCATION', + CONFIRMATION_MODAL = 'CONFIRMATION_MODAL', + POST_MESSAGE = 'POST_MESSAGE', +} + +export const ActionTriggerFunctionNames: Record = { + [ActionTriggerType.CLEAR_INTERVAL]: 'clearInterval', + [ActionTriggerType.CLEAR_PLUGIN_ACTION]: 'action.clear', + [ActionTriggerType.CLOSE_MODAL]: 'closeModal', + [ActionTriggerType.COPY_TO_CLIPBOARD]: 'copyToClipboard', + [ActionTriggerType.DOWNLOAD]: 'download', + [ActionTriggerType.NAVIGATE_TO]: 'navigateTo', + [ActionTriggerType.RESET_WIDGET_META_RECURSIVE_BY_NAME]: 'resetWidget', + [ActionTriggerType.RUN_PLUGIN_ACTION]: 'action.run', + [ActionTriggerType.SET_INTERVAL]: 'setInterval', + [ActionTriggerType.SHOW_ALERT]: 'showAlert', + [ActionTriggerType.SHOW_MODAL_BY_NAME]: 'showModal', + [ActionTriggerType.STORE_VALUE]: 'storeValue', + [ActionTriggerType.REMOVE_VALUE]: 'removeValue', + [ActionTriggerType.CLEAR_STORE]: 'clearStore', + [ActionTriggerType.GET_CURRENT_LOCATION]: 'getCurrentLocation', + [ActionTriggerType.WATCH_CURRENT_LOCATION]: 'watchLocation', + [ActionTriggerType.STOP_WATCHING_CURRENT_LOCATION]: 'stopWatch', + [ActionTriggerType.CONFIRMATION_MODAL]: 'ConfirmationModal', + [ActionTriggerType.POST_MESSAGE]: 'postWindowMessage', +} + +export interface RunPluginActionDescription { + type: ActionTriggerType.RUN_PLUGIN_ACTION + payload: { + actionId: string + params?: Record + onSuccess?: string + onError?: string + } +} + +export interface ClearPluginActionDescription { + type: ActionTriggerType.CLEAR_PLUGIN_ACTION + payload: { + actionId: string + } +} + +export interface NavigateActionDescription { + type: ActionTriggerType.NAVIGATE_TO + payload: { + pageNameOrUrl: string + params?: Record + target?: NavigationTargetType + } +} + +export interface ShowAlertActionDescription { + type: ActionTriggerType.SHOW_ALERT + payload: { + message: string | unknown + style?: TypeOptions + } +} + +export interface ShowModalActionDescription { + type: ActionTriggerType.SHOW_MODAL_BY_NAME + payload: { modalName: string } +} + +export interface CloseModalActionDescription { + type: ActionTriggerType.CLOSE_MODAL + payload: { modalName: string } +} + +export interface StoreValueActionDescription { + type: ActionTriggerType.STORE_VALUE + payload: { + key: string + value: string + persist: boolean + uniqueActionRequestId: string + } +} + +export interface RemoveValueActionDescription { + type: ActionTriggerType.REMOVE_VALUE + payload: { + key: string + } +} + +export interface ClearStoreActionDescription { + type: ActionTriggerType.CLEAR_STORE + payload: null +} + +export interface DownloadActionDescription { + type: ActionTriggerType.DOWNLOAD + payload: { + data: any + name: string + type: string + } +} + +export interface CopyToClipboardDescription { + type: ActionTriggerType.COPY_TO_CLIPBOARD + payload: { + data: string + options: { debug?: boolean; format?: string } + } +} + +export interface ResetWidgetDescription { + type: ActionTriggerType.RESET_WIDGET_META_RECURSIVE_BY_NAME + payload: { + widgetName: string + resetChildren: boolean + } +} + +export interface SetIntervalDescription { + type: ActionTriggerType.SET_INTERVAL + payload: { + callback: string + interval: number + id?: string + } +} + +export interface ClearIntervalDescription { + type: ActionTriggerType.CLEAR_INTERVAL + payload: { + id: string + } +} + +interface GeolocationOptions { + maximumAge?: number + timeout?: number + enableHighAccuracy?: boolean +} + +interface GeolocationPayload { + onSuccess?: string + onError?: string + options?: GeolocationOptions +} + +export interface GetCurrentLocationDescription { + type: ActionTriggerType.GET_CURRENT_LOCATION + payload: GeolocationPayload +} + +export interface WatchCurrentLocationDescription { + type: ActionTriggerType.WATCH_CURRENT_LOCATION + payload: GeolocationPayload +} + +export interface StopWatchingCurrentLocationDescription { + type: ActionTriggerType.STOP_WATCHING_CURRENT_LOCATION + payload?: Record +} + +export interface ConfirmationModal { + type: ActionTriggerType.CONFIRMATION_MODAL + payload?: Record +} + +export interface PostMessageDescription { + type: ActionTriggerType.POST_MESSAGE + payload: { + message: unknown + source: string + targetOrigin: string + } +} + +export type ActionDescription = + | RunPluginActionDescription + | ClearPluginActionDescription + | NavigateActionDescription + | ShowAlertActionDescription + | ShowModalActionDescription + | CloseModalActionDescription + | StoreValueActionDescription + | RemoveValueActionDescription + | ClearStoreActionDescription + | DownloadActionDescription + | CopyToClipboardDescription + | ResetWidgetDescription + | SetIntervalDescription + | ClearIntervalDescription + | GetCurrentLocationDescription + | WatchCurrentLocationDescription + | StopWatchingCurrentLocationDescription + | ConfirmationModal + | PostMessageDescription diff --git a/auxiliaries/code-editor/src/CodeEditor/entities/DataTree/dataTreeFactory.ts b/auxiliaries/code-editor/src/CodeEditor/entities/DataTree/dataTreeFactory.ts new file mode 100644 index 0000000..d0e41ab --- /dev/null +++ b/auxiliaries/code-editor/src/CodeEditor/entities/DataTree/dataTreeFactory.ts @@ -0,0 +1,79 @@ +import { AppTheme } from '@modou/code-editor/CodeEditor/entities/AppTheming' +import { + ActionEntityConfig, + ActionEntityEvalTree, + ENTITY_TYPE, + JSActionEntityConfig, + JSActionEvalTree, + WidgetConfig, +} from '@modou/code-editor/CodeEditor/entities/DataTree/types' +import { AppDataState } from '@modou/code-editor/CodeEditor/reducers/entityReducers/appReducer' +import { WidgetConfigProps } from '@modou/code-editor/CodeEditor/reducers/entityReducers/widgetConfigReducer' +import { WidgetProps } from '@modou/code-editor/CodeEditor/widgets/BaseWidget' + +interface Page { + pageName: string + pageId: string + isDefault: boolean + latest?: boolean + isHidden?: boolean + slug: string + customSlug?: string + userPermissions?: string[] +} + +export interface DataTreeAppsmith extends Omit { + ENTITY_TYPE: ENTITY_TYPE.APPSMITH + store: Record + theme: AppTheme['properties'] +} + +export interface WidgetEvalTree extends WidgetProps { + meta: Record + ENTITY_TYPE: ENTITY_TYPE.WIDGET +} + +export interface DataTreeWidget extends WidgetEvalTree, WidgetConfig {} +export type DataTreeJSAction = JSActionEvalTree & JSActionEntityConfig + +export interface DataTreeAction + extends ActionEntityEvalTree, + ActionEntityConfig {} + +export type DataTreeObjectEntity = + | DataTreeWidget + | DataTreeAppsmith + | DataTreeJSAction + | DataTreeAction + +// TODO 补全 ActionDispatcher +export type DataTreeEntity = DataTreeObjectEntity | Page[] +export interface WidgetEntityConfig + extends Partial, + Omit, + WidgetConfig { + defaultMetaProps: string[] + type: string +} +export interface UnEvalTreeAction extends ActionEntityEvalTree { + __config__: ActionEntityConfig +} +export interface UnEvalTreeJSAction extends JSActionEvalTree { + __config__: JSActionEntityConfig +} +export interface UnEvalTreeWidget extends WidgetEvalTree { + __config__: WidgetEntityConfig +} +export type UnEvalTreeEntityObject = + | UnEvalTreeAction + | UnEvalTreeJSAction + | UnEvalTreeWidget + +export type UnEvalTreeEntity = + | UnEvalTreeEntityObject + | DataTreeAppsmith + | Page[] + +export interface UnEvalTree { + [entityName: string]: UnEvalTreeEntity +} diff --git a/auxiliaries/code-editor/src/CodeEditor/entities/DataTree/types.ts b/auxiliaries/code-editor/src/CodeEditor/entities/DataTree/types.ts new file mode 100644 index 0000000..c986e0a --- /dev/null +++ b/auxiliaries/code-editor/src/CodeEditor/entities/DataTree/types.ts @@ -0,0 +1,133 @@ +import { ActionResponse } from '@modou/code-editor/CodeEditor/api/ActionAPI' +import { PluginId } from '@modou/code-editor/CodeEditor/api/PluginApi' +import { ValidationConfig } from '@modou/code-editor/CodeEditor/constants/PropertyControlConstants' +import { + ActionConfig, + PluginType, +} from '@modou/code-editor/CodeEditor/entities/Action' +import { + ActionDescription, + ClearPluginActionDescription, + RunPluginActionDescription, +} from '@modou/code-editor/CodeEditor/entities/DataTree/actionTriggers' +import { Variable } from '@modou/code-editor/CodeEditor/entities/JSCollection' +import { + DependencyMap, + DynamicPath, +} from '@modou/code-editor/CodeEditor/utils/DynamicBindingUtils' + +export type ActionDispatcher = ( + ...args: any[] +) => Promise | ActionDescription +export enum ENTITY_TYPE { + ACTION = 'ACTION', + WIDGET = 'WIDGET', + APPSMITH = 'APPSMITH', + JSACTION = 'JSACTION', +} +export enum EvaluationSubstitutionType { + TEMPLATE = 'TEMPLATE', + PARAMETER = 'PARAMETER', + SMART_SUBSTITUTE = 'SMART_SUBSTITUTE', +} + +// Action entity types +export interface ActionEntityEvalTree { + actionId: string + isLoading: boolean + data: ActionResponse['body'] + run: ActionDispatcher | RunPluginActionDescription | Record + clear: + | ActionDispatcher + | ClearPluginActionDescription + | Record + responseMeta: { + statusCode?: string + isExecutionSuccess: boolean + headers?: unknown + } + ENTITY_TYPE: ENTITY_TYPE.ACTION + config: Partial + datasourceUrl: string +} + +export interface ActionEntityConfig { + dynamicBindingPathList: DynamicPath[] + bindingPaths: Record + reactivePaths: Record + ENTITY_TYPE: ENTITY_TYPE.ACTION + dependencyMap: DependencyMap + logBlackList: Record + pluginType: PluginType + pluginId: PluginId + actionId: string + name: string +} + +// JSAction (JSObject) entity Types + +export interface MetaArgs { + arguments: Variable[] + isAsync: boolean + confirmBeforeExecute: boolean + body: string +} + +export interface JSActionEntityConfig { + meta: Record + dynamicBindingPathList: DynamicPath[] + bindingPaths: Record + reactivePaths: Record + variables: string[] + dependencyMap: DependencyMap + pluginType: PluginType.JS + name: string + ENTITY_TYPE: ENTITY_TYPE.JSACTION + actionId: string +} + +export interface JSActionEvalTree { + [propName: string]: any + + body: string +} + +// Widget entity Types + +// Private widgets do not get evaluated +// For example, for widget Button1 in a List widget List1, List1.template.Button1.text gets evaluated, +// so there is no need to evaluate Button1.text +export type PrivateWidgets = Record + +/** + * Map of overriding property as key and overridden property as values + */ +export type OverridingPropertyPaths = Record + +export enum OverridingPropertyType { + META = 'META', + DEFAULT = 'DEFAULT', +} + +/** + * Map of property name as key and value as object with defaultPropertyName and metaPropertyName which it depends on. + */ +export type PropertyOverrideDependency = Record< + string, + { + DEFAULT: string | undefined + META: string | undefined + } +> + +export interface WidgetConfig { + bindingPaths: Record + reactivePaths: Record + triggerPaths: Record + validationPaths: Record + ENTITY_TYPE: ENTITY_TYPE.WIDGET + logBlackList: Record + propertyOverrideDependency: PropertyOverrideDependency + overridingPropertyPaths: OverridingPropertyPaths + privateWidgets: PrivateWidgets +} diff --git a/auxiliaries/code-editor/src/CodeEditor/entities/Datasource/index.ts b/auxiliaries/code-editor/src/CodeEditor/entities/Datasource/index.ts new file mode 100644 index 0000000..dff527f --- /dev/null +++ b/auxiliaries/code-editor/src/CodeEditor/entities/Datasource/index.ts @@ -0,0 +1,136 @@ +import _ from 'lodash' + +import { APIResponseError } from '@modou/code-editor/CodeEditor/api/APIResponses' +import { + ActionConfig, + Property, +} from '@modou/code-editor/CodeEditor/entities/Action' + +export enum AuthType { + OAUTH2 = 'oAuth2', + DBAUTH = 'dbAuth', +} + +export enum AuthenticationStatus { + NONE = 'NONE', + IN_PROGRESS = 'IN_PROGRESS', + SUCCESS = 'SUCCESS', +} + +export interface DatasourceAuthentication { + authType?: string + username?: string + password?: string + label?: string + headerPrefix?: string + value?: string + addTo?: string + bearerToken?: string + authenticationStatus?: string + authenticationType?: string +} + +export interface DatasourceColumns { + name: string + type: string +} + +export interface DatasourceKeys { + name: string + type: string +} + +export interface DatasourceStructure { + tables?: DatasourceTable[] + error?: APIResponseError +} + +export interface QueryTemplate { + actionConfiguration?: ActionConfig + configuration: Record + title: string + body: string + pluginSpecifiedTemplates?: Array<{ key?: string; value?: unknown }> +} + +export interface DatasourceTable { + type: string + name: string + columns: DatasourceColumns[] + keys: DatasourceKeys[] + templates: QueryTemplate[] +} + +// todo: check which fields are truly optional and move the common ones into base +interface BaseDatasource { + pluginId: string + name: string + workspaceId: string + isValid: boolean + isConfigured?: boolean + userPermissions?: string[] + isDeleting?: boolean +} + +export const isEmbeddedRestDatasource = ( + val: any, +): val is EmbeddedRestDatasource => { + if (!_.isObject(val)) { + return false + } + if (!('datasourceConfiguration' in val)) { + return false + } + val = val as EmbeddedRestDatasource + // Object should exist and have value + if (!val.datasourceConfiguration) return false + // url might exist as a key but not have value, so we won't check value + return 'url' in val.datasourceConfiguration +} + +export interface EmbeddedRestDatasource extends BaseDatasource { + datasourceConfiguration: { url: string } + invalids: string[] + messages: string[] +} + +export interface DatasourceConfiguration { + url: string + authentication?: DatasourceAuthentication + properties?: Property[] + headers?: Property[] + queryParameters?: Property[] + databaseName?: string +} + +export interface Datasource extends BaseDatasource { + id: string + datasourceConfiguration: DatasourceConfiguration + invalids?: string[] + structure?: DatasourceStructure + messages?: string[] + success?: boolean +} + +export interface MockDatasource { + name: string + description: string + packageName: string + pluginType: string + pluginName?: string +} + +export const DEFAULT_DATASOURCE = ( + pluginId: string, + workspaceId: string, +): EmbeddedRestDatasource => ({ + name: 'DEFAULT_REST_DATASOURCE', + datasourceConfiguration: { + url: '', + }, + invalids: [], + isValid: true, + pluginId, + workspaceId, + messages: [], +}) diff --git a/auxiliaries/code-editor/src/CodeEditor/entities/JSCollection/index.ts b/auxiliaries/code-editor/src/CodeEditor/entities/JSCollection/index.ts new file mode 100644 index 0000000..d55a0fa --- /dev/null +++ b/auxiliaries/code-editor/src/CodeEditor/entities/JSCollection/index.ts @@ -0,0 +1,33 @@ +import { LayoutOnLoadActionErrors } from '@modou/code-editor/CodeEditor/constants/AppsmithActionConstants/ActionConstants' + +import { BaseAction, PluginType } from '../Action' + +export interface Variable { + name: string + value: any +} +export interface JSCollection { + id: string + applicationId: string + workspaceId: string + name: string + pageId: string + pluginId: string + pluginType: PluginType.JS + actions: JSAction[] + body: string + variables: Variable[] + userPermissions?: string[] + errorReports?: LayoutOnLoadActionErrors[] +} + +export interface JSActionConfig { + body: string + isAsync: boolean + timeoutInMillisecond: number + jsArguments: Variable[] +} +export interface JSAction extends BaseAction { + actionConfiguration: JSActionConfig + clientSideExecution: boolean +} diff --git a/auxiliaries/code-editor/src/CodeEditor/entities/Replay/ReplayEntity/ReplayCanvas.ts b/auxiliaries/code-editor/src/CodeEditor/entities/Replay/ReplayEntity/ReplayCanvas.ts new file mode 100644 index 0000000..a6a2072 --- /dev/null +++ b/auxiliaries/code-editor/src/CodeEditor/entities/Replay/ReplayEntity/ReplayCanvas.ts @@ -0,0 +1,163 @@ +import { Diff } from 'deep-diff' +import { set } from 'lodash' + +import { AppTheme } from '@modou/code-editor/CodeEditor/entities/AppTheming' +import { ENTITY_TYPE } from '@modou/code-editor/CodeEditor/entities/DataTree/types' +import { ReplayEntity } from '@modou/code-editor/CodeEditor/entities/Replay' +import { + FOCUSES, + TOASTS, + UPDATES, + WIDGETS, + addToArray, + setPropertyUpdate, +} from '@modou/code-editor/CodeEditor/entities/Replay/replayUtils' +import { CanvasWidgetsReduxState } from '@modou/code-editor/CodeEditor/reducers/entityReducers/canvasWidgetsReducer' + +export interface Canvas { + widgets: CanvasWidgetsReduxState + theme: AppTheme +} +export type CanvasDiff = Diff +export type DSLDiff = Diff + +const positionProps = [ + 'leftColumn', + 'rightColumn', + 'topRow', + 'bottomRow', + 'minHeight', + 'parentColumnSpace', + 'parentRowSpace', + 'children', + 'parentId', + 'renderMode', + 'detachFromLayout', + 'noContainerOffset', + 'isCanvas', + 'height', +] + +/** + * checks property changed is a positional property + * + * @param widgetProperty + * @returns + */ +function isPositionUpdate(widgetProperty: string) { + return positionProps.includes(widgetProperty) +} +export default class ReplayCanvas extends ReplayEntity { + public constructor(entity: Canvas) { + super(entity, ENTITY_TYPE.WIDGET) + } + + /** + * process the diff + * + * @param diff + * @param replay + * @param isUndo + * @returns + */ + public processDiff(diff: CanvasDiff, replay: any, isUndo: boolean) { + if (!diff || !diff.path || !diff.path.length || diff.path[1] === '0') return + + if (diff.path.includes('widgets')) { + return this.processDiffForWidgets(diff, replay, isUndo) + } + + if (diff.path.includes('theme')) { + return this.processDiffForTheme(diff, replay) + } + } + + /** + * process diff related to app theming + * + * @param diff + * @param replay + */ + public processDiffForTheme(diff: CanvasDiff, replay: any) { + if (!diff || !diff.path || !diff.path.length || diff.path[1] === '0') return + + set(replay, 'theme', true) + + if (diff.path.join('.') === 'theme.name') { + set(replay, 'themeChanged', true) + } + } + + /** + * process diffs related to DSL ( widgets ) + * + * @param diff + * @param replay + * @param isUndo + * @returns + */ + public processDiffForWidgets(diff: CanvasDiff, replay: any, isUndo: boolean) { + if (!diff || !diff.path || !diff.path.length || diff.path[1] === '0') return + + const widgetId = diff.path[1] + + switch (diff.kind) { + // new elements is added in dsl + case 'N': + if (diff.path.length === 2) { + const toast = this.createToast( + diff.rhs, + this.entity.widgets[widgetId], + widgetId, + isUndo, + !isUndo, + ) + addToArray(replay, TOASTS, toast) + } else { + setPropertyUpdate(replay, [WIDGETS, widgetId, UPDATES], diff.path) + } + break + // element is deleted in dsl + case 'D': + if (diff.path.length === 2) { + const toast = this.createToast( + diff.lhs, + this.entity.widgets[widgetId], + widgetId, + isUndo, + isUndo, + ) + addToArray(replay, TOASTS, toast) + } else { + setPropertyUpdate(replay, [WIDGETS, widgetId, UPDATES], diff.path) + } + break + // element is edited + case 'E': + if (isPositionUpdate(diff.path[diff.path.length - 2])) { + set(replay, [WIDGETS, widgetId, FOCUSES], true) + } else { + setPropertyUpdate(replay, [WIDGETS, widgetId, UPDATES], diff.path) + } + break + default: + break + } + } + + private createToast( + diffWidget: any, + dslWidget: CanvasWidgetsReduxState | undefined, + widgetId: string, + isUndo: boolean, + isCreated: boolean, + ) { + const widgetName = isCreated ? diffWidget.widgetName : dslWidget?.widgetName + return { + isCreated, + isUndo, + widgetName, + widgetId, + } + } +} diff --git a/auxiliaries/code-editor/src/CodeEditor/entities/Replay/ReplayEntity/ReplayEditor.ts b/auxiliaries/code-editor/src/CodeEditor/entities/Replay/ReplayEntity/ReplayEditor.ts new file mode 100644 index 0000000..ec331a8 --- /dev/null +++ b/auxiliaries/code-editor/src/CodeEditor/entities/Replay/ReplayEntity/ReplayEditor.ts @@ -0,0 +1,89 @@ +/* + This type represents all the form objects that can be undone/redone. + (Action, datasource, jsAction etc) +*/ +import { Diff } from 'deep-diff' +import { isEmpty } from 'lodash' + +import { ENTITY_TYPE } from '@modou/code-editor/CodeEditor/entities/DataTree/types' +import { JSActionConfig } from '@modou/code-editor/CodeEditor/entities/JSCollection' +import { ReplayEntity } from '@modou/code-editor/CodeEditor/entities/Replay' +import { pathArrayToString } from '@modou/code-editor/CodeEditor/entities/Replay/replayUtils' + +import { Action } from '../../Action' +import { Datasource } from '../../Datasource' +import { Canvas } from './ReplayCanvas' + +export type Replayable = + | Partial + | Partial + | Partial + | Partial + +type ReplayEditorDiff = Diff + +export interface ReplayEditorUpdate { + modifiedProperty: string + index?: number + update: Replayable | ReplayEditorDiff + kind: 'N' | 'D' | 'E' | 'A' + isUndo?: boolean +} +export class ReplayEditor extends ReplayEntity { + // eslint-disable-next-line @typescript-eslint/no-useless-constructor + constructor(entity: Replayable, entityType: ENTITY_TYPE) { + super(entity, entityType) + } + + public processDiff( + diff: Diff, + replay: any, + isUndo: boolean, + ): void { + if (!diff || !diff.path || !diff.path.length) return + replay.updates = (replay.updates || []).concat( + this.getChanges(diff, isUndo) ?? [], + ) + } + + /* + The should get us the modified property (configProperty from editor, + settings and form json files), the updated value and the kind of update. + The modifiedProperty would be used to highlight the field that has been replayed. + We might need to use the kind in future to display toast + messages or even highlight based on the kind. + */ + private getChanges( + diff: Diff, + isUndo: boolean, + ): ReplayEditorUpdate | undefined { + const { kind, path } = diff + if (diff.kind === 'N') { + if (isEmpty(diff.rhs)) return + return { + modifiedProperty: pathArrayToString(path), + update: diff.rhs, + kind, + } + } else if (diff.kind === 'A') { + return { + modifiedProperty: pathArrayToString(path), + update: diff.item, + index: diff.index, + kind, + isUndo, + } + } else if (diff.kind === 'E') { + return { + modifiedProperty: pathArrayToString(path), + update: isUndo ? diff.lhs : diff.rhs, + kind, + } + } + return { + modifiedProperty: pathArrayToString(path), + update: diff.lhs, + kind, + } + } +} diff --git a/auxiliaries/code-editor/src/CodeEditor/entities/Replay/index.ts b/auxiliaries/code-editor/src/CodeEditor/entities/Replay/index.ts new file mode 100644 index 0000000..df0cc68 --- /dev/null +++ b/auxiliaries/code-editor/src/CodeEditor/entities/Replay/index.ts @@ -0,0 +1,161 @@ +import { captureException } from '@sentry/react' +import { Diff, applyChange, diff as deepDiff, revertChange } from 'deep-diff' +import { Doc, Map, UndoManager } from 'yjs' + +import { ENTITY_TYPE } from '@modou/code-editor/CodeEditor/entities/DataTree/types' + +import { getPathsFromDiff } from './replayUtils' + +const _DIFF_ = 'diff' +type ReplayType = 'UNDO' | 'REDO' + +export abstract class ReplayEntity { + private readonly diffMap: any + private undoManager: UndoManager + protected entity: T + private readonly replayEntityType: ENTITY_TYPE + logs: any[] = [] + protected abstract processDiff( + diff: Diff, + replay: any, + isUndo: boolean, + ): any + + constructor(entity: T, replayEntityType: ENTITY_TYPE) { + const doc = new Doc() + this.diffMap = doc.get('map', Map) + this.entity = entity + this.diffMap.set(_DIFF_, []) + this.undoManager = new UndoManager(this.diffMap, { captureTimeout: 100 }) + this.replayEntityType = replayEntityType + } + + /** + * checks if there is anything in the redoStack or undoStack + * + * @return boolean + */ + canReplay(replayType: ReplayType) { + switch (replayType) { + case 'UNDO': + return this.undoManager.undoStack.length > 0 + case 'REDO': + return this.undoManager.redoStack.length > 0 + default: + return false + } + } + + /** + * get the diffs from yMap + * + * @returns + */ + private getDiffs() { + return this.diffMap.get(_DIFF_) + } + + /** + * replay actions ( undo redo ) + * + * Note: + * important thing to note is that for redo we redo first, then + * get the diff map and undo, we get diff first, then undo + * + * @param replayType + */ + replay(replayType: ReplayType) { + const start = performance.now() + + if (this.canReplay(replayType)) { + let diffs + + switch (replayType) { + case 'UNDO': + diffs = this.getDiffs() + this.undoManager.undo() + break + case 'REDO': + this.undoManager.redo() + diffs = this.getDiffs() + break + } + + const replay = this.applyDiffs(diffs, replayType) + const stop = performance.now() + this.logs.push({ + log: `replay ${replayType}`, + undoTime: `${stop - start} ms`, + replay: replay, + diffs: diffs, + }) + + return { + replayEntity: this.entity, + replay, + logs: this.logs, + event: `REPLAY_${replayType}`, + timeTaken: stop - start, + paths: getPathsFromDiff(diffs), + replayEntityType: this.replayEntityType, + } + } + + return null + } + + /** + * saves the changes (diff) in yMap + * only if there is a deep diff + * + * @param entity + */ + update(entity: T) { + const startTime = performance.now() + const diffs = deepDiff(this.entity, entity) + if (diffs?.length) { + this.entity = entity + this.diffMap.set(_DIFF_, diffs) + } + const endTime = performance.now() + this.logs.push({ + log: 'replay updating', + updateTime: `${endTime - startTime} ms`, + }) + } + + clearLogs() { + this.logs = [] + } + + /** + * apply the diff on the current dsl + * + * @param diffs + * @param isUndo + */ + applyDiffs(diffs: Array>, replayType: ReplayType) { + const replay: any = {} + const isUndo = replayType === 'UNDO' + const applyDiff = isUndo ? revertChange : applyChange + + for (const diff of diffs) { + if (!Array.isArray(diff.path) || diff.path.length === 0) { + continue + } + try { + this.processDiff(diff, replay, isUndo) + applyDiff(this.entity, true, diff) + } catch (e) { + captureException(e, { + extra: { + diff, + updateLength: diffs.length, + }, + }) + } + } + + return replay + } +} diff --git a/auxiliaries/code-editor/src/CodeEditor/entities/Replay/replayUtils.ts b/auxiliaries/code-editor/src/CodeEditor/entities/Replay/replayUtils.ts new file mode 100644 index 0000000..36c6fcb --- /dev/null +++ b/auxiliaries/code-editor/src/CodeEditor/entities/Replay/replayUtils.ts @@ -0,0 +1,109 @@ +import { Diff } from 'deep-diff' +import { get, isArray, isEmpty, set } from 'lodash' + +export const UPDATES = 'propertyUpdates' +export const REPLAY_DELAY = 300 +export const REPLAY_FOCUS_DELAY = 100 +export const TOASTS = 'toasts' +export const FOCUSES = 'needsFocus' +export const WIDGETS = 'widgets' + +/** + * checks the existing value and sets he propertyUpdate if required + * + * @param replay + * @param path + * @param value + * @returns + */ +export function setPropertyUpdate( + replay: any, + path: string[], + value: string[], +) { + const existingPathValue = get(replay, path) + + if (!existingPathValue || existingPathValue.length > 2) { + set(replay, path, value) + set(replay, UPDATES, true) + } +} + +/** + * pushes value to array element in array of objects + * + * @param obj + * @param key + * @param value + * @returns + */ +export function addToArray(obj: any, key: string, value: any) { + if (!obj) return + + if (obj[key] && Array.isArray(obj[key])) { + obj[key].push(value) + } else { + obj[key] = [value] + } +} + +/** + * creates paths changed from diffs array + * + * @param diffs + * @returns + */ +export function getPathsFromDiff(diffs: Array>) { + const paths = [] + + for (const diff of diffs) { + if (!diff.path || !Array.isArray(diff.path)) continue + paths.push(diff.path.join('.')) + } + + return paths +} + +/** + * creates paths changed from diffs array + * + * @param path + * @returns + */ +export function pathArrayToString(path?: string[]) { + let stringPath = '' + if (!path || path.length === 0) return stringPath + stringPath = path[0] + for (let i = 1; i < path.length; i++) { + stringPath += isNaN(parseInt(path[i])) ? `.${path[i]}` : `[${path[i]}]` + } + return stringPath +} + +/** + * Retrieves field config and parent section using the config property + * + * @param config + * @param field + * @param parentSection + * @returns + */ +export function findFieldInfo( + config: Array, + field: string, + parentSection = '', +) { + let result = {} + if (!config || !isArray(config)) return result + for (const conf of config) { + if (conf.configProperty === field) { + result = { conf, parentSection } + break + } else if (conf.children) { + parentSection = conf.sectionName || parentSection + result = findFieldInfo(conf.children, field, parentSection) + if (!isEmpty(result)) break + } + } + return result +} diff --git a/auxiliaries/code-editor/src/CodeEditor/index.tsx b/auxiliaries/code-editor/src/CodeEditor/index.tsx index c35056e..1af652f 100644 --- a/auxiliaries/code-editor/src/CodeEditor/index.tsx +++ b/auxiliaries/code-editor/src/CodeEditor/index.tsx @@ -30,13 +30,13 @@ import { injectGlobal, mcss } from '@modou/css-in-js' import './common/code-mirror-libs' import './common/modes' -export type CodeEditorExpected = { +export interface CodeEditorExpected { type: string example: ExpectedValueExample autocompleteDataType: AutocompleteDataType } -// TODO: tern uses global variable, maybe there is some workaround +// TODO: Tern uses global variable, maybe there is some workaround const updateMarkings = (editor: CodeMirror.Editor, marking: MarkHelper[]) => { marking.forEach((helper) => helper(editor)) } @@ -229,6 +229,10 @@ export const CodeEditor: FC< }) // mock end + // evaluated start + let content =

undefined
+ // evaluated end + return ( <>
@@ -237,17 +241,19 @@ export const CodeEditor: FC<
预期结构 - {} + + {props.expected.type} +
预期结构 - 示例 - EXPECTED STRUCTURE - EXAMPLE + {props.expected.example}
计算值 - EVALUATED VALUE + {content}
diff --git a/auxiliaries/code-editor/src/CodeEditor/mock.ts b/auxiliaries/code-editor/src/CodeEditor/mock.ts index 89e03ab..b3f71a3 100644 --- a/auxiliaries/code-editor/src/CodeEditor/mock.ts +++ b/auxiliaries/code-editor/src/CodeEditor/mock.ts @@ -1,15 +1,13 @@ import { AutocompleteDataType } from '@modou/code-editor/CodeEditor/autocomplete/CodeMirrorTernService' -import { - ENTITY_TYPE, - FieldEntityInformation, -} from '@modou/code-editor/CodeEditor/common/editor-config' +import { FieldEntityInformation } from '@modou/code-editor/CodeEditor/common/editor-config' +import { ENTITY_TYPE } from '@modou/code-editor/CodeEditor/entities/DataTree/types' export const mock_dyn_def = { '!name': 'DATA_TREE', Button1: { '!doc': 'Buttons are used to capture user intent and trigger actions based on that intent', - '!url': 'https://docs.appsmith.com/widget-reference/button', + '!url': 'https://docs.__modou__.com/widget-reference/button', isVisible: { '!type': 'bool', '!doc': 'Boolean value indicating if the widget is in visible state', @@ -28,16 +26,16 @@ export const mock_dyn_def = { Input1: { '!doc': 'An input text field is used to capture a users textual input such as their names, numbers, emails etc. Inputs are used in forms and can have custom validations.', - '!url': 'https://docs.appsmith.com/widget-reference/input', + '!url': 'https://docs.__modou__.com/widget-reference/input', text: { '!type': 'string', '!doc': 'The text value of the input', - '!url': 'https://docs.appsmith.com/widget-reference/input', + '!url': 'https://docs.__modou__.com/widget-reference/input', }, inputText: { '!type': 'string', '!doc': 'The unformatted text value of the input', - '!url': 'https://docs.appsmith.com/widget-reference/input', + '!url': 'https://docs.__modou__.com/widget-reference/input', }, isValid: 'bool', isVisible: { @@ -49,12 +47,12 @@ export const mock_dyn_def = { 'Input1.text': { '!type': 'string', '!doc': 'The text value of the input', - '!url': 'https://docs.appsmith.com/widget-reference/input', + '!url': 'https://docs.__modou__.com/widget-reference/input', }, 'Input1.inputText': { '!type': 'string', '!doc': 'The unformatted text value of the input', - '!url': 'https://docs.appsmith.com/widget-reference/input', + '!url': 'https://docs.__modou__.com/widget-reference/input', }, 'Input1.isValid': 'bool', 'Input1.isVisible': { @@ -62,7 +60,7 @@ export const mock_dyn_def = { '!doc': 'Boolean value indicating if the widget is in visible state', }, 'Input1.isDisabled': 'bool', - appsmith: { + __modou__: { user: { email: 'string', workspaceIds: '[string]', @@ -98,7 +96,7 @@ export const mock_dyn_def = { '!doc': "The user's geo location information. Only available when requested", '!url': - 'https://docs.appsmith.com/v/v1.2.1/framework-reference/geolocation', + 'https://docs.__modou__.com/v/v1.2.1/framework-reference/geolocation', getCurrentPosition: 'fn(onSuccess: fn() -> void, onError: fn() -> void, options: object) -> void', watchPosition: 'fn(options: object) -> void', @@ -134,7 +132,7 @@ mock_entityInfo.set('Button1', { type: 'WIDGET', subType: 'BUTTON_WIDGET', }) -mock_entityInfo.set('appsmith', { +mock_entityInfo.set('__modou__', { type: 'APPSMITH', subType: 'APPSMITH', }) @@ -1192,7 +1190,7 @@ export const mock_code_editor_props = { ], }, }, - appsmith: { + __modou__: { user: { email: 'liuleiytu@gmail.com', workspaceIds: ['63a210adcb0c41354d0ff121'], @@ -1213,9 +1211,9 @@ export const mock_code_editor_props = { }, URL: { fullPath: - 'https://dev.appsmith.com/applications/63a210adcb0c41354d0ff122/pages/63a210adcb0c41354d0ff125/edit', - host: 'dev.appsmith.com', - hostname: 'dev.appsmith.com', + 'https://dev.__modou__.com/applications/63a210adcb0c41354d0ff122/pages/63a210adcb0c41354d0ff125/edit', + host: 'dev.__modou__.com', + hostname: 'dev.__modou__.com', queryParams: {}, protocol: 'https:', pathname: @@ -1271,4 +1269,9 @@ export const mock_code_editor_props = { propertyPath: 'text', } as unknown as FieldEntityInformation, blockCompletions: undefined, + expected: { + type: 'string', + example: 'abc', + autocompleteDataType: 'STRING', + }, } diff --git a/auxiliaries/code-editor/src/CodeEditor/reducers/entityReducers/appReducer.ts b/auxiliaries/code-editor/src/CodeEditor/reducers/entityReducers/appReducer.ts new file mode 100644 index 0000000..b834e38 --- /dev/null +++ b/auxiliaries/code-editor/src/CodeEditor/reducers/entityReducers/appReducer.ts @@ -0,0 +1,32 @@ +import { APP_MODE } from '@modou/code-editor/CodeEditor/entities/App' + +export interface AuthUserState { + username: string + email: string + id: string +} +export interface UrlDataState { + queryParams: Record + protocol: string + host: string + hostname: string + port: string + pathname: string + hash: string + fullPath: string +} + +export interface AppStoreState { + transient: Record + persistent: Record +} +export interface AppDataState { + mode?: APP_MODE + user: AuthUserState + URL: UrlDataState + store: AppStoreState + geolocation: { + canBeRequested: boolean + currentPosition?: Partial + } +} diff --git a/auxiliaries/code-editor/src/CodeEditor/reducers/entityReducers/canvasWidgetsReducer.ts b/auxiliaries/code-editor/src/CodeEditor/reducers/entityReducers/canvasWidgetsReducer.ts new file mode 100644 index 0000000..9207820 --- /dev/null +++ b/auxiliaries/code-editor/src/CodeEditor/reducers/entityReducers/canvasWidgetsReducer.ts @@ -0,0 +1,10 @@ +import { WidgetProps } from '@modou/code-editor/CodeEditor/widgets/BaseWidget' + +export type FlattenedWidgetProps = + | (WidgetProps & { + children?: string[] + }) + | orType +export interface CanvasWidgetsReduxState { + [widgetId: string]: FlattenedWidgetProps +} diff --git a/auxiliaries/code-editor/src/CodeEditor/reducers/entityReducers/widgetConfigReducer.ts b/auxiliaries/code-editor/src/CodeEditor/reducers/entityReducers/widgetConfigReducer.ts new file mode 100644 index 0000000..a82c4fc --- /dev/null +++ b/auxiliaries/code-editor/src/CodeEditor/reducers/entityReducers/widgetConfigReducer.ts @@ -0,0 +1,16 @@ +export interface WidgetBlueprint { + view?: Array<{ + type: string + size?: { rows: number; cols: number } + position: { top?: number; left?: number } + props: Record + }> + operations?: any +} +export interface WidgetConfigProps { + rows: number + columns: number + blueprint?: WidgetBlueprint + widgetName: string + enhancements?: Record // TODO(abhinav): SPECIFY TYPES +} diff --git a/auxiliaries/code-editor/src/CodeEditor/reducers/evaluationReducers/formEvaluationReducer.ts b/auxiliaries/code-editor/src/CodeEditor/reducers/evaluationReducers/formEvaluationReducer.ts new file mode 100644 index 0000000..8ab58c1 --- /dev/null +++ b/auxiliaries/code-editor/src/CodeEditor/reducers/evaluationReducers/formEvaluationReducer.ts @@ -0,0 +1,45 @@ +// Type for the object that will store the dynamic values for each component +export interface DynamicValues { + allowedToFetch: boolean + isLoading: boolean + hasStarted: boolean + hasFetchFailed: boolean + data: any + config: DynamicValuesConfig + evaluatedConfig: DynamicValuesConfig + dynamicDependencyPathList?: Set | undefined +} + +export interface DynamicValuesConfig { + url?: string + params: Record +} + +export interface EvaluatedFormConfig { + updateEvaluatedConfig: boolean + paths: string[] + evaluateFormConfigObject: FormConfigEvalObject +} + +export type ConditonalObject = Record + +// Type for the object that will store the evaluation output for each component +export interface ConditionalOutput { + visible?: boolean + enabled?: boolean + fetchDynamicValues?: DynamicValues + conditionals?: ConditonalObject + evaluateFormConfig?: EvaluatedFormConfig + configPropertyPath?: string + staticDependencyPathList?: Set | undefined +} + +export interface FormConfigEvalObject { + [path: string]: { expression: string; output: string } +} + +// Type for the object that will store the eval output for the form +export type FormEvalOutput = Record + +// Type for the object that will store the eval output for the app +export type FormEvaluationState = Record diff --git a/auxiliaries/code-editor/src/CodeEditor/sagas/ActionExecution/ActionExecutionSagas.ts b/auxiliaries/code-editor/src/CodeEditor/sagas/ActionExecution/ActionExecutionSagas.ts new file mode 100644 index 0000000..35106fd --- /dev/null +++ b/auxiliaries/code-editor/src/CodeEditor/sagas/ActionExecution/ActionExecutionSagas.ts @@ -0,0 +1,6 @@ +import { TriggerSource } from '@modou/code-editor/CodeEditor/constants/AppsmithActionConstants/ActionConstants' + +export type TriggerMeta = { + source?: TriggerSource + triggerPropertyName?: string +} diff --git a/auxiliaries/code-editor/src/CodeEditor/sagas/ActionExecution/NavigateActionSaga.ts b/auxiliaries/code-editor/src/CodeEditor/sagas/ActionExecution/NavigateActionSaga.ts new file mode 100644 index 0000000..4f45c50 --- /dev/null +++ b/auxiliaries/code-editor/src/CodeEditor/sagas/ActionExecution/NavigateActionSaga.ts @@ -0,0 +1,4 @@ +export enum NavigationTargetType { + SAME_WINDOW = 'SAME_WINDOW', + NEW_WINDOW = 'NEW_WINDOW', +} diff --git a/auxiliaries/code-editor/src/CodeEditor/utils/AppsmithConsole.ts b/auxiliaries/code-editor/src/CodeEditor/utils/AppsmithConsole.ts new file mode 100644 index 0000000..c8a46d0 --- /dev/null +++ b/auxiliaries/code-editor/src/CodeEditor/utils/AppsmithConsole.ts @@ -0,0 +1,20 @@ +export const AppsmithConsole = { + addLogs: (...args: any) => { + console.log('AppsmithConsole__addLogs', args) + }, + info: (...args: any) => { + console.log('AppsmithConsole__info', args) + }, + warning: (...args: any) => { + console.log('AppsmithConsole__warning', args) + }, + error: (...args: any) => { + console.log('AppsmithConsole__error', args) + }, + addErrors: (...args: any) => { + console.log('AppsmithConsole__addErrors', args) + }, + deleteErrors: (...args: any) => { + console.log('AppsmithConsole__deleteErrors', args) + }, +} diff --git a/auxiliaries/code-editor/src/CodeEditor/utils/DynamicBindingUtils.ts b/auxiliaries/code-editor/src/CodeEditor/utils/DynamicBindingUtils.ts index 7b8558c..6de1cd9 100644 --- a/auxiliaries/code-editor/src/CodeEditor/utils/DynamicBindingUtils.ts +++ b/auxiliaries/code-editor/src/CodeEditor/utils/DynamicBindingUtils.ts @@ -1,10 +1,35 @@ +import parser from 'fast-xml-parser' +import _, { get, isString, VERSION as lodashVersion } from 'lodash' +import moment from 'moment-timezone' +import forge from 'node-forge' + +import { ViewTypes } from '@modou/code-editor/CodeEditor/components/formControls/utils' import { DATA_BIND_REGEX } from '@modou/code-editor/CodeEditor/constants/bindings' +import { Action } from '@modou/code-editor/CodeEditor/entities/Action' +import { Severity } from '@modou/code-editor/CodeEditor/entities/AppsmithConsole' +import { DataTreeEntity } from '@modou/code-editor/CodeEditor/entities/DataTree/dataTreeFactory' +import { WidgetProps } from '@modou/code-editor/CodeEditor/widgets/BaseWidget' +import { + getEntityNameAndPropertyPath, + isJSAction, + isTrueObject, +} from '@modou/code-editor/CodeEditor/works/Evaluation/evaluationUtils' + +import { Types, getType } from './TypeHelpers' +export type DependencyMap = Record +export type FormEditorConfigs = Record +export type FormSettingsConfigs = Record +export type FormDependencyConfigs = Record +export type FormDatasourceButtonConfigs = Record + +// referencing DATA_BIND_REGEX fails for the value "{{Table1.tableData[Table1.selectedRowIndex]}}" +// if you run it multiple times and don't recreate export const isDynamicValue = (value: string): boolean => DATA_BIND_REGEX.test(value) // {{}}{{}}} -export const getDynamicStringSegments = (dynamicString: string): string[] => { +export function getDynamicStringSegments(dynamicString: string): string[] { let stringSegments = [] const indexOfDoubleParanStart = dynamicString.indexOf('{{') if (indexOfDoubleParanStart === -1) { @@ -42,3 +67,534 @@ export const getDynamicStringSegments = (dynamicString: string): string[] => { } return stringSegments } + +// {{}}{{}}} +export const getDynamicBindings = ( + dynamicString: string, + entity?: DataTreeEntity, +): { stringSegments: string[]; jsSnippets: string[] } => { + // Protect against bad string parse + if (!dynamicString || !_.isString(dynamicString)) { + return { stringSegments: [], jsSnippets: [] } + } + const sanitisedString = dynamicString.trim() + let stringSegments, paths: any + if (entity && isJSAction(entity)) { + stringSegments = [sanitisedString] + paths = [sanitisedString] + } else { + // Get the {{binding}} bound values + stringSegments = getDynamicStringSegments(sanitisedString) + // Get the "binding" path values + paths = stringSegments.map((segment) => { + const length = segment.length + const matches = isDynamicValue(segment) + if (matches) { + return segment.substring(2, length - 2) + } + return '' + }) + } + return { stringSegments, jsSnippets: paths } +} + +export const combineDynamicBindings = ( + jsSnippets: string[], + stringSegments: string[], +) => { + return stringSegments + .map((segment, index) => { + if (jsSnippets[index] && jsSnippets[index].length > 0) { + return jsSnippets[index] + } else { + return `'${segment}'` + } + }) + .join(' + ') +} + +export enum EvalErrorTypes { + CYCLICAL_DEPENDENCY_ERROR = 'CYCLICAL_DEPENDENCY_ERROR', + EVAL_PROPERTY_ERROR = 'EVAL_PROPERTY_ERROR', + EVAL_TREE_ERROR = 'EVAL_TREE_ERROR', + UNKNOWN_ERROR = 'UNKNOWN_ERROR', + BAD_UNEVAL_TREE_ERROR = 'BAD_UNEVAL_TREE_ERROR', + PARSE_JS_ERROR = 'PARSE_JS_ERROR', + EXTRACT_DEPENDENCY_ERROR = 'EXTRACT_DEPENDENCY_ERROR', + CLONE_ERROR = 'CLONE_ERROR', +} + +export interface EvalError { + type: EvalErrorTypes + message: string + context?: Record +} + +export enum EVAL_WORKER_ACTIONS { + SETUP = 'SETUP', + EVAL_TREE = 'EVAL_TREE', + EVAL_ACTION_BINDINGS = 'EVAL_ACTION_BINDINGS', + EVAL_TRIGGER = 'EVAL_TRIGGER', + PROCESS_TRIGGER = 'PROCESS_TRIGGER', + CLEAR_CACHE = 'CLEAR_CACHE', + VALIDATE_PROPERTY = 'VALIDATE_PROPERTY', + UNDO = 'undo', + REDO = 'redo', + EVAL_EXPRESSION = 'EVAL_EXPRESSION', + UPDATE_REPLAY_OBJECT = 'UPDATE_REPLAY_OBJECT', + SET_EVALUATION_VERSION = 'SET_EVALUATION_VERSION', + INIT_FORM_EVAL = 'INIT_FORM_EVAL', + EXECUTE_SYNC_JS = 'EXECUTE_SYNC_JS', + LINT_TREE = 'LINT_TREE', +} + +export interface ExtraLibrary { + version: string + docsURL: string + displayName: string + accessor: string + lib: any +} + +export const extraLibraries: ExtraLibrary[] = [ + { + accessor: '_', + lib: _, + version: lodashVersion, + docsURL: `https://lodash.com/docs/${lodashVersion}`, + displayName: 'lodash', + }, + { + accessor: 'moment', + lib: moment, + version: moment.version, + docsURL: `https://momentjs.com/docs/`, + displayName: 'moment', + }, + { + accessor: 'xmlParser', + lib: parser, + version: '3.17.5', + docsURL: 'https://github.com/NaturalIntelligence/fast-xml-parser', + displayName: 'xmlParser', + }, + { + accessor: 'forge', + // We are removing some functionalities of node-forge because they wont + // work in the worker thread + lib: _.omit(forge, ['tls', 'http', 'xhr', 'socket', 'task']), + version: '1.3.0', + docsURL: 'https://github.com/digitalbazaar/forge', + displayName: 'forge', + }, +] +/** + * creates dynamic list of constants based on + * current list of extra libraries i.e lodash("_"), moment etc + * to be used in widget and entity name validations + */ +export const extraLibrariesNames = extraLibraries.reduce( + (prev: Record, curr) => { + prev[curr.accessor] = curr.accessor + return prev + }, + {}, +) + +export interface DynamicPath { + key: string + value?: string +} + +export interface WidgetDynamicPathListProps { + dynamicBindingPathList?: DynamicPath[] + dynamicTriggerPathList?: DynamicPath[] + dynamicPropertyPathList?: DynamicPath[] +} + +export interface EntityWithBindings { + dynamicBindingPathList?: DynamicPath[] +} + +export const getEntityDynamicBindingPathList = ( + entity: EntityWithBindings, +): DynamicPath[] => { + if ( + entity?.dynamicBindingPathList && + Array.isArray(entity.dynamicBindingPathList) + ) { + return [...entity.dynamicBindingPathList] + } + return [] +} + +export const isPathADynamicBinding = ( + entity: EntityWithBindings, + path: string, +): boolean => { + if ( + entity?.dynamicBindingPathList && + Array.isArray(entity.dynamicBindingPathList) + ) { + return _.find(entity.dynamicBindingPathList, { key: path }) !== undefined + } + return false +} +/** + * Get property path from full property path + * Input: "Table1.meta.searchText" => Output: "meta.searchText" + * @param {string} fullPropertyPath + * @return {*} + */ +export const getPropertyPath = (fullPropertyPath: string) => { + return fullPropertyPath.substring(fullPropertyPath.indexOf('.') + 1) +} + +export const getWidgetDynamicTriggerPathList = ( + widget: WidgetProps, +): DynamicPath[] => { + if ( + widget?.dynamicTriggerPathList && + Array.isArray(widget.dynamicTriggerPathList) + ) { + return [...widget.dynamicTriggerPathList] + } + return [] +} + +export const isPathDynamicTrigger = ( + widget: WidgetProps, + path: string, +): boolean => { + if ( + widget?.dynamicTriggerPathList && + Array.isArray(widget.dynamicTriggerPathList) + ) { + return _.find(widget.dynamicTriggerPathList, { key: path }) !== undefined + } + return false +} + +export const getWidgetDynamicPropertyPathList = ( + widget: WidgetProps, +): DynamicPath[] => { + if ( + widget?.dynamicPropertyPathList && + Array.isArray(widget.dynamicPropertyPathList) + ) { + return [...widget.dynamicPropertyPathList] + } + return [] +} + +export const isPathDynamicProperty = ( + widget: WidgetProps, + path: string, +): boolean => { + if ( + widget?.dynamicPropertyPathList && + Array.isArray(widget.dynamicPropertyPathList) + ) { + return _.find(widget.dynamicPropertyPathList, { key: path }) !== undefined + } + return false +} + +export const THEME_BINDING_REGEX = /{{.*appsmith\.theme\..*}}/ + +export const isThemeBoundProperty = ( + widget: WidgetProps, + path: string, +): boolean => { + return widget?.[path] && THEME_BINDING_REGEX.test(widget[path]) +} + +export const unsafeFunctionForEval = [ + 'XMLHttpRequest', + 'setInterval', + 'clearInterval', + 'setImmediate', + 'importScripts', + 'Navigator', +] + +export const isChildPropertyPath = ( + parentPropertyPath: string, + childPropertyPath: string, +): boolean => { + return ( + parentPropertyPath === childPropertyPath || + childPropertyPath.startsWith(`${parentPropertyPath}.`) || + childPropertyPath.startsWith(`${parentPropertyPath}[`) + ) +} + +/** + * Paths set via evaluator on entities + * During evaluation, the evaluator will set various data points + * on the entity objects to describe their state while evaluating. + * This information can be found on the following paths + * These paths are meant to be objects with + * information about the properties in + * a single place + * + * Stored in a flattened object like + * widget.__evaluation__.errors.primaryColumns.customColumn.computedValue = [...] + **/ +export const EVALUATION_PATH = '__evaluation__' +export const EVAL_ERROR_PATH = `${EVALUATION_PATH}.errors` +export const EVAL_VALUE_PATH = `${EVALUATION_PATH}.evaluatedValues` + +/** + * non-populated object + { + __evaluation__:{ + evaluatedValues:{ + primaryColumns: [...], + primaryColumns.status: {...}, + primaryColumns.action: {...} + } + } + } + + * Populated Object + { + __evaluation__:{ + evaluatedValues:{ + primaryColumns: { + status: [...], + action:[...] + } + } + } + } + + */ +const getNestedEvalPath = ( + fullPropertyPath: string, + pathType: string, + fullPath = true, + isPopulated = false, +) => { + const { entityName, propertyPath } = + getEntityNameAndPropertyPath(fullPropertyPath) + const nestedPath = isPopulated + ? `${pathType}.${propertyPath}` + : `${pathType}.['${propertyPath}']` + + if (fullPath) { + return `${entityName}.${nestedPath}` + } + return nestedPath +} + +export const getEvalErrorPath = ( + fullPropertyPath: string, + options = { + fullPath: true, + isPopulated: false, + }, +) => { + return getNestedEvalPath( + fullPropertyPath, + EVAL_ERROR_PATH, + options.fullPath, + options.isPopulated, + ) +} + +export const getEvalValuePath = ( + fullPropertyPath: string, + options = { + fullPath: true, + isPopulated: false, + }, +) => { + return getNestedEvalPath( + fullPropertyPath, + EVAL_VALUE_PATH, + options.fullPath, + options.isPopulated, + ) +} + +export enum PropertyEvaluationErrorType { + VALIDATION = 'VALIDATION', + PARSE = 'PARSE', + LINT = 'LINT', +} + +export interface DataTreeError { + raw: string + errorMessage: string + severity: Severity.WARNING | Severity.ERROR +} + +export interface EvaluationError extends DataTreeError { + errorType: + | PropertyEvaluationErrorType.PARSE + | PropertyEvaluationErrorType.VALIDATION + originalBinding?: string +} + +export interface LintError extends DataTreeError { + errorType: PropertyEvaluationErrorType.LINT + errorSegment: string + originalBinding: string + variables: Array + code: string + line: number + ch: number +} + +export interface DataTreeEvaluationProps { + __evaluation__?: { + errors: Record + evaluatedValues?: Record + } +} + +export const PropertyEvalErrorTypeDebugMessage: Record< + PropertyEvaluationErrorType, + (propertyPath: string) => string +> = { + [PropertyEvaluationErrorType.VALIDATION]: (propertyPath: string) => + `The value at ${propertyPath} is invalid`, + [PropertyEvaluationErrorType.PARSE]: () => `Could not parse the binding`, + [PropertyEvaluationErrorType.LINT]: () => `Errors found while evaluating`, +} + +// this variable temporarily holds dynamic paths generated by the recursive function (getDynamicValuePaths - Line 468). +let temporaryDynamicPathStore: DynamicPath[] = [] + +// recursive function to get full key path of any object that has dynamic bindings. +const getDynamicValuePaths = (val: any, parentPath: string) => { + if (isString(val) && isDynamicValue(val)) { + return temporaryDynamicPathStore.push({ key: `${parentPath}` }) + } + + if (Array.isArray(val)) { + val.forEach((obj, index) => { + return getDynamicValuePaths(obj, `${parentPath}[${index}]`) + }) + } + + if (isTrueObject(val)) { + Object.entries(val).forEach(([key, value]) => { + getDynamicValuePaths(value, `${parentPath}.${key}`) + }) + } +} + +export function getDynamicBindingsChangesSaga( + action: Action, + value: unknown, + field: string, +) { + const bindingField = field.replace('actionConfiguration.', '') + // we listen to any viewType changes. + const viewType = field.endsWith('.viewType') + let dynamicBindings: DynamicPath[] = action.dynamicBindingPathList || [] + + if (field.endsWith('.jsonData') || field.endsWith('.componentData')) { + return dynamicBindings + } + + if ( + action.datasource && + 'datasourceConfiguration' in action.datasource && + field === 'datasource' + ) { + // only the datasource.datasourceConfiguration.url can be a dynamic field + dynamicBindings = dynamicBindings.filter( + (binding) => binding.key !== 'datasourceUrl', + ) + const datasourceUrl = action.datasource.datasourceConfiguration.url + isDynamicValue(datasourceUrl) && + dynamicBindings.push({ key: 'datasourceUrl' }) + return dynamicBindings + } + + // When a key-value pair is added or deleted from a fieldArray + // Value is an Array representing the new fieldArray. + + if (Array.isArray(value)) { + // first we clear the dynamic bindings of any paths that is a child of the current path. + dynamicBindings = dynamicBindings.filter( + (binding) => !isChildPropertyPath(bindingField, binding.key), + ) + + // then we recursively go through the value and find paths with dynamic bindings + temporaryDynamicPathStore = [] + if (value) { + getDynamicValuePaths(value, bindingField) + } + if (!!temporaryDynamicPathStore && temporaryDynamicPathStore.length > 0) { + dynamicBindings = [...dynamicBindings, ...temporaryDynamicPathStore] + } + } else if (getType(value) === Types.OBJECT) { + dynamicBindings = dynamicBindings.filter((dynamicPath) => { + if (isChildPropertyPath(bindingField, dynamicPath.key)) { + const childPropertyValue = _.get(value, dynamicPath.key) + return isDynamicValue(childPropertyValue) + } + return false + }) + } else if (typeof value === 'string') { + const fieldExists = _.some(dynamicBindings, { key: bindingField }) + + const isDynamic = isDynamicValue(value) + + if (!isDynamic && fieldExists) { + dynamicBindings = dynamicBindings.filter((d) => d.key !== bindingField) + } + if (isDynamic && !fieldExists) { + dynamicBindings.push({ key: bindingField }) + } + } + + // the reason this is done is to change the dynamicBindingsPathlist of a + // component when a user toggles the form control + // from component mode to json mode and vice versa. + + // when in json mode, we want to get rid of all the existing componentData paths + // and replace it with a single path for the json mode + // for example: [{key: 'formData.sortBy.data[0].column'}, + // {key: 'formData.sortBy.data[1].column'}] will be replaced with just this [{key: 'formData.sortBy.data'}] + + // when in component mode, we want to first remove all the paths for json mode and + // get back all the paths in the componentData that have dynamic bindings and add them to + // the the dynamic bindings pathlist. + // for example: [{key: 'formData.sortBy.data'}] will be replaced with this + // [{key: 'formData.sortBy.data[0].column'}, {key: 'formData.sortBy.data[1].column'}] + + // if the currently changing field is a component's view type + if (viewType) { + const dataBindingField = bindingField.replace('.viewType', '.data') + // then we filter the field of any paths that includes the binding fields + dynamicBindings = dynamicBindings.filter( + (dynamicPath) => !dynamicPath?.key?.includes(dataBindingField), + ) + + // if the value of the viewType is of json and, we push in the field + if (value === ViewTypes.JSON) { + const jsonFieldPath = field.replace('.viewType', '.jsonData') + const jsonFieldValue = get(action, jsonFieldPath) + if (isDynamicValue(jsonFieldValue)) { + dynamicBindings.push({ key: dataBindingField }) + } + } else if (value === ViewTypes.COMPONENT) { + const componentFieldPath = field.replace('.viewType', '.componentData') + const componentFieldValue = get(action, componentFieldPath) + temporaryDynamicPathStore = [] + + if (componentFieldValue) { + getDynamicValuePaths(componentFieldValue, dataBindingField) + } + if (!!temporaryDynamicPathStore && temporaryDynamicPathStore.length > 0) { + dynamicBindings = [...dynamicBindings, ...temporaryDynamicPathStore] + } + } + } + return dynamicBindings +} diff --git a/auxiliaries/code-editor/src/CodeEditor/utils/JSPaneUtils.tsx b/auxiliaries/code-editor/src/CodeEditor/utils/JSPaneUtils.tsx new file mode 100644 index 0000000..4123f38 --- /dev/null +++ b/auxiliaries/code-editor/src/CodeEditor/utils/JSPaneUtils.tsx @@ -0,0 +1,21 @@ +// check difference for after body change and parsing +import { Variable } from '@modou/code-editor/CodeEditor/entities/JSCollection' + +export interface ParsedJSSubAction { + name: string + body: string + arguments: Variable[] + isAsync: boolean + // parsedFunction - used only to determine if function is async + parsedFunction?: () => unknown +} + +export interface ParsedBody { + actions: ParsedJSSubAction[] + variables: Variable[] +} + +export interface JSUpdate { + id: string + parsedBody: ParsedBody | undefined +} diff --git a/auxiliaries/code-editor/src/CodeEditor/utils/WidgetFactory.tsx b/auxiliaries/code-editor/src/CodeEditor/utils/WidgetFactory.tsx new file mode 100644 index 0000000..e341f23 --- /dev/null +++ b/auxiliaries/code-editor/src/CodeEditor/utils/WidgetFactory.tsx @@ -0,0 +1,9 @@ +type WidgetDerivedPropertyType = any +export type WidgetTypeConfigMap = Record< + string, + { + defaultProperties: Record + metaProperties: Record + derivedProperties: WidgetDerivedPropertyType + } +> diff --git a/auxiliaries/code-editor/src/CodeEditor/utils/validation/common.ts b/auxiliaries/code-editor/src/CodeEditor/utils/validation/common.ts index 66f9772..8cdc302 100644 --- a/auxiliaries/code-editor/src/CodeEditor/utils/validation/common.ts +++ b/auxiliaries/code-editor/src/CodeEditor/utils/validation/common.ts @@ -3,4 +3,4 @@ export type ExpectedValueExample = | number | boolean | Record - | Array + | unknown[] diff --git a/auxiliaries/code-editor/src/CodeEditor/utils/validation/getIsSafeURL.ts b/auxiliaries/code-editor/src/CodeEditor/utils/validation/getIsSafeURL.ts new file mode 100644 index 0000000..cf23f34 --- /dev/null +++ b/auxiliaries/code-editor/src/CodeEditor/utils/validation/getIsSafeURL.ts @@ -0,0 +1,39 @@ +/** + * REF: https://github.com/angular/angular/blob/master/packages/core/src/sanitization/url_sanitizer.ts + * A pattern that recognizes a commonly useful subset of URLs that are safe. + * + * This regular expression matches a subset of URLs that will not cause script + * execution if used in URL context within a HTML document. Specifically, this + * regular expression matches if (comment from here on and regex copied from + * Soy's EscapingConventions): + * (1) Either an allowed protocol (http, https, mailto or ftp). + * (2) or no protocol. A protocol must be followed by a colon. The below + * allows that by allowing colons only after one of the characters [/?#]. + * A colon after a hash (#) must be in the fragment. + * Otherwise, a colon after a (?) must be in a query. + * Otherwise, a colon after a single solidus (/) must be in a path. + * Otherwise, a colon after a double solidus (//) must be in the authority + * (before port). + * + * The pattern disallows &, used in HTML entity declarations before + * one of the characters in [/?#]. This disallows HTML entities used in the + * protocol name, which should never happen, e.g. "http" for "http". + * It also disallows HTML entities in the first path part of a relative path, + * e.g. "foo<bar/baz". Our existing escaping functions should not produce + * that. More importantly, it disallows masking of a colon, + * e.g. "javascript:...". + * + * This regular expression was taken from the Closure sanitization library. + */ +const SAFE_URL_PATTERN = + /^(?:(?:https?|mailto|ftp|tel|file|sms):|[^&:/?#]*(?:[/?#]|$))/gi + +/** A pattern that matches safe data URLs. Only matches image, video and audio types. */ +const DATA_URL_PATTERN = + /^data:(?:image\/(?:bmp|gif|jpeg|jpg|png|tiff|webp)|video\/(?:mpeg|mp4|ogg|webm)|audio\/(?:mp3|oga|ogg|opus));base64,[a-z0-9+\/]+=*$/i + +const getIsSafeURL = (value: any) => + typeof value === 'string' && + (value.match(SAFE_URL_PATTERN) ?? value.match(DATA_URL_PATTERN)) + +export default getIsSafeURL diff --git a/auxiliaries/code-editor/src/CodeEditor/widgets/BaseWidget.ts b/auxiliaries/code-editor/src/CodeEditor/widgets/BaseWidget.ts new file mode 100644 index 0000000..19d6a1d --- /dev/null +++ b/auxiliaries/code-editor/src/CodeEditor/widgets/BaseWidget.ts @@ -0,0 +1,58 @@ +import { + RenderMode, + WidgetType, +} from '@modou/code-editor/CodeEditor/constants/WidgetConstants' +import { DataTreeWidget } from '@modou/code-editor/CodeEditor/entities/DataTree/dataTreeFactory' +import { + DataTreeEvaluationProps, + WidgetDynamicPathListProps, +} from '@modou/code-editor/CodeEditor/utils/DynamicBindingUtils' + +export interface WidgetRowCols { + leftColumn: number + rightColumn: number + topRow: number + bottomRow: number + minHeight?: number // Required to reduce the size of CanvasWidgets. + height?: number +} +export interface WidgetPositionProps extends WidgetRowCols { + parentColumnSpace: number + parentRowSpace: number + // The detachFromLayout flag tells use about the following properties when enabled + // 1) Widget does not drag/resize + // 2) Widget CAN (but not neccessarily) be a dropTarget + // Examples: MainContainer is detached from layout, + // MODAL_WIDGET is also detached from layout. + detachFromLayout?: boolean + noContainerOffset?: boolean // This won't offset the child in parent +} +export interface WidgetBaseProps { + widgetId: string + type: WidgetType + widgetName: string + parentId?: string + renderMode: RenderMode + version: number + childWidgets?: DataTreeWidget[] +} +export interface WidgetDisplayProps { + // TODO: Some of these props are mandatory + isVisible?: boolean + isLoading: boolean + isDisabled?: boolean + backgroundColor?: string + animateLoading?: boolean +} +export interface WidgetDataProps + extends WidgetBaseProps, + WidgetPositionProps, + WidgetDisplayProps {} +export interface WidgetProps + extends WidgetDataProps, + WidgetDynamicPathListProps, + DataTreeEvaluationProps { + key?: string + isDefaultClickDisabled?: boolean + [key: string]: any +} diff --git a/auxiliaries/code-editor/src/CodeEditor/works/Evaluation/Actions.ts b/auxiliaries/code-editor/src/CodeEditor/works/Evaluation/Actions.ts new file mode 100644 index 0000000..e012ac5 --- /dev/null +++ b/auxiliaries/code-editor/src/CodeEditor/works/Evaluation/Actions.ts @@ -0,0 +1,365 @@ +import { klona } from 'klona/full' +import _, { uniqueId } from 'lodash' + +import { DataTree } from '@modou/code-editor/CodeEditor/common/editor-config' +import { EventType } from '@modou/code-editor/CodeEditor/constants/AppsmithActionConstants/ActionConstants' +import { + ActionDescription, + ActionTriggerType, +} from '@modou/code-editor/CodeEditor/entities/DataTree/actionTriggers' +import { DataTreeEntity } from '@modou/code-editor/CodeEditor/entities/DataTree/dataTreeFactory' +import { promisifyAction } from '@modou/code-editor/CodeEditor/works/Evaluation/PromisifyAction' +import { + isAction, + isAppsmithEntity, + isTrueObject, +} from '@modou/code-editor/CodeEditor/works/Evaluation/evaluationUtils' + +import { NavigationTargetType } from '../../sagas/ActionExecution/NavigateActionSaga' + +type ActionDescriptionWithExecutionType = ActionDescription & { + executionType: ExecutionType +} +type ActionDispatcherWithExecutionType = ( + ...args: any[] +) => ActionDescriptionWithExecutionType + +enum ExecutionType { + PROMISE = 'PROMISE', + TRIGGER = 'TRIGGER', +} + +export const DATA_TREE_FUNCTIONS: Record< + string, + | ActionDispatcherWithExecutionType + | { + qualifier: (entity: DataTreeEntity) => boolean + func: (entity: DataTreeEntity) => ActionDispatcherWithExecutionType + path?: string + } +> = { + navigateTo: function ( + pageNameOrUrl: string, + params: Record, + target?: NavigationTargetType, + ) { + return { + type: ActionTriggerType.NAVIGATE_TO, + payload: { pageNameOrUrl, params, target }, + executionType: ExecutionType.PROMISE, + } + }, + showAlert: function ( + message: string, + style: 'info' | 'success' | 'warning' | 'error' | 'default', + ) { + return { + type: ActionTriggerType.SHOW_ALERT, + payload: { message, style }, + executionType: ExecutionType.PROMISE, + } + }, + showModal: function (modalName: string) { + return { + type: ActionTriggerType.SHOW_MODAL_BY_NAME, + payload: { modalName }, + executionType: ExecutionType.PROMISE, + } + }, + closeModal: function (modalName: string) { + return { + type: ActionTriggerType.CLOSE_MODAL, + payload: { modalName }, + executionType: ExecutionType.PROMISE, + } + }, + storeValue: function (key: string, value: string, persist = true) { + // momentarily store this value in local state to support loops + _.set(self, ['appsmith', 'store', key], value) + return { + type: ActionTriggerType.STORE_VALUE, + payload: { + key, + value, + persist, + uniqueActionRequestId: uniqueId('store_value_id_'), + }, + executionType: ExecutionType.PROMISE, + } + }, + removeValue: function (key: string) { + return { + type: ActionTriggerType.REMOVE_VALUE, + payload: { key }, + executionType: ExecutionType.PROMISE, + } + }, + clearStore: function () { + return { + type: ActionTriggerType.CLEAR_STORE, + executionType: ExecutionType.PROMISE, + payload: null, + } + }, + download: function (data: string, name: string, type: string) { + return { + type: ActionTriggerType.DOWNLOAD, + payload: { data, name, type }, + executionType: ExecutionType.PROMISE, + } + }, + copyToClipboard: function ( + data: string, + options?: { debug?: boolean; format?: string }, + ) { + return { + type: ActionTriggerType.COPY_TO_CLIPBOARD, + payload: { + data, + options: { debug: options?.debug, format: options?.format }, + }, + executionType: ExecutionType.PROMISE, + } + }, + resetWidget: function (widgetName: string, resetChildren = true) { + return { + type: ActionTriggerType.RESET_WIDGET_META_RECURSIVE_BY_NAME, + payload: { widgetName, resetChildren }, + executionType: ExecutionType.PROMISE, + } + }, + run: { + qualifier: (entity) => isAction(entity), + func: (entity) => + function ( + onSuccessOrParams?: () => unknown | Record, + onError?: () => unknown, + params = {}, + ): ActionDescriptionWithExecutionType { + const noArguments = + !onSuccessOrParams && !onError && isTrueObject(params) + const isNewSignature = noArguments || isTrueObject(onSuccessOrParams) + + const actionParams = isTrueObject(onSuccessOrParams) + ? onSuccessOrParams + : params + + if (isNewSignature) { + return { + type: ActionTriggerType.RUN_PLUGIN_ACTION, + payload: { + actionId: isAction(entity) ? entity.actionId : '', + params: actionParams, + }, + executionType: ExecutionType.PROMISE, + } + } + // Backwards compatibility + return { + type: ActionTriggerType.RUN_PLUGIN_ACTION, + payload: { + actionId: isAction(entity) ? entity.actionId : '', + onSuccess: onSuccessOrParams + ? onSuccessOrParams.toString() + : undefined, + onError: onError ? onError.toString() : undefined, + params: actionParams, + }, + executionType: ExecutionType.TRIGGER, + } + }, + }, + clear: { + qualifier: (entity) => isAction(entity), + func: (entity) => + function () { + return { + type: ActionTriggerType.CLEAR_PLUGIN_ACTION, + payload: { + actionId: isAction(entity) ? entity.actionId : '', + }, + executionType: ExecutionType.PROMISE, + } + }, + }, + setInterval: function (callback: Function, interval: number, id?: string) { + return { + type: ActionTriggerType.SET_INTERVAL, + payload: { + callback: callback.toString(), + interval, + id, + }, + executionType: ExecutionType.TRIGGER, + } + }, + clearInterval: function (id: string) { + return { + type: ActionTriggerType.CLEAR_INTERVAL, + payload: { + id, + }, + executionType: ExecutionType.TRIGGER, + } + }, + getGeoLocation: { + qualifier: (entity) => isAppsmithEntity(entity), + path: 'appsmith.geolocation.getCurrentPosition', + func: () => + function ( + successCallback?: () => unknown, + errorCallback?: () => unknown, + options?: { + maximumAge?: number + timeout?: number + enableHighAccuracy?: boolean + }, + ) { + return { + type: ActionTriggerType.GET_CURRENT_LOCATION, + payload: { + options, + onError: errorCallback + ? `{{${errorCallback.toString()}}}` + : undefined, + onSuccess: successCallback + ? `{{${successCallback.toString()}}}` + : undefined, + }, + executionType: + errorCallback ?? successCallback + ? ExecutionType.TRIGGER + : ExecutionType.PROMISE, + } + }, + }, + watchGeoLocation: { + qualifier: (entity) => isAppsmithEntity(entity), + path: 'appsmith.geolocation.watchPosition', + func: () => + function ( + onSuccessCallback?: Function, + onErrorCallback?: Function, + options?: { + maximumAge?: number + timeout?: number + enableHighAccuracy?: boolean + }, + ) { + return { + type: ActionTriggerType.WATCH_CURRENT_LOCATION, + payload: { + options, + onSuccess: onSuccessCallback + ? `{{${onSuccessCallback.toString()}}}` + : undefined, + onError: onErrorCallback + ? `{{${onErrorCallback.toString()}}}` + : undefined, + }, + executionType: ExecutionType.TRIGGER, + } + }, + }, + stopWatchGeoLocation: { + qualifier: (entity) => isAppsmithEntity(entity), + path: 'appsmith.geolocation.clearWatch', + func: () => + function () { + return { + type: ActionTriggerType.STOP_WATCHING_CURRENT_LOCATION, + payload: {}, + executionType: ExecutionType.PROMISE, + } + }, + }, + postWindowMessage: function ( + message: unknown, + source: string, + targetOrigin: string, + ) { + return { + type: ActionTriggerType.POST_MESSAGE, + payload: { + message, + source, + targetOrigin, + }, + executionType: ExecutionType.TRIGGER, + } + }, +} +export const enhanceDataTreeWithFunctions = ( + dataTree: Readonly, + requestId = '', + // Whether not to add functions like "run", "clear" to entity + skipEntityFunctions = false, + eventType?: EventType, +): DataTree => { + const clonedDT = klona(dataTree) + self.TRIGGER_COLLECTOR = [] + Object.entries(DATA_TREE_FUNCTIONS).forEach(([name, funcOrFuncCreator]) => { + if ( + typeof funcOrFuncCreator === 'object' && + 'qualifier' in funcOrFuncCreator + ) { + !skipEntityFunctions && + Object.entries(dataTree).forEach(([entityName, entity]) => { + if (funcOrFuncCreator.qualifier(entity)) { + const func = funcOrFuncCreator.func(entity) + const funcName = `${ + funcOrFuncCreator.path ?? `${entityName}.${name}` + }` + _.set( + clonedDT, + funcName, + pusher.bind( + { + TRIGGER_COLLECTOR: self.TRIGGER_COLLECTOR, + REQUEST_ID: requestId, + EVENT_TYPE: eventType, + }, + func, + ), + ) + } + }) + } else { + _.set( + clonedDT, + name, + pusher.bind( + { + TRIGGER_COLLECTOR: self.TRIGGER_COLLECTOR, + REQUEST_ID: requestId, + }, + funcOrFuncCreator, + ), + ) + } + }) + + return clonedDT +} +export const pusher = function ( + this: { + TRIGGER_COLLECTOR: ActionDescription[] + REQUEST_ID: string + EVENT_TYPE?: EventType + }, + action: ActionDispatcherWithExecutionType, + ...args: any[] +) { + const actionDescription = action(...args) + const { executionType, payload, type } = actionDescription + const actionPayload = { + type, + payload, + } as ActionDescription + + if (executionType && executionType === ExecutionType.TRIGGER) { + this.TRIGGER_COLLECTOR.push(actionPayload) + } else { + return promisifyAction(this.REQUEST_ID, actionPayload, this.EVENT_TYPE) + } +} diff --git a/auxiliaries/code-editor/src/CodeEditor/works/Evaluation/HTTPRequestOverride.ts b/auxiliaries/code-editor/src/CodeEditor/works/Evaluation/HTTPRequestOverride.ts new file mode 100644 index 0000000..8222f76 --- /dev/null +++ b/auxiliaries/code-editor/src/CodeEditor/works/Evaluation/HTTPRequestOverride.ts @@ -0,0 +1,16 @@ +const _originalFetch = self.fetch + +export function interceptAndOverrideHttpRequest() { + Object.defineProperty(self, 'fetch', { + writable: false, + configurable: false, + value: function (...args: any) { + if (!self.ALLOW_ASYNC) { + self.IS_ASYNC = true + return + } + const request = new Request(args[0], { ...args[1], credentials: 'omit' }) + return _originalFetch(request) + }, + }) +} diff --git a/auxiliaries/code-editor/src/CodeEditor/works/Evaluation/JSObject/index.ts b/auxiliaries/code-editor/src/CodeEditor/works/Evaluation/JSObject/index.ts new file mode 100644 index 0000000..da0a1bd --- /dev/null +++ b/auxiliaries/code-editor/src/CodeEditor/works/Evaluation/JSObject/index.ts @@ -0,0 +1,404 @@ +import { isEmpty, set } from 'lodash' + +import { JsObjectProperty, NodeTypes, parseJSObjectWithAST } from '@modou/ast' +import { DataTree } from '@modou/code-editor/CodeEditor/common/editor-config' +import { APP_MODE } from '@modou/code-editor/CodeEditor/entities/App' +import { + DataTreeAppsmith, + DataTreeJSAction, +} from '@modou/code-editor/CodeEditor/entities/DataTree/dataTreeFactory' +import { EvalErrorTypes } from '@modou/code-editor/CodeEditor/utils/DynamicBindingUtils' +import { + JSUpdate, + ParsedJSSubAction, +} from '@modou/code-editor/CodeEditor/utils/JSPaneUtils' +import { + removeFunctionsAndVariableJSCollection, + updateJSCollectionInUnEvalTree, +} from '@modou/code-editor/CodeEditor/works/Evaluation/JSObject/utils' +import evaluateSync, { + isFunctionAsync, +} from '@modou/code-editor/CodeEditor/works/Evaluation/evaluate' +import { + getEntityNameAndPropertyPath, + isJSAction, +} from '@modou/code-editor/CodeEditor/works/Evaluation/evaluationUtils' +import DataTreeEvaluator from '@modou/code-editor/CodeEditor/works/common/DataTreeEvaluator' +import { + DataTreeDiff, + DataTreeDiffEvent, +} from '@modou/code-editor/CodeEditor/works/common/DataTreeEvaluator/validationUtils' + +interface Variables { + name: string + value: string +} +interface Actions { + name: string + body: string + arguments: Array<{ key: string; value: unknown }> + parsedFunction: any + isAsync: boolean +} +const regex = /^export default[\s]*?({[\s\S]*?})/ +function deleteResolvedFunctionsAndCurrentJSCollectionState( + dataTreeEvalRef: DataTreeEvaluator, + entityName: string, +) { + Reflect.deleteProperty(dataTreeEvalRef.resolvedFunctions, entityName) + Reflect.deleteProperty(dataTreeEvalRef.currentJSCollectionState, entityName) +} +function parseFunction( + parsedElement: JsObjectProperty, + unEvalDataTree: DataTree, + dataTreeEvalRef: DataTreeEvaluator, + entityName: string, + actions: Actions[], +) { + const { result } = evaluateSync( + parsedElement.value, + unEvalDataTree, + {}, + true, + undefined, + undefined, + true, + ) + if (result) { + let params: Array<{ key: string; value: unknown }> = [] + + if (parsedElement.arguments) { + params = parsedElement.arguments.map(({ defaultValue, paramName }) => ({ + key: paramName, + value: defaultValue, + })) + } + + const functionString: string = parsedElement.value + set( + dataTreeEvalRef.resolvedFunctions, + `${entityName}.${parsedElement.key}`, + result, + ) + set( + dataTreeEvalRef.currentJSCollectionState, + `${entityName}.${parsedElement.key}`, + functionString, + ) + actions.push({ + name: parsedElement.key, + body: functionString, + arguments: params, + parsedFunction: result, + isAsync: false, + }) + } +} + +function parseVariables( + variables: Variables[], + parsedElement: JsObjectProperty, + dataTreeEvalRef: DataTreeEvaluator, + entityName: string, +) { + variables.push({ + name: parsedElement.key, + value: parsedElement.value, + }) + set( + dataTreeEvalRef.currentJSCollectionState, + `${entityName}.${parsedElement.key}`, + parsedElement.value, + ) +} + +function getParsedBody( + parsedObject: JsObjectProperty[], + unEvalDataTree: DataTree, + dataTreeEvalRef: DataTreeEvaluator, + entityName: string, +) { + const actions: Actions[] = [] + const variables: Variables[] = [] + for (const parsedElement of parsedObject) { + switch (parsedElement.type) { + case 'literal': + continue + case NodeTypes.ArrowFunctionExpression: + case NodeTypes.FunctionExpression: + parseFunction( + parsedElement, + unEvalDataTree, + dataTreeEvalRef, + entityName, + actions, + ) + break + default: + parseVariables(variables, parsedElement, dataTreeEvalRef, entityName) + } + } + return { + actions, + variables, + } +} +export function saveResolvedFunctionsAndJSUpdates( + dataTreeEvalRef: DataTreeEvaluator, + entity: DataTreeJSAction, + unEvalDataTree: DataTree, + entityName: string, + jsUpdates: Record, +) { + const correctFormat = regex.test(entity.body) + if (correctFormat) { + const body = entity.body.replace(/export default/g, '') + try { + deleteResolvedFunctionsAndCurrentJSCollectionState( + dataTreeEvalRef, + entityName, + ) + const parseStartTime = performance.now() + const parsedObject = parseJSObjectWithAST(body) + const parseEndTime = performance.now() + const JSObjectASTParseTime = parseEndTime - parseStartTime + dataTreeEvalRef.logs.push({ + JSObjectName: entityName, + JSObjectASTParseTime, + }) + + const parsedBody = parsedObject + ? { + body: entity.body, + ...getParsedBody( + parsedObject, + unEvalDataTree, + dataTreeEvalRef, + entityName, + ), + } + : undefined + + set(jsUpdates, `${entityName}`, { + parsedBody, + id: entity.actionId, + }) + } catch (e) { + // if we need to push error as popup in case + } + } else { + const errors = { + type: EvalErrorTypes.PARSE_JS_ERROR, + context: { + entity, + propertyPath: entity.name + '.body', + }, + message: 'Start object with export default', + } + dataTreeEvalRef.errors.push(errors) + } + return jsUpdates +} +export function parseJSUpdates( + jsUpdates: Record, + unEvalDataTree: DataTree, + dataTreeEvalRef: DataTreeEvaluator, +) { + const jsUpdateKeys = Object.keys(jsUpdates) + for (const entityName of jsUpdateKeys) { + const parsedBody = jsUpdates[entityName].parsedBody + if (!parsedBody) continue + parsedBody.actions = parsedBody.actions.map((action) => { + return { + ...action, + isAsync: isFunctionAsync( + action.parsedFunction, + unEvalDataTree, + dataTreeEvalRef.resolvedFunctions, + dataTreeEvalRef.logs, + ), + // parsedFunction - used only to determine if function is async + parsedFunction: undefined, + } as ParsedJSSubAction + }) + } + return jsUpdates +} + +export function getJSEntities(dataTree: DataTree) { + const jsCollections: Record = {} + const dataTreeKeys = Object.keys(dataTree) + for (const key of dataTreeKeys) { + const entity = dataTree[key] + if (isJSAction(entity)) { + jsCollections[entity.name] = entity + } + } + return jsCollections +} + +export function parseJSActions( + dataTreeEvalRef: DataTreeEvaluator, + unEvalDataTree: DataTree, +) { + let jsUpdates: Record = {} + const unEvalDataTreeKeys = Object.keys(unEvalDataTree) + for (const entityName of unEvalDataTreeKeys) { + const entity = unEvalDataTree[entityName] + if (!isJSAction(entity)) { + continue + } + jsUpdates = saveResolvedFunctionsAndJSUpdates( + dataTreeEvalRef, + entity, + unEvalDataTree, + entityName, + jsUpdates, + ) + } + return parseJSUpdates(jsUpdates, unEvalDataTree, dataTreeEvalRef) +} +export function viewModeSaveResolvedFunctions( + dataTreeEvalRef: DataTreeEvaluator, + entity: DataTreeJSAction, + unEvalDataTree: DataTree, + entityName: string, +) { + try { + deleteResolvedFunctionsAndCurrentJSCollectionState( + dataTreeEvalRef, + entityName, + ) + const jsActions = entity.meta + const jsActionList = Object.keys(jsActions) + for (const jsAction of jsActionList) { + const { result } = evaluateSync( + jsActions[jsAction].body, + unEvalDataTree, + {}, + false, + undefined, + undefined, + true, + ) + + if (result) { + const functionString = jsActions[jsAction].body + + set( + dataTreeEvalRef.resolvedFunctions, + `${entityName}.${jsAction}`, + result, + ) + set( + dataTreeEvalRef.currentJSCollectionState, + `${entityName}.${jsAction}`, + functionString, + ) + } + } + } catch (e) { + // if we need to push error as popup in case + } +} +export function parseJSActionsForViewMode( + dataTreeEvalRef: DataTreeEvaluator, + unEvalDataTree: DataTree, +) { + const unEvalDataTreeKeys = Object.keys(unEvalDataTree) + for (const entityName of unEvalDataTreeKeys) { + const entity = unEvalDataTree[entityName] + if (!isJSAction(entity)) { + continue + } + viewModeSaveResolvedFunctions( + dataTreeEvalRef, + entity, + unEvalDataTree, + entityName, + ) + } +} +export function getAppMode(dataTree: DataTree) { + const appsmithObj = dataTree.appsmith as DataTreeAppsmith + return appsmithObj.mode as APP_MODE +} + +export const getUpdatedLocalUnEvalTreeAfterJSUpdates = ( + jsUpdates: Record, + localUnEvalTree: DataTree, +) => { + if (!isEmpty(jsUpdates)) { + Object.keys(jsUpdates).forEach((jsEntity) => { + const entity = localUnEvalTree[jsEntity] + const parsedBody = jsUpdates[jsEntity].parsedBody + if (isJSAction(entity)) { + if (parsedBody) { + // add/delete/update functions from dataTree + localUnEvalTree = updateJSCollectionInUnEvalTree( + parsedBody, + entity, + localUnEvalTree, + ) + } else { + // if parse error remove functions and variables from dataTree + localUnEvalTree = removeFunctionsAndVariableJSCollection( + localUnEvalTree, + entity, + ) + } + } + }) + } + return localUnEvalTree +} +export function parseJSActionsWithDifferences( + dataTreeEvalRef: DataTreeEvaluator, + unEvalDataTree: DataTree, + differences: DataTreeDiff[], +) { + let jsUpdates: Record = {} + for (const diff of differences) { + const payLoadPropertyPath = diff.payload.propertyPath + const { entityName, propertyPath } = + getEntityNameAndPropertyPath(payLoadPropertyPath) + const entity = unEvalDataTree[entityName] + + if (!isJSAction(entity)) { + continue + } + + switch (diff.event) { + case DataTreeDiffEvent.DELETE: + // when JSObject is deleted, we remove it from currentJSCollectionState & resolvedFunctions + deleteResolvedFunctionsAndCurrentJSCollectionState( + dataTreeEvalRef, + payLoadPropertyPath, + ) + break + case DataTreeDiffEvent.EDIT: + if (propertyPath === 'body') { + jsUpdates = saveResolvedFunctionsAndJSUpdates( + dataTreeEvalRef, + entity, + unEvalDataTree, + entityName, + jsUpdates, + ) + } + break + case DataTreeDiffEvent.NEW: + if (propertyPath === '') { + jsUpdates = saveResolvedFunctionsAndJSUpdates( + dataTreeEvalRef, + entity, + unEvalDataTree, + entityName, + jsUpdates, + ) + } + break + } + } + return parseJSUpdates(jsUpdates, unEvalDataTree, dataTreeEvalRef) +} diff --git a/auxiliaries/code-editor/src/CodeEditor/works/Evaluation/JSObject/utils.ts b/auxiliaries/code-editor/src/CodeEditor/works/Evaluation/JSObject/utils.ts new file mode 100644 index 0000000..4d8ea85 --- /dev/null +++ b/auxiliaries/code-editor/src/CodeEditor/works/Evaluation/JSObject/utils.ts @@ -0,0 +1,252 @@ +import { get, set, unset } from 'lodash' + +import { DataTree } from '@modou/code-editor/CodeEditor/common/editor-config' +import { APP_MODE } from '@modou/code-editor/CodeEditor/entities/App' +import { + DataTreeAppsmith, + DataTreeJSAction, +} from '@modou/code-editor/CodeEditor/entities/DataTree/dataTreeFactory' +import { EvaluationSubstitutionType } from '@modou/code-editor/CodeEditor/entities/DataTree/types' +import { + ParsedBody, + ParsedJSSubAction, +} from '@modou/code-editor/CodeEditor/utils/JSPaneUtils' +import { isJSAction } from '@modou/code-editor/CodeEditor/works/Evaluation/evaluationUtils' + +/** + * here we add/remove the properties (variables and actions) which got added/removed from the JSObject parsedBody. + NOTE: For other entity below logic is maintained in DataTreeFactory, for JSObject we handle it inside evaluations + * + * @param parsedBody + * @param jsCollection + * @param unEvalTree + * @returns + */ +export const updateJSCollectionInUnEvalTree = ( + parsedBody: ParsedBody, + jsCollection: DataTreeJSAction, + unEvalTree: DataTree, +) => { + // jsCollection here means unEvalTree JSObject + const modifiedUnEvalTree = unEvalTree + const functionsList: string[] = [] + const varList: string[] = jsCollection.variables + Object.keys(jsCollection.meta).forEach((action) => { + functionsList.push(action) + }) + + const oldConfig = Object.getPrototypeOf(jsCollection) as DataTreeJSAction + + if (parsedBody.actions && parsedBody.actions.length > 0) { + for (let i = 0; i < parsedBody.actions.length; i++) { + const action = parsedBody.actions[i] + if (Reflect.has(jsCollection, action.name)) { + if (jsCollection[action.name] !== action.body) { + const data = get( + modifiedUnEvalTree, + `${jsCollection.name}.${action.name}.data`, + {}, + ) + set( + modifiedUnEvalTree, + `${jsCollection.name}.${action.name}`, + String(action.body), + ) + + set( + modifiedUnEvalTree, + `${jsCollection.name}.${action.name}.data`, + data, + ) + } + } else { + const reactivePaths = oldConfig.reactivePaths + + reactivePaths[action.name] = EvaluationSubstitutionType.SMART_SUBSTITUTE + reactivePaths[`${action.name}.data`] = + EvaluationSubstitutionType.TEMPLATE + + const dynamicBindingPathList = oldConfig.dynamicBindingPathList + dynamicBindingPathList.push({ key: action.name }) + + const dependencyMap = oldConfig.dependencyMap + dependencyMap.body.push(action.name) + + const meta = oldConfig.meta + meta[action.name] = { + arguments: action.arguments, + isAsync: false, + confirmBeforeExecute: false, + body: action.body, + } + + const data = get( + modifiedUnEvalTree, + `${jsCollection.name}.${action.name}.data`, + {}, + ) + set( + modifiedUnEvalTree, + `${jsCollection.name}.${action.name}`, + String(action.body), + ) + set( + modifiedUnEvalTree, + `${jsCollection.name}.${action.name}.data`, + data, + ) + } + } + } + if (functionsList && functionsList.length > 0) { + for (let i = 0; i < functionsList.length; i++) { + const oldActionName = functionsList[i] + const existed = parsedBody.actions.find( + (js: ParsedJSSubAction) => js.name === oldActionName, + ) + if (!existed) { + const reactivePaths = oldConfig.reactivePaths + Reflect.deleteProperty(reactivePaths, oldActionName) + + oldConfig.dynamicBindingPathList = + oldConfig.dynamicBindingPathList.filter( + (path) => path.key !== oldActionName, + ) + + const dependencyMap = oldConfig.dependencyMap.body + const removeIndex = dependencyMap.indexOf(oldActionName) + if (removeIndex > -1) { + oldConfig.dependencyMap.body = dependencyMap.filter( + (item) => item !== oldActionName, + ) + } + const meta = oldConfig.meta + Reflect.deleteProperty(meta, oldActionName) + + unset(modifiedUnEvalTree[jsCollection.name], oldActionName) + unset(modifiedUnEvalTree[jsCollection.name], `${oldActionName}.data`) + } + } + } + if (parsedBody.variables.length) { + for (let i = 0; i < parsedBody.variables.length; i++) { + const newVar = parsedBody.variables[i] + const existedVar = varList.indexOf(newVar.name) + if (existedVar > -1) { + const existedVarVal = jsCollection[newVar.name] + if ( + (!!existedVarVal && existedVarVal.toString()) !== + newVar.value?.toString() || + (!existedVarVal && !!newVar) + ) { + set( + modifiedUnEvalTree, + `${jsCollection.name}.${newVar.name}`, + newVar.value, + ) + } + } else { + varList.push(newVar.name) + const reactivePaths = oldConfig.reactivePaths + reactivePaths[newVar.name] = EvaluationSubstitutionType.SMART_SUBSTITUTE + + const dynamicBindingPathList = oldConfig.dynamicBindingPathList + dynamicBindingPathList.push({ key: newVar.name }) + + set(modifiedUnEvalTree, `${jsCollection.name}.variables`, varList) + set( + modifiedUnEvalTree, + `${jsCollection.name}.${newVar.name}`, + newVar.value, + ) + } + } + let newVarList: string[] = varList + for (let i = 0; i < varList.length; i++) { + const varListItem = varList[i] + const existsInParsed = parsedBody.variables.find( + (item) => item.name === varListItem, + ) + if (!existsInParsed) { + const reactivePaths = oldConfig.reactivePaths + Reflect.deleteProperty(reactivePaths, varListItem) + + oldConfig.dynamicBindingPathList = + oldConfig.dynamicBindingPathList.filter( + (path) => path.key !== varListItem, + ) + + newVarList = newVarList.filter((item) => item !== varListItem) + unset(modifiedUnEvalTree[jsCollection.name], varListItem) + } + } + if (newVarList.length) { + set(modifiedUnEvalTree, `${jsCollection.name}.variables`, newVarList) + } + } + return modifiedUnEvalTree +} + +/** + * When JSObject parseBody is empty we remove all variables and actions from unEvalTree + * this will lead to removal of properties from the dataTree + * @param unEvalTree + * @param entity + * @returns + */ +export const removeFunctionsAndVariableJSCollection = ( + unEvalTree: DataTree, + entity: DataTreeJSAction, +) => { + const oldConfig = Object.getPrototypeOf(entity) as DataTreeJSAction + const modifiedDataTree: DataTree = unEvalTree + const functionsList: string[] = [] + Object.keys(entity.meta).forEach((action) => { + functionsList.push(action) + }) + // removed variables + const varList: string[] = entity.variables + set(modifiedDataTree, `${entity.name}.variables`, []) + for (let i = 0; i < varList.length; i++) { + const varName = varList[i] + unset(modifiedDataTree[entity.name], varName) + } + // remove functions + + const reactivePaths = entity.reactivePaths + const meta = entity.meta + + for (let i = 0; i < functionsList.length; i++) { + const actionName = functionsList[i] + Reflect.deleteProperty(reactivePaths, actionName) + Reflect.deleteProperty(meta, actionName) + unset(modifiedDataTree[entity.name], actionName) + + oldConfig.dynamicBindingPathList = oldConfig.dynamicBindingPathList.filter( + (path: any) => path.key !== actionName, + ) + + entity.dependencyMap.body = entity.dependencyMap.body.filter( + (item: any) => item !== actionName, + ) + } + + return modifiedDataTree +} + +export function isJSObjectFunction( + dataTree: DataTree, + jsObjectName: string, + key: string, +) { + const entity = dataTree[jsObjectName] + if (isJSAction(entity)) { + return Reflect.has(entity.meta, key) + } + return false +} + +export function getAppMode(dataTree: DataTree) { + const appsmithObj = dataTree.appsmith as DataTreeAppsmith + return appsmithObj.mode as APP_MODE +} diff --git a/auxiliaries/code-editor/src/CodeEditor/works/Evaluation/PromisifyAction.ts b/auxiliaries/code-editor/src/CodeEditor/works/Evaluation/PromisifyAction.ts new file mode 100644 index 0000000..c1fffe6 --- /dev/null +++ b/auxiliaries/code-editor/src/CodeEditor/works/Evaluation/PromisifyAction.ts @@ -0,0 +1,131 @@ +import { uniqueId } from 'lodash' + +import { EventType } from '@modou/code-editor/CodeEditor/constants/AppsmithActionConstants/ActionConstants' +import { EVAL_WORKER_ACTIONS } from '@modou/code-editor/CodeEditor/utils/DynamicBindingUtils' +import { + EvalResult, + createGlobalData, +} from '@modou/code-editor/CodeEditor/works/Evaluation/evaluate' +import { dataTreeEvaluator } from '@modou/code-editor/CodeEditor/works/Evaluation/evaluation.worker' + +import { + ActionDescription, + ActionTriggerType, +} from '../../entities/DataTree/actionTriggers' + +const ctx: Worker = self as any + +/* + * We wrap all actions with a promise. The promise will send a message to the main thread + * and wait for a response till it can resolve or reject the promise. This way we can invoke actions + * in the main thread while evaluating in the main thread. In principle, all actions now work as promises. + * + * needs a REQUEST_ID to be passed in to know which request is going on right now + */ + +export const promisifyAction = async ( + workerRequestId: string, + actionDescription: ActionDescription, + eventType?: EventType, +) => { + if (!self.ALLOW_ASYNC) { + /** + * To figure out if any function (JS action) is async, we do a dry run so that we can know if the function + * is using an async action. We set an IS_ASYNC flag to later indicate that a promise was called. + * @link isFunctionAsync + * */ + self.IS_ASYNC = true + throw new Error('Async function called in a sync field') + } + const workerRequestIdCopy = workerRequestId.concat('') + return new Promise((resolve, reject) => { + // We create a new sub request id for each request going on so that we can resolve the correct one later on + const subRequestId = uniqueId(`${workerRequestIdCopy}_`) + // send an execution request to the main thread + const responseData = { + trigger: actionDescription, + errors: [], + subRequestId, + eventType, + } + ctx.postMessage({ + type: EVAL_WORKER_ACTIONS.PROCESS_TRIGGER, + responseData, + requestId: workerRequestIdCopy, + promisified: true, + }) + const processResponse = function (event: MessageEvent) { + const { data, eventType, method, requestId, success } = event.data + // This listener will get all the messages that come to the worker + // we need to find the correct one pertaining to this promise + if ( + method === EVAL_WORKER_ACTIONS.PROCESS_TRIGGER && + requestId === workerRequestIdCopy && + subRequestId === event.data.data.subRequestId + ) { + // If we get a response for this same promise we will resolve or reject it + + // We could not find a data tree evaluator, + // maybe the page changed, or we have a cyclical dependency + if (!dataTreeEvaluator) { + // eslint-disable-next-line prefer-promise-reject-errors + reject('No Data Tree Evaluator found') + } else { + self.ALLOW_ASYNC = true + // Reset the global data with the correct request id for this promise + const globalData = createGlobalData({ + dataTree: dataTreeEvaluator.evalTree, + resolvedFunctions: dataTreeEvaluator.resolvedFunctions, + isTriggerBased: true, + context: { + requestId: workerRequestId, + eventType, + }, + }) + for (const entity in globalData) { + // @ts-expect-error: Types are not available + self[entity] = globalData[entity] + } + + // Resolve or reject the promise + if (success) { + resolve.apply(self, data.resolve) + } else { + reject(data.reason) + } + } + // we are done with this particular promise so remove the event listener + ctx.removeEventListener('message', processResponse) + } + } + ctx.addEventListener('message', processResponse) + }) +} +// To indicate the main thread that the processing of the trigger is done +// we send a finished message +export const completePromise = (requestId: string, result: EvalResult) => { + ctx.postMessage({ + type: EVAL_WORKER_ACTIONS.PROCESS_TRIGGER, + responseData: { + finished: true, + result, + }, + requestId, + promisified: true, + }) +} + +export const confirmationPromise = ( + requestId: string, + func: any, + name: string, + ...args: any[] +) => { + const payload: ActionDescription = { + type: ActionTriggerType.CONFIRMATION_MODAL, + payload: { + funName: name, + }, + } + return promisifyAction(requestId, payload).then(() => func(...args)) +} diff --git a/auxiliaries/code-editor/src/CodeEditor/works/Evaluation/TimeoutOverride.ts b/auxiliaries/code-editor/src/CodeEditor/works/Evaluation/TimeoutOverride.ts new file mode 100644 index 0000000..1a7dead --- /dev/null +++ b/auxiliaries/code-editor/src/CodeEditor/works/Evaluation/TimeoutOverride.ts @@ -0,0 +1,41 @@ +import { createGlobalData } from './evaluate' +import { dataTreeEvaluator } from './evaluation.worker' + +export const _internalSetTimeout = self.setTimeout +export const _internalClearTimeout = self.clearTimeout + +export function overrideTimeout() { + Object.defineProperty(self, 'setTimeout', { + writable: true, + configurable: true, + value: function (cb: (...args: any) => any, delay: number, ...args: any) { + if (!self.ALLOW_ASYNC) { + self.IS_ASYNC = true + throw new Error('Async function called in a sync field') + } + const globalData = createGlobalData({ + dataTree: dataTreeEvaluator?.evalTree || {}, + resolvedFunctions: dataTreeEvaluator?.resolvedFunctions || {}, + isTriggerBased: true, + }) + return _internalSetTimeout( + function (...args: any) { + self.ALLOW_ASYNC = true + Object.assign(self, globalData) + // eslint-disable-next-line n/no-callback-literal + cb(...args) + }, + delay, + ...args, + ) + }, + }) + + Object.defineProperty(self, 'clearTimeout', { + writable: true, + configurable: true, + value: function (timerId: number) { + return _internalClearTimeout(timerId) + }, + }) +} diff --git a/auxiliaries/code-editor/src/CodeEditor/works/Evaluation/UserLog.ts b/auxiliaries/code-editor/src/CodeEditor/works/Evaluation/UserLog.ts new file mode 100644 index 0000000..da8fa30 --- /dev/null +++ b/auxiliaries/code-editor/src/CodeEditor/works/Evaluation/UserLog.ts @@ -0,0 +1,156 @@ +import { klona } from 'klona/full' +import moment from 'moment' +import { nanoid } from 'nanoid' + +import { EventType } from '@modou/code-editor/CodeEditor/constants/AppsmithActionConstants/ActionConstants' +import { TriggerMeta } from '@modou/code-editor/CodeEditor/sagas/ActionExecution/ActionExecutionSagas' +import { + _internalClearTimeout, + _internalSetTimeout, +} from '@modou/code-editor/CodeEditor/works/Evaluation/TimeoutOverride' + +import { LogObject, Methods, Severity } from '../../entities/AppsmithConsole' + +class UserLog { + private readonly flushLogsTimerDelay = 0 + private logs: LogObject[] = [] + private flushLogTimerId: number | undefined + private requestInfo: { + requestId?: string + eventType?: EventType + triggerMeta?: TriggerMeta + } | null = null + + public setCurrentRequestInfo(requestInfo: { + requestId?: string + eventType?: EventType + triggerMeta?: TriggerMeta + }) { + this.requestInfo = requestInfo + } + + private resetFlushTimer() { + if (this.flushLogTimerId) _internalClearTimeout(this.flushLogTimerId) + this.flushLogTimerId = _internalSetTimeout(() => { + const logs = this.flushLogs() + self.postMessage({ + promisified: true, + responseData: { + logs, + eventType: this.requestInfo?.eventType, + triggerMeta: this.requestInfo?.triggerMeta, + }, + requestId: this.requestInfo?.requestId, + }) + }, this.flushLogsTimerDelay) + } + + private saveLog(method: Methods, data: any[]) { + const parsed = this.parseLogs(method, data) + this.logs.push(parsed) + this.resetFlushTimer() + } + + public overrideConsoleAPI() { + const { debug, error, info, log, table, warn } = console + // eslint-disable-next-line no-global-assign + console = { + ...console, + table: (...args: any) => { + table.call(this, args) + this.saveLog('table', args) + }, + error: (...args: any) => { + error.apply(this, args) + this.saveLog('error', args) + }, + log: (...args: any) => { + log.apply(this, args) + this.saveLog('log', args) + }, + debug: (...args: any) => { + debug.apply(this, args) + this.saveLog('debug', args) + }, + warn: (...args: any) => { + warn.apply(this, args) + this.saveLog('warn', args) + }, + info: (...args: any) => { + info.apply(this, args) + this.saveLog('info', args) + }, + } + } + + private replaceFunctionWithNamesFromObjects(data: any) { + if (typeof data === 'function') return `func() ${data.name}` + if (!data || typeof data !== 'object') return data + if (data instanceof Promise) return 'Promise' + const acc: any = + Object.prototype.toString.call(data) === '[object Array]' ? [] : {} + return Object.keys(data).reduce((acc, key) => { + acc[key] = this.replaceFunctionWithNamesFromObjects(data[key]) + return acc + }, acc) + } + + // iterates over the data and if data is object/array, then it will remove any functions from it + private sanitizeData(data: any): any { + try { + const returnData = this.replaceFunctionWithNamesFromObjects(data) + return returnData + } catch (e) { + return [`There was some error: ${e} ${JSON.stringify(data)}`] + } + } + + // returns the logs from the function execution after sanitising them and resets the logs object after that + public flushLogs(): LogObject[] { + const sanitisedLogs = this.logs.map((log) => { + return { + ...log, + data: this.sanitizeData(log.data), + } + }) + this.resetLogs() + return sanitisedLogs + } + + // parses the incoming log and converts it to the log object + public parseLogs(method: Methods, data: any[]): LogObject { + // Create an ID + const id = nanoid() + const timestamp = moment().format('hh:mm:ss') + // Parse the methods + let output = data + // For logs UI we only keep 3 levels of severity, info, warn, error + let severity = Severity.INFO + if (method === 'error') { + severity = Severity.ERROR + output = data.map((error) => { + try { + return error.stack || error + } catch (e) { + return error + } + }) + } else if (method === 'warn') { + severity = Severity.WARNING + } + + return { + method, + id, + data: klona(output), + timestamp, + severity, + } + } + + public resetLogs() { + this.logs = [] + } +} + +export const userLogs = new UserLog() diff --git a/auxiliaries/code-editor/src/CodeEditor/works/Evaluation/dataTreeUtils.ts b/auxiliaries/code-editor/src/CodeEditor/works/Evaluation/dataTreeUtils.ts new file mode 100644 index 0000000..afaa334 --- /dev/null +++ b/auxiliaries/code-editor/src/CodeEditor/works/Evaluation/dataTreeUtils.ts @@ -0,0 +1,84 @@ +import { set } from 'lodash' + +import { DataTree } from '@modou/code-editor/CodeEditor/common/editor-config' +import { + DataTreeEntity, + UnEvalTree, + UnEvalTreeEntityObject, +} from '@modou/code-editor/CodeEditor/entities/DataTree/dataTreeFactory' +import { EvalProps } from '@modou/code-editor/CodeEditor/works/common/DataTreeEvaluator' + +import { removeFunctions } from './evaluationUtils' + +/** + * This method accept an entity object as input and if it has __config__ property + * than it moves the __config__ to object's prototype + */ +export function createNewEntity(entity: UnEvalTreeEntityObject) { + if (!entity || !Reflect.has(entity, '__config__')) { + return entity + } + // eslint-disable-next-line @typescript-eslint/naming-convention + const { __config__, ...rest } = entity + const newObj = Object.create(__config__) + Object.assign(newObj, rest) as DataTreeEntity + return newObj +} +/** + * This method takes unevaltree received from mainThread as input and return a + * new unEvalTree with each entity config moved to entity object's prototype. + * Moving configs to prototype skips it from diffing, cloning and getAllPaths calculation. + * Refer: https://github.com/appsmithorg/appsmith/pull/18361 to know more + */ +export function createUnEvalTreeForEval(unevalTree: UnEvalTree) { + const newUnEvalTree: DataTree = {} + + for (const entityName of Object.keys(unevalTree)) { + const entity = unevalTree[entityName] + newUnEvalTree[entityName] = createNewEntity( + entity as UnEvalTreeEntityObject, + ) + } + + return newUnEvalTree +} + +/** + * This method loops through each entity object of dataTree and sets the entity config + * from prototype as object properties. + * This is done to send back dataTree in the format expected by mainThread. + */ +export function makeEntityConfigsAsObjProperties( + dataTree: DataTree, + option = {} as { + sanitizeDataTree?: boolean + evalProps?: EvalProps + }, +): DataTree { + const { evalProps, sanitizeDataTree = true } = option + const newDataTree: DataTree = {} + for (const entityName of Object.keys(dataTree)) { + const entityConfig = Object.getPrototypeOf(dataTree[entityName]) || {} + const entity = dataTree[entityName] + newDataTree[entityName] = { ...entityConfig, ...entity } + } + const dataTreeToReturn = sanitizeDataTree + ? JSON.parse(JSON.stringify(newDataTree)) + : newDataTree + + if (!evalProps) return dataTreeToReturn + + const sanitizedEvalProps = removeFunctions(evalProps) as EvalProps + for (const [entityName, entityEvalProps] of Object.entries( + sanitizedEvalProps, + )) { + if (!entityEvalProps.__evaluation__) continue + set( + dataTreeToReturn[entityName], + '__evaluation__', + entityEvalProps.__evaluation__, + ) + } + + return dataTreeToReturn +} diff --git a/auxiliaries/code-editor/src/CodeEditor/works/Evaluation/evaluate.ts b/auxiliaries/code-editor/src/CodeEditor/works/Evaluation/evaluate.ts new file mode 100644 index 0000000..f83f631 --- /dev/null +++ b/auxiliaries/code-editor/src/CodeEditor/works/Evaluation/evaluate.ts @@ -0,0 +1,477 @@ +import { isEmpty } from 'lodash' +import unescapeJS from 'unescape-js' + +import { DataTree } from '@modou/code-editor/CodeEditor/common/editor-config' +import { EventType } from '@modou/code-editor/CodeEditor/constants/AppsmithActionConstants/ActionConstants' +import { + LogObject, + Severity, +} from '@modou/code-editor/CodeEditor/entities/AppsmithConsole' +import { ActionDescription } from '@modou/code-editor/CodeEditor/entities/DataTree/actionTriggers' +import { TriggerMeta } from '@modou/code-editor/CodeEditor/sagas/ActionExecution/ActionExecutionSagas' +import { + EvaluationError, + PropertyEvaluationErrorType, + extraLibraries, + unsafeFunctionForEval, +} from '@modou/code-editor/CodeEditor/utils/DynamicBindingUtils' +import { enhanceDataTreeWithFunctions } from '@modou/code-editor/CodeEditor/works/Evaluation/Actions' +import { interceptAndOverrideHttpRequest } from '@modou/code-editor/CodeEditor/works/Evaluation/HTTPRequestOverride' +import { completePromise } from '@modou/code-editor/CodeEditor/works/Evaluation/PromisifyAction' +import { overrideTimeout } from '@modou/code-editor/CodeEditor/works/Evaluation/TimeoutOverride' +import { userLogs } from '@modou/code-editor/CodeEditor/works/Evaluation/UserLog' +import { indirectEval } from '@modou/code-editor/CodeEditor/works/Evaluation/indirectEval' + +export interface EvalResult { + result: any + errors: EvaluationError[] + triggers?: ActionDescription[] + logs?: LogObject[] +} + +export enum EvaluationScriptType { + EXPRESSION = 'EXPRESSION', + ANONYMOUS_FUNCTION = 'ANONYMOUS_FUNCTION', + ASYNC_ANONYMOUS_FUNCTION = 'ASYNC_ANONYMOUS_FUNCTION', + TRIGGERS = 'TRIGGERS', +} + +export const ScriptTemplate = '<>' + +export const EvaluationScripts: Record = { + [EvaluationScriptType.EXPRESSION]: ` + function closedFunction () { + const result = ${ScriptTemplate} + return result; + } + closedFunction.call(THIS_CONTEXT) + `, + [EvaluationScriptType.ANONYMOUS_FUNCTION]: ` + function callback (script) { + const userFunction = script; + const result = userFunction?.apply(THIS_CONTEXT, ARGUMENTS); + return result; + } + callback(${ScriptTemplate}) + `, + [EvaluationScriptType.ASYNC_ANONYMOUS_FUNCTION]: ` + async function callback (script) { + const userFunction = script; + const result = await userFunction?.apply(THIS_CONTEXT, ARGUMENTS); + return result; + } + callback(${ScriptTemplate}) + `, + [EvaluationScriptType.TRIGGERS]: ` + async function closedFunction () { + const result = await ${ScriptTemplate}; + return result; + } + closedFunction.call(THIS_CONTEXT); + `, +} + +const topLevelWorkerAPIs = Object.keys(self).reduce((acc, key: string) => { + acc[key] = true + return acc +}, {}) + +function resetWorkerGlobalScope() { + for (const key of Object.keys(self)) { + if (topLevelWorkerAPIs[key]) continue + if (key === 'evaluationVersion') continue + if (extraLibraries.find((lib) => lib.accessor === key)) { + continue + } + Reflect.deleteProperty(self, key) + } +} + +export const getScriptType = ( + evalArgumentsExist = false, + isTriggerBased = false, +): EvaluationScriptType => { + let scriptType = EvaluationScriptType.EXPRESSION + if (evalArgumentsExist && isTriggerBased) { + scriptType = EvaluationScriptType.ASYNC_ANONYMOUS_FUNCTION + } else if (evalArgumentsExist && !isTriggerBased) { + scriptType = EvaluationScriptType.ANONYMOUS_FUNCTION + } else if (isTriggerBased && !evalArgumentsExist) { + scriptType = EvaluationScriptType.TRIGGERS + } + return scriptType +} + +export const getScriptToEval = ( + userScript: string, + type: EvaluationScriptType, +): string => { + // Using replace here would break scripts with replacement patterns (ex: $&, $$) + const buffer = EvaluationScripts[type].split(ScriptTemplate) + return `${buffer[0]}${userScript}${buffer[1]}` +} + +export function setupEvaluationEnvironment() { + /// // Adding extra libraries separately + extraLibraries.forEach((library) => { + // @ts-expect-error: Types are not available + self[library.accessor] = library.lib + }) + + /// // Remove all unsafe functions + unsafeFunctionForEval.forEach((func) => { + // @ts-expect-error: Types are not available + self[func] = undefined + }) + userLogs.overrideConsoleAPI() + overrideTimeout() + interceptAndOverrideHttpRequest() +} + +const beginsWithLineBreakRegex = /^\s+|\s+$/ + +export interface createGlobalDataArgs { + dataTree: DataTree + resolvedFunctions: Record + context?: EvaluateContext + evalArguments?: unknown[] + isTriggerBased: boolean + // Whether not to add functions like "run", "clear" to entity in global data + skipEntityFunctions?: boolean +} + +export const createGlobalData = (args: createGlobalDataArgs) => { + const { + context, + dataTree, + evalArguments, + isTriggerBased, + resolvedFunctions, + skipEntityFunctions, + } = args + + const GLOBAL_DATA: Record = {} + /// // Adding callback data + GLOBAL_DATA.ARGUMENTS = evalArguments + /// / Adding contextual data not part of data tree + GLOBAL_DATA.THIS_CONTEXT = {} + if (context) { + if (context.thisContext) { + GLOBAL_DATA.THIS_CONTEXT = context.thisContext + } + if (context.globalContext) { + Object.entries(context.globalContext).forEach(([key, value]) => { + GLOBAL_DATA[key] = value + }) + } + } + if (isTriggerBased) { + /// / Add internal functions to dataTree; + const dataTreeWithFunctions = enhanceDataTreeWithFunctions( + dataTree, + context?.requestId, + skipEntityFunctions, + context?.eventType, + ) + /// // Adding Data tree with functions + Object.assign(GLOBAL_DATA, dataTreeWithFunctions) + } else { + // Object.assign removes prototypes of the entity object making sure configs are not shown to user. + Object.assign(GLOBAL_DATA, dataTree) + } + if (!isEmpty(resolvedFunctions)) { + Object.keys(resolvedFunctions).forEach((datum: any) => { + const resolvedObject = resolvedFunctions[datum] + Object.keys(resolvedObject).forEach((key: any) => { + const dataTreeKey = GLOBAL_DATA[datum] + if (dataTreeKey) { + const data = dataTreeKey[key]?.data + // do not remove we will be investigating this + // const isAsync = dataTreeKey?.meta[key]?.isAsync || false; + // const confirmBeforeExecute = dataTreeKey?.meta[key]?.confirmBeforeExecute || false; + dataTreeKey[key] = resolvedObject[key] + // if (isAsync && confirmBeforeExecute) { + // dataTreeKey[key] = confirmationPromise.bind( + // {}, + // context?.requestId, + // resolvedObject[key], + // dataTreeKey.name + "." + key, + // ); + // } else { + // dataTreeKey[key] = resolvedObject[key]; + // } + if (data) { + dataTreeKey[key].data = data + } + } + }) + }) + } + return GLOBAL_DATA +} + +export function sanitizeScript(js: string) { + // We remove any line breaks from the beginning of the script because that + // makes the final function invalid. We also unescape any escaped characters + // so that eval can happen + const trimmedJS = js.replace(beginsWithLineBreakRegex, '') + return self.evaluationVersion > 1 ? trimmedJS : unescapeJS(trimmedJS) +} + +/** Define a context just for this script + * thisContext will define it on the `this` + * globalContext will define it globally + * requestId is used for completing promises + */ +export interface EvaluateContext { + thisContext?: Record + globalContext?: Record + requestId?: string + eventType?: EventType + triggerMeta?: TriggerMeta +} + +export const getUserScriptToEvaluate = ( + userScript: string, + isTriggerBased: boolean, + evalArguments?: any[], +) => { + const unescapedJS = sanitizeScript(userScript) + // If nothing is present to evaluate, return + if (!unescapedJS.length) { + return { + script: '', + } + } + const scriptType = getScriptType(!!evalArguments, isTriggerBased) + const script = getScriptToEval(unescapedJS, scriptType) + return { script } +} + +export default function evaluateSync( + userScript: string, + dataTree: DataTree, + resolvedFunctions: Record, + isJSCollection: boolean, + context?: EvaluateContext, + evalArguments?: any[], + skipLogsOperations = false, +): EvalResult { + return (function () { + resetWorkerGlobalScope() + const errors: EvaluationError[] = [] + let logs: LogObject[] = [] + let result + // skipping log reset if the js collection is being evaluated without run + // Doing this because the promise execution is losing logs in the process due to resets + if (!skipLogsOperations) { + userLogs.resetLogs() + } + /** ** Setting the eval context ****/ + const GLOBAL_DATA: Record = createGlobalData({ + dataTree, + resolvedFunctions, + isTriggerBased: isJSCollection, + context, + evalArguments, + }) + GLOBAL_DATA.ALLOW_ASYNC = false + const { script } = getUserScriptToEvaluate(userScript, false, evalArguments) + // If nothing is present to evaluate, return instead of evaluating + if (!script.length) { + return { + errors: [], + result: undefined, + triggers: [], + } + } + + // Set it to self so that the eval function can have access to it + // as global data. This is what enables access all appsmith + // entity properties from the global context + for (const entity in GLOBAL_DATA) { + // @ts-expect-error: Types are not available + self[entity] = GLOBAL_DATA[entity] + } + + try { + result = indirectEval(script) + console.log('resultindirectEval', result, script) + } catch (error) { + const errorMessage = `${(error as Error).name}: ${ + (error as Error).message + }` + errors.push({ + errorMessage, + severity: Severity.ERROR, + raw: script, + errorType: PropertyEvaluationErrorType.PARSE, + originalBinding: userScript, + }) + } finally { + if (!skipLogsOperations) logs = userLogs.flushLogs() + for (const entity in GLOBAL_DATA) { + Reflect.deleteProperty(self, entity) + } + } + + return { result, errors, logs } + })() +} + +export async function evaluateAsync( + userScript: string, + dataTree: DataTree, + requestId: string, + resolvedFunctions: Record, + context?: EvaluateContext, + evalArguments?: any[], +) { + return (async function () { + resetWorkerGlobalScope() + const errors: EvaluationError[] = [] + let result + let logs + /** ** Setting the eval context ****/ + userLogs.resetLogs() + userLogs.setCurrentRequestInfo({ + requestId, + eventType: context?.eventType, + triggerMeta: context?.triggerMeta, + }) + const GLOBAL_DATA: Record = createGlobalData({ + dataTree, + resolvedFunctions, + isTriggerBased: true, + context: { ...context, requestId }, + evalArguments, + }) + const { script } = getUserScriptToEvaluate(userScript, true, evalArguments) + GLOBAL_DATA.ALLOW_ASYNC = true + // Set it to self so that the eval function can have access to it + // as global data. This is what enables access all appsmith + // entity properties from the global context + Object.keys(GLOBAL_DATA).forEach((key) => { + // @ts-expect-error: Types are not available + self[key] = GLOBAL_DATA[key] + }) + + try { + result = await indirectEval(script) + logs = userLogs.flushLogs() + } catch (error) { + const errorMessage = `UncaughtPromiseRejection: ${ + (error as Error).message + }` + errors.push({ + errorMessage, + severity: Severity.ERROR, + raw: script, + errorType: PropertyEvaluationErrorType.PARSE, + originalBinding: userScript, + }) + logs = userLogs.flushLogs() + } finally { + // Adding this extra try catch because there are cases when logs have child objects + // like functions or promises that cause issue in complete promise action, thus + // leading the app into a bad state. + try { + completePromise(requestId, { + result, + errors, + logs, + triggers: Array.from(self.TRIGGER_COLLECTOR), + }) + } catch (error) { + completePromise(requestId, { + result, + errors, + logs: [userLogs.parseLogs('log', ['failed to parse logs'])], + triggers: Array.from(self.TRIGGER_COLLECTOR), + }) + } + } + })() +} + +export function isFunctionAsync( + userFunction: unknown, + dataTree: DataTree, + resolvedFunctions: Record, + logs: unknown[] = [], +) { + return (function () { + /** ** Setting the eval context ****/ + const GLOBAL_DATA: Record = { + ALLOW_ASYNC: false, + IS_ASYNC: false, + } + /// / Add internal functions to dataTree; + const dataTreeWithFunctions = enhanceDataTreeWithFunctions(dataTree) + /// // Adding Data tree with functions + Object.keys(dataTreeWithFunctions).forEach((datum) => { + GLOBAL_DATA[datum] = dataTreeWithFunctions[datum] + }) + if (!isEmpty(resolvedFunctions)) { + Object.keys(resolvedFunctions).forEach((datum: any) => { + const resolvedObject = resolvedFunctions[datum] + Object.keys(resolvedObject).forEach((key: any) => { + const dataTreeKey = GLOBAL_DATA[datum] + if (dataTreeKey) { + const data = dataTreeKey[key]?.data + // do not remove, we will be investigating this + // const isAsync = dataTreeKey.meta[key]?.isAsync || false; + // const confirmBeforeExecute = + // dataTreeKey.meta[key]?.confirmBeforeExecute || false; + dataTreeKey[key] = resolvedObject[key] + // if (isAsync && confirmBeforeExecute) { + // dataTreeKey[key] = confirmationPromise.bind( + // {}, + // "", + // resolvedObject[key], + // key, + // ); + // } else { + // dataTreeKey[key] = resolvedObject[key]; + // } + if (data) { + dataTreeKey[key].data = data + } + } + }) + }) + } + // Set it to self so that the eval function can have access to it + // as global data. This is what enables access all appsmith + // entity properties from the global context + Object.keys(GLOBAL_DATA).forEach((key) => { + // @ts-expect-error: Types are not available + self[key] = GLOBAL_DATA[key] + }) + try { + if (typeof userFunction === 'function') { + if (userFunction.constructor.name === 'AsyncFunction') { + // functions declared with an async keyword + self.IS_ASYNC = true + } else { + const returnValue = userFunction() + if (!!returnValue && returnValue instanceof Promise) { + self.IS_ASYNC = true + } + if (self.TRIGGER_COLLECTOR.length) { + self.IS_ASYNC = true + } + } + } + } catch (e) { + // We do not want to throw errors for internal operations, to users. + // logLevel should help us in debugging this. + logs.push({ error: 'Error when determining async function' + e }) + } + const isAsync = !!self.IS_ASYNC + for (const entity in GLOBAL_DATA) { + Reflect.deleteProperty(self, entity) + } + return isAsync + })() +} diff --git a/auxiliaries/code-editor/src/CodeEditor/works/Evaluation/evaluation.worker.ts b/auxiliaries/code-editor/src/CodeEditor/works/Evaluation/evaluation.worker.ts new file mode 100644 index 0000000..2ebe479 --- /dev/null +++ b/auxiliaries/code-editor/src/CodeEditor/works/Evaluation/evaluation.worker.ts @@ -0,0 +1,442 @@ +// Workers do not have access to log.error +import { isEmpty } from 'lodash' + +import { DataTree } from '@modou/code-editor/CodeEditor/common/editor-config' +import { UserLogObject } from '@modou/code-editor/CodeEditor/entities/AppsmithConsole' +import { ReplayEntity } from '@modou/code-editor/CodeEditor/entities/Replay' +import ReplayCanvas from '@modou/code-editor/CodeEditor/entities/Replay/ReplayEntity/ReplayCanvas' +import { ReplayEditor } from '@modou/code-editor/CodeEditor/entities/Replay/ReplayEntity/ReplayEditor' +import { + DependencyMap, + EVAL_WORKER_ACTIONS, + EvalError, + EvalErrorTypes, +} from '@modou/code-editor/CodeEditor/utils/DynamicBindingUtils' +import { JSUpdate } from '@modou/code-editor/CodeEditor/utils/JSPaneUtils' +import { + createUnEvalTreeForEval, + makeEntityConfigsAsObjProperties, +} from '@modou/code-editor/CodeEditor/works/Evaluation/dataTreeUtils' +import evaluate, { + evaluateAsync, + setupEvaluationEnvironment, +} from '@modou/code-editor/CodeEditor/works/Evaluation/evaluate' +import { + getSafeToRenderDataTree, + removeFunctions, +} from '@modou/code-editor/CodeEditor/works/Evaluation/evaluationUtils' +import { setFormEvaluationSaga } from '@modou/code-editor/CodeEditor/works/Evaluation/formEval' +import { + EvalTreeRequestData, + EvalTreeResponseData, + EvalWorkerRequest, + EvalWorkerResponse, +} from '@modou/code-editor/CodeEditor/works/Evaluation/types' +import { EvalMetaUpdates } from '@modou/code-editor/CodeEditor/works/common/DataTreeEvaluator/types' +import { + DataTreeDiff, + validateWidgetProperty, +} from '@modou/code-editor/CodeEditor/works/common/DataTreeEvaluator/validationUtils' +import { WorkerErrorTypes } from '@modou/code-editor/CodeEditor/works/common/types' + +import DataTreeEvaluator, { CrashingError } from '../common/DataTreeEvaluator' + +const CANVAS = 'canvas' + +export let dataTreeEvaluator: DataTreeEvaluator | undefined + +let replayMap: Record> + +// TODO: Create a more complete RPC setup in the subtree-eval branch. +function messageEventListener(fn: typeof eventRequestHandler) { + return (e: MessageEvent) => { + const startTime = performance.now() + const { method, requestData, requestId } = e.data + if (method) { + const responseData = fn({ method, requestData, requestId }) + if (responseData) { + const endTime = performance.now() + try { + self.postMessage({ + requestId, + responseData, + timeTaken: (endTime - startTime).toFixed(2), + }) + } catch (e) { + console.error(e) + // we don't want to log dataTree because it is huge. + // eslint-disable-next-line @typescript-eslint/no-unused-vars + const { dataTree, ...rest } = requestData + self.postMessage({ + requestId, + responseData: { + errors: [ + { + type: WorkerErrorTypes.CLONE_ERROR, + message: (e as Error)?.message, + context: JSON.stringify(rest), + }, + ], + }, + timeTaken: (endTime - startTime).toFixed(2), + }) + } + } + } + } +} + +function eventRequestHandler({ + method, + requestData, + requestId, +}: EvalWorkerRequest): EvalWorkerResponse { + switch (method) { + case EVAL_WORKER_ACTIONS.SETUP: { + setupEvaluationEnvironment() + return true + } + case EVAL_WORKER_ACTIONS.EVAL_ACTION_BINDINGS: { + const { bindings, executionParams } = requestData + if (!dataTreeEvaluator) { + return { values: undefined, errors: [] } + } + + const values = dataTreeEvaluator.evaluateActionBindings( + bindings, + executionParams, + ) + + const cleanValues = removeFunctions(values) + + const errors = dataTreeEvaluator.errors + dataTreeEvaluator.clearErrors() + return { values: cleanValues, errors } + } + case EVAL_WORKER_ACTIONS.EVAL_TRIGGER: { + const { + callbackData, + dynamicTrigger, + eventType, + globalContext, + triggerMeta, + // FIXME TYPE __unEvalTree__ + // eslint-disable-next-line @typescript-eslint/naming-convention + unEvalTree: __unEvalTree__, + } = requestData + if (!dataTreeEvaluator) { + return { triggers: [], errors: [] } + } + + const unEvalTree = createUnEvalTreeForEval(__unEvalTree__) + + const { evalOrder, nonDynamicFieldValidationOrder } = + dataTreeEvaluator.setupUpdateTree(unEvalTree) + dataTreeEvaluator.evalAndValidateSubTree( + evalOrder, + nonDynamicFieldValidationOrder, + ) + const evalTree = dataTreeEvaluator.evalTree + const resolvedFunctions = dataTreeEvaluator.resolvedFunctions + + void dataTreeEvaluator.evaluateTriggers( + dynamicTrigger, + evalTree, + requestId, + resolvedFunctions, + callbackData, + { + globalContext, + eventType, + triggerMeta, + }, + ) + + break + } + case EVAL_WORKER_ACTIONS.PROCESS_TRIGGER: + case EVAL_WORKER_ACTIONS.LINT_TREE: + /** + * These actions will not be processed here. They will be handled in the eval trigger sub steps + * @link promisifyAction + **/ + break + case EVAL_WORKER_ACTIONS.CLEAR_CACHE: { + dataTreeEvaluator = undefined + return true + } + case EVAL_WORKER_ACTIONS.VALIDATE_PROPERTY: { + const { property, props, validation, value } = requestData + return removeFunctions( + validateWidgetProperty(validation, value, props, property), + ) + } + case EVAL_WORKER_ACTIONS.UNDO: { + const { entityId } = requestData + if (!replayMap[entityId || CANVAS]) return + const replayResult = replayMap[entityId || CANVAS].replay('UNDO') + replayMap[entityId || CANVAS].clearLogs() + return replayResult + } + case EVAL_WORKER_ACTIONS.REDO: { + const { entityId } = requestData + if (!replayMap[entityId ?? CANVAS]) return + const replayResult = replayMap[entityId ?? CANVAS].replay('REDO') + replayMap[entityId ?? CANVAS].clearLogs() + return replayResult + } + case EVAL_WORKER_ACTIONS.EXECUTE_SYNC_JS: { + const { functionCall } = requestData + + if (!dataTreeEvaluator) { + return true + } + const evalTree = dataTreeEvaluator.evalTree + const resolvedFunctions = dataTreeEvaluator.resolvedFunctions + const { errors, logs, result } = evaluate( + functionCall, + evalTree, + resolvedFunctions, + false, + undefined, + ) + return { errors, logs, result } + } + case EVAL_WORKER_ACTIONS.EVAL_EXPRESSION: { + const { expression, isTrigger } = requestData + const evalTree = dataTreeEvaluator?.evalTree + if (!evalTree) return {} + // TODO find a way to do this for snippets + return isTrigger + ? evaluateAsync(expression, evalTree, 'SNIPPET', {}) + : evaluate(expression, evalTree, {}, false) + } + case EVAL_WORKER_ACTIONS.UPDATE_REPLAY_OBJECT: { + const { entity, entityId, entityType } = requestData + const replayObject = replayMap[entityId] + if (replayObject) { + replayObject.update(entity) + } else { + replayMap[entityId] = new ReplayEditor(entity, entityType) + } + break + } + case EVAL_WORKER_ACTIONS.SET_EVALUATION_VERSION: { + const { version } = requestData + self.evaluationVersion = version || 1 + break + } + case EVAL_WORKER_ACTIONS.INIT_FORM_EVAL: { + const { currentEvalState, payload, type } = requestData + const response = setFormEvaluationSaga(type, payload, currentEvalState) + return response + } + case EVAL_WORKER_ACTIONS.EVAL_TREE: { + let evalOrder: string[] = [] + let lintOrder: string[] = [] + let jsUpdates: Record = {} + let unEvalUpdates: DataTreeDiff[] = [] + let nonDynamicFieldValidationOrder: string[] = [] + let isCreateFirstTree = false + let dataTree: DataTree = {} + let errors: EvalError[] = [] + let logs: any[] = [] + let userLogs: UserLogObject[] = [] + let dependencies: DependencyMap = {} + let evalMetaUpdates: EvalMetaUpdates = [] + + const { + allActionValidationConfig, + // requiresLinting, + shouldReplay, + theme, + // FIXME TYPE __unEvalTree__ + // eslint-disable-next-line @typescript-eslint/naming-convention + unevalTree: __unevalTree__, + widgets, + widgetTypeConfigMap, + } = requestData as EvalTreeRequestData + + const unevalTree = createUnEvalTreeForEval(__unevalTree__) + + try { + if (!dataTreeEvaluator) { + isCreateFirstTree = true + replayMap = replayMap || {} + replayMap[CANVAS] = new ReplayCanvas({ widgets, theme }) + console.log( + 'DataTreeEvaluatorDataTreeEvaluator', + requestData, + widgetTypeConfigMap, + allActionValidationConfig, + ) + dataTreeEvaluator = new DataTreeEvaluator( + widgetTypeConfigMap, + allActionValidationConfig, + ) + + const setupFirstTreeResponse = + dataTreeEvaluator.setupFirstTree(unevalTree) + evalOrder = setupFirstTreeResponse.evalOrder + lintOrder = setupFirstTreeResponse.lintOrder + jsUpdates = setupFirstTreeResponse.jsUpdates + + // TODO:(LiuLei) initiateLinting + // initiateLinting( + // lintOrder, + // makeEntityConfigsAsObjProperties(dataTreeEvaluator.oldUnEvalTree, { + // sanitizeDataTree: false, + // }), + // requiresLinting, + // ) + + const dataTreeResponse = dataTreeEvaluator.evalAndValidateFirstTree() + dataTree = makeEntityConfigsAsObjProperties( + dataTreeResponse.evalTree, + { + evalProps: dataTreeEvaluator.evalProps, + }, + ) + } else if (dataTreeEvaluator.hasCyclicalDependency) { + if (dataTreeEvaluator && !isEmpty(allActionValidationConfig)) { + // allActionValidationConfigs may not be set in dataTreeEvaluatior. + // Therefore, set it explicitly via setter method + dataTreeEvaluator.setAllActionValidationConfig( + allActionValidationConfig, + ) + } + if (shouldReplay) { + replayMap[CANVAS]?.update({ widgets, theme }) + } + dataTreeEvaluator = new DataTreeEvaluator( + widgetTypeConfigMap, + allActionValidationConfig, + ) + if (dataTreeEvaluator && !isEmpty(allActionValidationConfig)) { + dataTreeEvaluator.setAllActionValidationConfig( + allActionValidationConfig, + ) + } + const setupFirstTreeResponse = + dataTreeEvaluator.setupFirstTree(unevalTree) + isCreateFirstTree = true + evalOrder = setupFirstTreeResponse.evalOrder + lintOrder = setupFirstTreeResponse.lintOrder + jsUpdates = setupFirstTreeResponse.jsUpdates + + // TODO:(LiuLei) initiateLinting + // initiateLinting( + // lintOrder, + // makeEntityConfigsAsObjProperties(dataTreeEvaluator.oldUnEvalTree, { + // sanitizeDataTree: false, + // }), + // requiresLinting, + // ) + + const dataTreeResponse = dataTreeEvaluator.evalAndValidateFirstTree() + dataTree = makeEntityConfigsAsObjProperties( + dataTreeResponse.evalTree, + { + evalProps: dataTreeEvaluator.evalProps, + }, + ) + } else { + if (dataTreeEvaluator && !isEmpty(allActionValidationConfig)) { + dataTreeEvaluator.setAllActionValidationConfig( + allActionValidationConfig, + ) + } + isCreateFirstTree = false + if (shouldReplay) { + replayMap[CANVAS]?.update({ widgets, theme }) + } + const setupUpdateTreeResponse = + dataTreeEvaluator.setupUpdateTree(unevalTree) + evalOrder = setupUpdateTreeResponse.evalOrder + // eslint-disable-next-line @typescript-eslint/no-unused-vars + lintOrder = setupUpdateTreeResponse.lintOrder + jsUpdates = setupUpdateTreeResponse.jsUpdates + unEvalUpdates = setupUpdateTreeResponse.unEvalUpdates + // TODO:(LiuLei) initiateLinting + // initiateLinting( + // lintOrder, + // makeEntityConfigsAsObjProperties(dataTreeEvaluator.oldUnEvalTree, { + // sanitizeDataTree: false, + // }), + // requiresLinting, + // ) + nonDynamicFieldValidationOrder = + setupUpdateTreeResponse.nonDynamicFieldValidationOrder + const updateResponse = dataTreeEvaluator.evalAndValidateSubTree( + evalOrder, + nonDynamicFieldValidationOrder, + ) + dataTree = makeEntityConfigsAsObjProperties( + dataTreeEvaluator.evalTree, + { + evalProps: dataTreeEvaluator.evalProps, + }, + ) + evalMetaUpdates = JSON.parse( + JSON.stringify(updateResponse.evalMetaUpdates), + ) + } + // eslint-disable-next-line no-self-assign + dataTreeEvaluator = dataTreeEvaluator + dependencies = dataTreeEvaluator.inverseDependencyMap + errors = dataTreeEvaluator.errors + dataTreeEvaluator.clearErrors() + logs = dataTreeEvaluator.logs + userLogs = dataTreeEvaluator.userLogs + if (shouldReplay) { + if (replayMap[CANVAS]?.logs) + logs = logs.concat(replayMap[CANVAS]?.logs) + replayMap[CANVAS]?.clearLogs() + } + + dataTreeEvaluator.clearLogs() + } catch (error) { + if (dataTreeEvaluator !== undefined) { + errors = dataTreeEvaluator.errors + logs = dataTreeEvaluator.logs + userLogs = dataTreeEvaluator.userLogs + } + if (!(error instanceof CrashingError)) { + errors.push({ + type: EvalErrorTypes.UNKNOWN_ERROR, + message: (error as Error).message, + }) + console.error(error) + } + + dataTree = getSafeToRenderDataTree( + makeEntityConfigsAsObjProperties(unevalTree, { + sanitizeDataTree: false, + evalProps: dataTreeEvaluator?.evalProps, + }), + widgetTypeConfigMap, + ) + + unEvalUpdates = [] + } + + return { + dataTree, + dependencies, + errors, + evalMetaUpdates, + evaluationOrder: evalOrder, + jsUpdates, + logs, + userLogs, + unEvalUpdates, + isCreateFirstTree, + } as EvalTreeResponseData + } + default: { + console.error('Action not registered on evalWorker', method) + } + } +} + +self.onmessage = messageEventListener(eventRequestHandler) diff --git a/auxiliaries/code-editor/src/CodeEditor/works/Evaluation/evaluationSubstitution.ts b/auxiliaries/code-editor/src/CodeEditor/works/Evaluation/evaluationSubstitution.ts new file mode 100644 index 0000000..980e467 --- /dev/null +++ b/auxiliaries/code-editor/src/CodeEditor/works/Evaluation/evaluationSubstitution.ts @@ -0,0 +1,151 @@ +import _ from 'lodash' + +import { EvaluationSubstitutionType } from '@modou/code-editor/CodeEditor/entities/DataTree/types' +import { isDynamicValue } from '@modou/code-editor/CodeEditor/utils/DynamicBindingUtils' +import { Types, getType } from '@modou/code-editor/CodeEditor/utils/TypeHelpers' + +import { QUOTED_BINDING_REGEX } from '../../constants/bindings' + +const filterBindingSegmentsAndRemoveQuotes = ( + binding: string, + subSegments: string[], + subSegmentValues: unknown[], +) => { + const bindingStrippedQuotes = binding.replace( + QUOTED_BINDING_REGEX, + (original, firstGroup) => { + return firstGroup + }, + ) + const subBindings: string[] = [] + const subValues: unknown[] = [] + subSegments.forEach((segment, i) => { + if (isDynamicValue(segment)) { + subBindings.push(segment) + subValues.push(subSegmentValues[i]) + } + }) + return { binding: bindingStrippedQuotes, subBindings, subValues } +} + +export const smartSubstituteDynamicValues = ( + originalBinding: string, + subSegments: string[], + subSegmentValues: unknown[], +): string => { + const { binding, subBindings, subValues } = + filterBindingSegmentsAndRemoveQuotes( + originalBinding, + subSegments, + subSegmentValues, + ) + let finalBinding = binding + subBindings.forEach((b, i) => { + const value = subValues[i] + switch (getType(value)) { + case Types.NUMBER: + case Types.BOOLEAN: + case Types.NULL: + case Types.UNDEFINED: + // Direct substitution + finalBinding = finalBinding.replace(b, `${value}`) + break + case Types.STRING: + // Add quotes to a string + // JSON.stringify string to escape any unsupported characters + finalBinding = finalBinding.replace(b, `${JSON.stringify(value)}`) + break + case Types.ARRAY: + case Types.OBJECT: + // Stringify and substitute + finalBinding = finalBinding.replace(b, JSON.stringify(value, null, 2)) + break + } + }) + return finalBinding +} + +export const parameterSubstituteDynamicValues = ( + originalBinding: string, + subSegments: string[], + subSegmentValues: unknown[], +) => { + const { binding, subBindings, subValues } = + filterBindingSegmentsAndRemoveQuotes( + originalBinding, + subSegments, + subSegmentValues, + ) + // if only one binding is provided in the whole string, we need to throw an error + if (subSegments.length === 1 && subBindings.length === 1) { + throw Error( + 'Dynamic bindings in prepared statements are only used to provide parameters inside SQL query.' + + ' No SQL query found.', + ) + } + + let finalBinding = binding + const parameters: Record = {} + subBindings.forEach((b, i) => { + // Replace binding with $1, $2; + const key = `$${i + 1}` + finalBinding = finalBinding.replace(b, key) + parameters[key] = + typeof subValues[i] === 'object' + ? JSON.stringify(subValues[i], null, 2) + : subValues[i] + }) + return { value: finalBinding, parameters } +} +// For creating a final value where bindings could be in a template format +export const templateSubstituteDynamicValues = ( + binding: string, + subBindings: string[], + subValues: unknown[], +): string => { + // Replace the string with the data tree values + let finalValue = binding + subBindings.forEach((b, i) => { + let value = subValues[i] + if (Array.isArray(value) || _.isObject(value)) { + value = JSON.stringify(value) + } + try { + if (typeof value === 'string' && JSON.parse(value)) { + value = value.replace(/\\([\s\S])|(")/g, '\\$1$2') + } + } catch (e) { + // do nothing + } + finalValue = finalValue.replace(b, `${value}`) + }) + return finalValue +} + +export const substituteDynamicBindingWithValues = ( + binding: string, + subSegments: string[], + subSegmentValues: unknown[], + evaluationSubstitutionType: EvaluationSubstitutionType, +): string | { value: string; parameters: Record } => { + switch (evaluationSubstitutionType) { + case EvaluationSubstitutionType.TEMPLATE: + return templateSubstituteDynamicValues( + binding, + subSegments, + subSegmentValues, + ) + case EvaluationSubstitutionType.SMART_SUBSTITUTE: + return smartSubstituteDynamicValues( + binding, + subSegments, + subSegmentValues, + ) + case EvaluationSubstitutionType.PARAMETER: + return parameterSubstituteDynamicValues( + binding, + subSegments, + subSegmentValues, + ) + } +} diff --git a/auxiliaries/code-editor/src/CodeEditor/works/Evaluation/evaluationUtils.ts b/auxiliaries/code-editor/src/CodeEditor/works/Evaluation/evaluationUtils.ts new file mode 100644 index 0000000..9402eab --- /dev/null +++ b/auxiliaries/code-editor/src/CodeEditor/works/Evaluation/evaluationUtils.ts @@ -0,0 +1,636 @@ +import { Diff } from 'deep-diff' +import { klona } from 'klona/full' +import { get, isFunction, isObject, isString, set, toPath } from 'lodash' +import { warn as logWarn } from 'loglevel' + +import { + DataTreeAction, + DataTreeAppsmith, + DataTreeEntity, + DataTreeJSAction, + DataTreeObjectEntity, + DataTreeWidget, +} from '@modou/code-editor/CodeEditor/entities/DataTree/dataTreeFactory' +import { ENTITY_TYPE } from '@modou/code-editor/CodeEditor/entities/DataTree/types' +import { + DependencyMap, + EVAL_ERROR_PATH, + EvaluationError, + PropertyEvaluationErrorType, + isChildPropertyPath, + isDynamicValue, +} from '@modou/code-editor/CodeEditor/utils/DynamicBindingUtils' +import { EvalProps } from '@modou/code-editor/CodeEditor/works/common/DataTreeEvaluator' +import { EvalMetaUpdates } from '@modou/code-editor/CodeEditor/works/common/DataTreeEvaluator/types' + +import { DataTree } from '../../common/editor-config' +import { WidgetTypeConfigMap } from '../../utils/WidgetFactory' +import { + DataTreeDiff, + DataTreeDiffEvent, + validateWidgetProperty, +} from '../common/DataTreeEvaluator/validationUtils' + +export const IMMEDIATE_PARENT_REGEX = /^(.*)(\..*|\[.*\])$/ + +export const getEntityNameAndPropertyPath = ( + fullPath: string, +): { + entityName: string + propertyPath: string +} => { + const indexOfFirstDot = fullPath.indexOf('.') + if (indexOfFirstDot === -1) { + // No dot was found so path is the entity name itself + return { + entityName: fullPath, + propertyPath: '', + } + } + const entityName = fullPath.substring(0, indexOfFirstDot) + const propertyPath = fullPath.substring(indexOfFirstDot + 1) + return { entityName, propertyPath } +} + +// For the times when you need to know if something truly an object like { a: 1, b: 2} +// typeof, lodash.isObject and others will return false positives for things like array, null, etc +export const isTrueObject = ( + item: unknown, +): item is Record => { + return Object.prototype.toString.call(item) === '[object Object]' +} + +export function isJSAction(entity: DataTreeEntity): entity is DataTreeJSAction { + return ( + typeof entity === 'object' && + 'ENTITY_TYPE' in entity && + entity.ENTITY_TYPE === ENTITY_TYPE.JSACTION + ) +} + +export function isAction( + entity: Partial, +): entity is DataTreeAction { + return ( + typeof entity === 'object' && + 'ENTITY_TYPE' in entity && + entity.ENTITY_TYPE === ENTITY_TYPE.ACTION + ) +} + +export function isAppsmithEntity( + entity: DataTreeEntity, +): entity is DataTreeAppsmith { + return ( + typeof entity === 'object' && + 'ENTITY_TYPE' in entity && + entity.ENTITY_TYPE === ENTITY_TYPE.APPSMITH + ) +} + +export const overrideWidgetProperties = (params: { + entity: DataTreeWidget + propertyPath: string + value: unknown + currentTree: DataTree + evalMetaUpdates: EvalMetaUpdates +}) => { + const { currentTree, entity, evalMetaUpdates, propertyPath, value } = params + const clonedValue = klona(value) + if (propertyPath in entity.overridingPropertyPaths) { + const overridingPropertyPaths = entity.overridingPropertyPaths[propertyPath] + + overridingPropertyPaths.forEach((overriddenPropertyPath) => { + const overriddenPropertyPathArray = overriddenPropertyPath.split('.') + set( + currentTree, + [entity.widgetName, ...overriddenPropertyPathArray], + clonedValue, + ) + // evalMetaUpdates has all updates from property which overrides meta values. + if ( + propertyPath.split('.')[0] !== 'meta' && + overriddenPropertyPathArray[0] === 'meta' + ) { + const metaPropertyPath = overriddenPropertyPathArray.slice(1) + evalMetaUpdates.push({ + widgetId: entity.widgetId, + metaPropertyPath, + value: clonedValue, + }) + } + }) + } else if ( + propertyPath in entity.propertyOverrideDependency && + clonedValue === undefined + ) { + // When a reset a widget its meta value becomes undefined, ideally they should reset to default value. + // below we handle logic to reset meta values to default values. + const propertyOverridingKeyMap = + entity.propertyOverrideDependency[propertyPath] + if (propertyOverridingKeyMap.DEFAULT) { + const defaultValue = entity[propertyOverridingKeyMap.DEFAULT] + const clonedDefaultValue = klona(defaultValue) + if (defaultValue !== undefined) { + const propertyPathArray = propertyPath.split('.') + set( + currentTree, + [entity.widgetName, ...propertyPathArray], + clonedDefaultValue, + ) + + return { + overwriteParsedValue: true, + newValue: clonedDefaultValue, + } + } + } + } +} + +export function isWidget( + entity: Partial, +): entity is DataTreeWidget { + return ( + typeof entity === 'object' && + 'ENTITY_TYPE' in entity && + entity.ENTITY_TYPE === ENTITY_TYPE.WIDGET + ) +} + +export const resetValidationErrorsForEntityProperty = ({ + evalProps, + fullPropertyPath, +}: { + fullPropertyPath: string + evalProps: EvalProps +}) => { + const { entityName, propertyPath } = + getEntityNameAndPropertyPath(fullPropertyPath) + if (propertyPath) { + const errorPath = `${entityName}.${EVAL_ERROR_PATH}['${propertyPath}']` + const existingErrorsExceptValidation = ( + get(evalProps, errorPath, []) as EvaluationError[] + ).filter( + (error) => error.errorType !== PropertyEvaluationErrorType.VALIDATION, + ) + set(evalProps, errorPath, existingErrorsExceptValidation) + } +} +function isInt(val: string | number): boolean { + return Number.isInteger(val) || (isString(val) && /^\d+$/.test(val)) +} +export const convertPathToString = (arrPath: Array) => { + let string = '' + arrPath.forEach((segment) => { + if (isInt(segment)) { + string = string + '[' + segment + ']' + } else { + if (string.length !== 0) { + string = string + '.' + } + string = string + segment + } + }) + return string +} + +export const addWidgetPropertyDependencies = ({ + entity, + entityName, +}: { + entity: DataTreeWidget + entityName: string +}) => { + const dependencies: DependencyMap = {} + + Object.entries(entity.propertyOverrideDependency).forEach( + ([overriddenPropertyKey, overridingPropertyKeyMap]) => { + const existingDependenciesSet = new Set( + dependencies[`${entityName}.${overriddenPropertyKey}`] || [], + ) + // add meta dependency + overridingPropertyKeyMap.META && + existingDependenciesSet.add( + `${entityName}.${overridingPropertyKeyMap.META}`, + ) + // add default dependency + overridingPropertyKeyMap.DEFAULT && + existingDependenciesSet.add( + `${entityName}.${overridingPropertyKeyMap.DEFAULT}`, + ) + + dependencies[`${entityName}.${overriddenPropertyKey}`] = [ + ...existingDependenciesSet, + ] + }, + ) + return dependencies +} +export const makeParentsDependOnChild = ( + depMap: DependencyMap, + child: string, + allkeys: Record, +): DependencyMap => { + const result: DependencyMap = depMap + let curKey = child + if (!allkeys[curKey]) { + logWarn( + `makeParentsDependOnChild - ${curKey} is not present in dataTree.`, + 'This might result in a cyclic dependency.', + ) + } + let matches: string[] | null + // Note: The `=` is intentional + // Stops looping when match is null + while ((matches = curKey.match(IMMEDIATE_PARENT_REGEX)) !== null) { + const parentKey = matches[1] + // Todo: switch everything to set. + const existing = new Set(result[parentKey] || []) + existing.add(curKey) + result[parentKey] = Array.from(existing) + curKey = parentKey + } + return result +} + +export const makeParentsDependOnChildren = ( + depMap: DependencyMap, + allkeys: Record, +): DependencyMap => { + // return depMap; + // Make all parents depend on child + Object.keys(depMap).forEach((key) => { + depMap = makeParentsDependOnChild(depMap, key, allkeys) + depMap[key].forEach((path) => { + depMap = makeParentsDependOnChild(depMap, path, allkeys) + }) + }) + return depMap +} +export function isValidEntity( + entity: DataTreeEntity, +): entity is DataTreeObjectEntity { + if (!isObject(entity)) { + return false + } + return 'ENTITY_TYPE' in entity +} +export const isDynamicLeaf = (unEvalTree: DataTree, propertyPath: string) => { + const [entityName, ...propPathEls] = toPath(propertyPath) + // Framework feature: Top level items are never leaves + if (entityName === propertyPath) return false + // Ignore if this was a deleted op + if (!(entityName in unEvalTree)) return false + + const entity = unEvalTree[entityName] + if (!isAction(entity) && !isWidget(entity) && !isJSAction(entity)) + return false + const relativePropertyPath = convertPathToString(propPathEls) + return ( + relativePropertyPath in entity.reactivePaths || + (isWidget(entity) && relativePropertyPath in entity.triggerPaths) + ) +} +// these paths are not required to go through evaluate tree as these are internal properties +const ignorePathsForEvalRegex = + '.(reactivePaths|bindingPaths|triggerPaths|validationPaths|dynamicBindingPathList)' + +const isUninterestingChangeForDependencyUpdate = (path: string) => { + return path.match(ignorePathsForEvalRegex) +} + +export const translateDiffEventToDataTreeDiffEvent = ( + difference: Diff, + unEvalDataTree: DataTree, +): DataTreeDiff | DataTreeDiff[] => { + let result: DataTreeDiff | DataTreeDiff[] = { + payload: { + propertyPath: '', + value: '', + }, + event: DataTreeDiffEvent.NOOP, + } + if (!difference.path) { + return result + } + const propertyPath = convertPathToString(difference.path) + + // add propertyPath to NOOP event + result.payload = { + propertyPath, + value: '', + } + + // we do not need evaluate these paths coz these are internal paths + const isUninterestingPathForUpdateTree = + isUninterestingChangeForDependencyUpdate(propertyPath) + if (isUninterestingPathForUpdateTree) { + return result + } + const { entityName } = getEntityNameAndPropertyPath(propertyPath) + const entity = unEvalDataTree[entityName] + const isJsAction = isJSAction(entity) + switch (difference.kind) { + case 'N': { + result.event = DataTreeDiffEvent.NEW + result.payload = { + propertyPath, + } + break + } + case 'D': { + result.event = DataTreeDiffEvent.DELETE + result.payload = { propertyPath } + break + } + case 'E': { + let rhsChange, lhsChange + if (isJsAction) { + rhsChange = typeof difference.rhs === 'string' + lhsChange = typeof difference.lhs === 'string' + } else { + rhsChange = + typeof difference.rhs === 'string' && isDynamicValue(difference.rhs) + + lhsChange = + typeof difference.lhs === 'string' && isDynamicValue(difference.lhs) + } + + // JsObject function renaming + // remove .data from a String instance manually + // since it won't be identified when calculating diffs + // source for .data in a String instance -> `updateLocalUnEvalTree` + if ( + isJsAction && + rhsChange && + difference.lhs instanceof String && + get(difference.lhs, 'data') + ) { + result = [ + { + event: DataTreeDiffEvent.DELETE, + payload: { + propertyPath: `${propertyPath}.data`, + }, + }, + { + event: DataTreeDiffEvent.EDIT, + payload: { + propertyPath, + value: difference.rhs, + }, + }, + ] + } else if (rhsChange || lhsChange) { + result = [ + { + event: DataTreeDiffEvent.EDIT, + payload: { + propertyPath, + value: difference.rhs, + }, + }, + ] + /** + * If lhs is an array/object + * Add delete events for all memberExpressions + */ + if (Array.isArray(difference.lhs)) { + difference.lhs.forEach((diff, idx) => { + ;(result as DataTreeDiff[]).push({ + event: DataTreeDiffEvent.DELETE, + payload: { + propertyPath: `${propertyPath}[${idx}]`, + }, + }) + }) + } + + if (isTrueObject(difference.lhs)) { + Object.keys(difference.lhs).forEach((diffKey) => { + const path = `${propertyPath}.${diffKey}` + ;(result as DataTreeDiff[]).push({ + event: DataTreeDiffEvent.DELETE, + payload: { + propertyPath: path, + }, + }) + }) + } + } else if (difference.lhs === undefined || difference.rhs === undefined) { + // Handle static value changes that change structure that can lead to + // old bindings being eligible + if ( + difference.lhs === undefined && + (isTrueObject(difference.rhs) || Array.isArray(difference.rhs)) + ) { + result.event = DataTreeDiffEvent.NEW + result.payload = { propertyPath } + } + if ( + difference.rhs === undefined && + (isTrueObject(difference.lhs) || Array.isArray(difference.lhs)) + ) { + result.event = DataTreeDiffEvent.DELETE + result.payload = { propertyPath } + } + } else if ( + isTrueObject(difference.lhs) && + !isTrueObject(difference.rhs) + ) { + // This will happen for static value changes where a property went + // from being an object to any other type like string or number + // in such a case we want to delete all nested paths of the + // original lhs object + + result = Object.keys(difference.lhs).map((diffKey) => { + const path = `${propertyPath}.${diffKey}` + return { + event: DataTreeDiffEvent.DELETE, + payload: { + propertyPath: path, + }, + } + }) + + // when an object is being replaced by an array + // list all new array accessors that are being added + // so dependencies will be created based on existing bindings + if (Array.isArray(difference.rhs)) { + result = result.concat( + translateDiffArrayIndexAccessors( + propertyPath, + difference.rhs, + DataTreeDiffEvent.NEW, + ), + ) + } + } else if ( + !isTrueObject(difference.lhs) && + isTrueObject(difference.rhs) + ) { + // This will happen for static value changes where a property went + // from being any other type like string or number to an object + // in such a case we want to add all nested paths of the + // new rhs object + result = Object.keys(difference.rhs).map((diffKey) => { + const path = `${propertyPath}.${diffKey}` + return { + event: DataTreeDiffEvent.NEW, + payload: { + propertyPath: path, + }, + } + }) + + // when an array is being replaced by an object + // remove all array accessors that are deleted + // so dependencies by existing bindings are removed + if (Array.isArray(difference.lhs)) { + result = result.concat( + translateDiffArrayIndexAccessors( + propertyPath, + difference.lhs, + DataTreeDiffEvent.DELETE, + ), + ) + } + } + break + } + case 'A': { + return translateDiffEventToDataTreeDiffEvent( + { + ...difference.item, + path: [...difference.path, difference.index], + }, + unEvalDataTree, + ) + } + default: { + break + } + } + return result +} +function translateDiffArrayIndexAccessors( + propertyPath: string, + lhs: any[], + DELETE: any, +): any { + throw new Error('Function not implemented.') +} +// The idea is to find the immediate parents of the property paths +// e.g. For Table1.selectedRow.email, the parent is Table1.selectedRow +export const getImmediateParentsOfPropertyPaths = ( + propertyPaths: string[], +): string[] => { + // Use a set to ensure that we dont have duplicates + const parents: Set = new Set() + + propertyPaths.forEach((path) => { + const matches = path.match(IMMEDIATE_PARENT_REGEX) + + if (matches !== null) { + parents.add(matches[1]) + } + }) + + return Array.from(parents) +} +/* + Table1.selectedRow + Table1.selectedRow.email: ["Input1.defaultText"] + */ + +export const addDependantsOfNestedPropertyPaths = ( + parentPaths: string[], + inverseMap: DependencyMap, +): Set => { + const withNestedPaths: Set = new Set() + const dependantNodes = Object.keys(inverseMap) + parentPaths.forEach((propertyPath) => { + withNestedPaths.add(propertyPath) + dependantNodes + .filter((dependantNodePath) => + isChildPropertyPath(propertyPath, dependantNodePath), + ) + .forEach((dependantNodePath) => { + inverseMap[dependantNodePath].forEach((path) => { + withNestedPaths.add(path) + }) + }) + }) + return withNestedPaths +} +export const trimDependantChangePaths = ( + changePaths: Set, + dependencyMap: DependencyMap, +): string[] => { + const trimmedPaths = [] + for (const path of changePaths) { + let foundADependant = false + if (path in dependencyMap) { + const dependants = dependencyMap[path] + for (const dependantPath of dependants) { + if (changePaths.has(dependantPath)) { + foundADependant = true + break + } + } + } + if (!foundADependant) { + trimmedPaths.push(path) + } + } + return trimmedPaths +} + +// We need to remove functions from data tree to avoid any unexpected identifier while JSON parsing +// Check issue https://github.com/appsmithorg/appsmith/issues/719 +export const removeFunctions = (value: any) => { + if (isFunction(value)) { + return 'Function call' + } else if (isObject(value)) { + return JSON.parse( + JSON.stringify(value, (_, v) => + typeof v === 'bigint' ? v.toString() : v, + ), + ) + } else { + return value + } +} + +export function getSafeToRenderDataTree( + tree: DataTree, + widgetTypeConfigMap: WidgetTypeConfigMap, +) { + return Object.keys(tree).reduce((tree, entityKey: string) => { + const entity = tree[entityKey] as DataTreeWidget + if (!isWidget(entity)) { + return tree + } + const safeToRenderEntity = { ...entity } + // Set user input values to their parsed values + Object.entries(entity.validationPaths).forEach(([property, validation]) => { + const value = get(entity, property) + // Pass it through parse + const { parsed } = validateWidgetProperty( + validation, + value, + entity, + property, + ) + set(safeToRenderEntity, property, parsed) + }) + // Set derived values to undefined or else they would go as bindings + Object.keys(widgetTypeConfigMap[entity.type].derivedProperties).forEach( + (property) => { + set(safeToRenderEntity, property, undefined) + }, + ) + return { ...tree, [entityKey]: safeToRenderEntity } + }, tree) +} diff --git a/auxiliaries/code-editor/src/CodeEditor/works/Evaluation/formEval.ts b/auxiliaries/code-editor/src/CodeEditor/works/Evaluation/formEval.ts new file mode 100644 index 0000000..a72e34d --- /dev/null +++ b/auxiliaries/code-editor/src/CodeEditor/works/Evaluation/formEval.ts @@ -0,0 +1,555 @@ +import { isEmpty, uniq } from 'lodash' + +import { + EvaluatedFormConfig, + FormEvalOutput, +} from '@modou/code-editor/CodeEditor/reducers/evaluationReducers/formEvaluationReducer' + +import { isTrueObject } from './evaluationUtils' + +export enum ConditionType { + HIDE = 'hide', // When set, the component will be shown until condition is true + SHOW = 'show', // When set, the component will be hidden until condition is true + ENABLE = 'enable', // When set, the component will be enabled until condition is true + DISABLE = 'disable', // When set, the component will be disabled until condition is true + FETCH_DYNAMIC_VALUES = 'fetchDynamicValues', // When set, the component will fetch the values dynamically + EVALUATE_FORM_CONFIG = 'evaluateFormConfig', // When set, the component will evaluate the form config settings +} + +export enum FormDataPaths { + COMMAND = 'actionConfiguration.formData.command.data', + ENTITY_TYPE = 'actionConfiguration.formData.entityType.data', +} + +// Object to hold the initial eval object +let finalEvalObj: FormEvalOutput + +// This variable, holds an array of strings that represent the path for the evalConfigs. +// This path os used to configure the evalFormConfig objects for various form configs +let evalConfigPaths: string[] = [] + +// This regex matches the config property string up to countless places. +export const MATCH_ACTION_CONFIG_PROPERTY = + /\b(actionConfiguration\.\w+.(?:(\w+.)){1,})\b/g +export function matchExact(r: RegExp, str: string) { + const match = str.match(r) + return match || [] +} + +// Recursive function to generate the evaluation state for form config +const generateInitialEvalState = (formConfig: FormConfigType) => { + const conditionals: Record = {} + const conditionTypes: Record = {} + let dependencyPaths: string[] = [] + + // // Any element is only added to the eval state if they have a conditional statement present, if not they are allowed to be rendered + // if ("conditionals" in formConfig && !!formConfig.conditionals) { + let key = 'unknowns' + + // A unique key is used to refer the object in the eval state, can be propertyName, configProperty or identifier + if ('propertyName' in formConfig && !!formConfig.propertyName) { + key = formConfig.propertyName + } else if ('configProperty' in formConfig && !!formConfig.configProperty) { + key = formConfig.configProperty + } else if ('identifier' in formConfig && !!formConfig.identifier) { + key = formConfig.identifier + } + + // Any element is only added to the eval state if they have a conditional statement present, + // if not they are allowed to be rendered + if ('conditionals' in formConfig && !!formConfig.conditionals) { + const allConditionTypes = Object.keys(formConfig.conditionals) + if ( + allConditionTypes.includes(ConditionType.HIDE) || + allConditionTypes.includes(ConditionType.SHOW) + ) { + conditionTypes.visible = false + merge(conditionals, formConfig.conditionals) + + const showOrHideDependencies = matchExact( + MATCH_ACTION_CONFIG_PROPERTY, + formConfig.conditionals?.show || formConfig.conditionals?.hide || '', + ) + + dependencyPaths = [...dependencyPaths, ...showOrHideDependencies] + } + + if ( + allConditionTypes.includes(ConditionType.ENABLE) || + allConditionTypes.includes(ConditionType.DISABLE) + ) { + conditionTypes.enabled = true + merge(conditionals, formConfig.conditionals) + + const enableOrDisableDependencies = matchExact( + MATCH_ACTION_CONFIG_PROPERTY, + formConfig.conditionals?.enable || + formConfig.conditionals?.disable || + '', + ) + + dependencyPaths = [...dependencyPaths, ...enableOrDisableDependencies] + } + + // if (allConditionTypes.includes(ConditionType.EVALUATE_FORM_CONFIG)) { + // // Setting the component as invisible since it has elements that will be evaluated later + // conditionTypes.visible = false; + // const evaluateFormConfig: EvaluatedFormConfig = { + // updateEvaluatedConfig: false, + // paths: formConfig.conditionals.evaluateFormConfig.paths, + // evaluateFormConfigObject: extractEvalConfigFromFormConfig( + // formConfig, + // formConfig.conditionals.evaluateFormConfig.paths, + // ), + // }; + // conditionTypes.evaluateFormConfig = evaluateFormConfig; + // conditionals.evaluateFormConfig = + // formConfig.conditionals.evaluateFormConfig.condition; + // } + + if (allConditionTypes.includes(ConditionType.FETCH_DYNAMIC_VALUES)) { + const fetchDynamicValuesDependencies = matchExact( + MATCH_ACTION_CONFIG_PROPERTY, + formConfig.conditionals?.fetchDynamicValues?.condition || '', + ) + let dynamicDependencyPathList: Set | undefined + + if (fetchDynamicValuesDependencies.length > 0) { + dynamicDependencyPathList = new Set(fetchDynamicValuesDependencies) + } else { + dynamicDependencyPathList = undefined + } + + const dynamicValues: DynamicValues = { + allowedToFetch: false, + isLoading: false, + hasStarted: false, + hasFetchFailed: false, + data: [], + config: formConfig.conditionals.fetchDynamicValues.config, + dynamicDependencyPathList, + evaluatedConfig: { params: {} }, + } + conditionTypes.fetchDynamicValues = dynamicValues + conditionals.fetchDynamicValues = + formConfig.conditionals.fetchDynamicValues.condition + } + + // make the evalConfigPaths empty before calling the generateFormEvalFormConfigPaths + // this is helpful since we are iterating through the form configs and we do not want to store the value of a + // prev form config into another one. + evalConfigPaths = [] + + // recursively generate the paths for form cofigs that need evalFormConfig. + // and we store them in the evalFormFonfig + generateEvalFormConfigPaths(formConfig) + + // we generate a unique array of paths, if the paths are greater than 0, + // we generate and add the evaluateFormConfig object to the current formConfig. + if (uniq(evalConfigPaths).length > 0) { + conditionTypes.visible = false + const evaluateFormConfig: EvaluatedFormConfig = { + updateEvaluatedConfig: false, + paths: uniq(evalConfigPaths), + evaluateFormConfigObject: extractEvalConfigFromFormConfig( + formConfig, + uniq(evalConfigPaths), + ), + } + conditionTypes.evaluateFormConfig = evaluateFormConfig + conditionals.evaluateFormConfig = '{{true}}' + } + } + + // keep the configProperty in the formConfig values. + let configPropertyPath + if (!!formConfig.configProperty) { + configPropertyPath = formConfig.configProperty + } + + let staticDependencyPathList: Set | undefined + + if (dependencyPaths.length > 0) { + staticDependencyPathList = new Set(dependencyPaths) + } else { + staticDependencyPathList = undefined + } + + // Conditionals are stored in the eval state itself for quick access + finalEvalObj[key] = { + ...conditionTypes, + conditionals, + configPropertyPath, + staticDependencyPathList, + } + + if ('children' in formConfig && !!formConfig.children) + formConfig.children.forEach((config: FormConfigType) => + generateInitialEvalState(config), + ) + + if ('schema' in formConfig && !!formConfig.schema) + formConfig.schema.forEach((config: FormConfigType) => + generateInitialEvalState({ ...config }), + ) +} + +// The idea here is to recursively go through each of the key value pairs of the current form config +// then we check if the form config or its children/options/schemas have dynamic values +// if the children/options/schemas have dynamic values within them, we add the key name of the parent to the evalFormConfigPaths +// this might sound strange but we add the evaluateFormConfig property to the parent. +// this is why we pass the parent key into the function and use it to update the evalFormConfig. +function generateEvalFormConfigPaths( + formConfig: FormConfigType, + parentKey = '', +) { + // this stores all the paths for the current form config, + // we then use this path to update the evalFormConfig array with the parent + const paths: string[] = [] + // we never check the conditionals object, or the placeholderText. + // we also never check children and schema cause the recursive function that this function + // is called in already checks the children and schemas (to prevent double recursive checks). + // the second placeHolderText is due to a rogue value in the formConfig of one of + // S3 datasource form config. + const configToBeChecked = { + ...formConfig, + conditionals: undefined, + children: undefined, + schema: undefined, + placeholderText: undefined, + placeHolderText: undefined, + } + + Object.entries(configToBeChecked).forEach(([key, value]) => { + // we check if the current value for the key is a dynamic value, if yes, we push the current key into our paths array. + if (!!value) { + if (isString(value)) { + if (isDynamicValue(value)) { + paths.push(key) + // if parent key is empty, then there is a very good chance it's coming from the root form config. + // and in that case we can just set it to it. + if (!parentKey) parentKey = key + } + } + + // if it's an array, we run it recursively on the array values. + if (isArray(value)) { + value.forEach((val) => { + generateEvalFormConfigPaths(val, key) + }) + } + + // if it is an object, we do the same. + if (isTrueObject(value as FormConfigType)) { + generateEvalFormConfigPaths(value, key) + } + } + }) + + // if the path array is greater than one, we update the evalConfigPaths with parent key. + if (paths.length > 0) { + evalConfigPaths.push(parentKey) + } +} + +function evaluateDynamicValuesConfig( + actionConfiguration: ActionConfig, + config: Record, +) { + const evaluatedConfig: Record = { ...config } + const configArray = Object.entries(config) + if (configArray.length > 0) { + configArray.forEach(([key, value]) => { + if (typeof value === 'object') { + evaluatedConfig[key] = evaluateDynamicValuesConfig( + actionConfiguration, + value, + ) + } else if (typeof value === 'string' && value.length > 0) { + if (isDynamicValue(value)) { + let evaluatedValue = '' + try { + evaluatedValue = eval(value) + } catch (e) { + evaluatedValue = 'error' + } finally { + evaluatedConfig[key] = evaluatedValue + } + } + } + }) + } + return evaluatedConfig +} + +function evaluateFormConfigElements( + actionConfiguration: ActionConfig, + config: FormConfigEvalObject, +) { + const paths = Object.keys(config) + if (paths.length > 0) { + paths.forEach((path) => { + const { expression } = config[path] + try { + const evaluatedVal = eval(expression) + config[path].output = evaluatedVal + } catch (e) {} + }) + } + return config +} + +// Function to run the eval for the whole form when data changes +function evaluate( + actionConfiguration: ActionConfig, + currentEvalState: FormEvalOutput, + actionDiffPath?: string, + hasRouteChanged?: boolean, +) { + Object.keys(currentEvalState).forEach((key: string) => { + try { + if (currentEvalState[key].hasOwnProperty('conditionals')) { + const conditionBlock = currentEvalState[key].conditionals + if (!!conditionBlock) { + Object.keys(conditionBlock).forEach((conditionType: string) => { + const output = eval(conditionBlock[conditionType]) + if (conditionType === ConditionType.HIDE) { + currentEvalState[key].visible = !output + } else if (conditionType === ConditionType.SHOW) { + currentEvalState[key].visible = output + } else if (conditionType === ConditionType.DISABLE) { + currentEvalState[key].enabled = !output + } else if (conditionType === ConditionType.ENABLE) { + currentEvalState[key].enabled = output + } else if ( + conditionType === ConditionType.FETCH_DYNAMIC_VALUES && + currentEvalState[key].hasOwnProperty('fetchDynamicValues') && + !!currentEvalState[key].fetchDynamicValues + ) { + // this boolean value represents if the current action diff path is a dependency to the form config. + let isActionDiffADependency = false + + // If the key in the currentEval state has dynamicDependencyPathList, + // we check to see if the path of the changed value + // exists in the path list, if it does, we evaluate + // the dynamicValues and fetch the data via API call, + // but if the value does not exist in the path list, + // we prevent the dynamic value from being refetched via API call. + // in other words, if the current actionDiffPath is a dependency, + // then isActionDiffADependency becomes true. + if ( + currentEvalState[key] && + !!currentEvalState[key]?.fetchDynamicValues + ?.dynamicDependencyPathList && + !isEmpty( + currentEvalState[key]?.fetchDynamicValues + ?.dynamicDependencyPathList, + ) && + !!actionDiffPath && + currentEvalState[ + key + ]?.fetchDynamicValues?.dynamicDependencyPathList?.has( + actionDiffPath, + ) + ) { + isActionDiffADependency = true + } + + // if the actionDiffPath is a dependency or if the route has changed + // (navigated to another action/page) of if there's no actionDiffPath at all (when the page is refreshed) + // we want to trigger an API call for the dynamic values. + if ( + isActionDiffADependency || + !actionDiffPath || + hasRouteChanged + ) { + ;( + currentEvalState[key].fetchDynamicValues as DynamicValues + ).allowedToFetch = output + ;( + currentEvalState[key].fetchDynamicValues as DynamicValues + ).isLoading = output + ;( + currentEvalState[key].fetchDynamicValues as DynamicValues + ).evaluatedConfig = evaluateDynamicValuesConfig( + actionConfiguration, + (currentEvalState[key].fetchDynamicValues as DynamicValues) + .config, + ) as DynamicValuesConfig + } else { + ;( + currentEvalState[key].fetchDynamicValues as DynamicValues + ).allowedToFetch = false + ;( + currentEvalState[key].fetchDynamicValues as DynamicValues + ).isLoading = false + } + } else if ( + conditionType === ConditionType.EVALUATE_FORM_CONFIG && + currentEvalState[key].hasOwnProperty('evaluateFormConfig') && + !!currentEvalState[key].evaluateFormConfig + ) { + ;( + currentEvalState[key].evaluateFormConfig as EvaluatedFormConfig + ).updateEvaluatedConfig = output + currentEvalState[key].visible = output + if (output && !!currentEvalState[key].evaluateFormConfig) + ( + currentEvalState[key] + .evaluateFormConfig as EvaluatedFormConfig + ).evaluateFormConfigObject = evaluateFormConfigElements( + actionConfiguration, + ( + currentEvalState[key] + .evaluateFormConfig as EvaluatedFormConfig + ).evaluateFormConfigObject, + ) + } + }) + } + } + } catch (e) {} + }) + return currentEvalState +} + +// Fetches current evaluation and runs a new one based on the new data +function getFormEvaluation( + formId: string, + actionConfiguration: ActionConfig, + currentEvalState: FormEvaluationState, + actionDiffPath?: string, + hasRouteChanged?: boolean, +): FormEvaluationState { + // Only change the form evaluation state if the form ID is same or the evaluation state is present + if (!!currentEvalState && currentEvalState.hasOwnProperty(formId)) { + const currentFormIdEvalState = currentEvalState[formId] + // specific conditions to be evaluated + let conditionToBeEvaluated = {} + // dynamic conditions always need evaluations + let dynamicConditionsToBeFetched = {} + for (const [key, value] of Object.entries(currentFormIdEvalState)) { + if ( + value && + !!value.configPropertyPath && + !!actionDiffPath && + actionDiffPath?.includes(value.configPropertyPath) + ) { + conditionToBeEvaluated = { ...conditionToBeEvaluated, [key]: value } + } + + // static dependency pathlist should be a key of identifiers that point to + // formControls that are dependent on the result of the current form config value. + // it is important to note the difference between staticDependencyPathList + // and dynamicDependencyPathList is that the former is for formConfigs that don't + // require API calls. + // they are mostly layout based i.e. show/hide, enable/disable + if (!!value.staticDependencyPathList && !!actionDiffPath) { + value.staticDependencyPathList.forEach(() => { + if (value.staticDependencyPathList?.has(actionDiffPath)) { + conditionToBeEvaluated = { + ...conditionToBeEvaluated, + [key]: value, + } + } + }) + } + + // if there are dynamic values present, add them to the condition to be evaluated. + if (value && (!!value.fetchDynamicValues || !!value.evaluateFormConfig)) { + dynamicConditionsToBeFetched = { + ...dynamicConditionsToBeFetched, + [key]: value, + } + } + } + + // if no condition is to be evaluated or if the currently changing action diff path is the command path + // then we run evaluations on the whole form. + if ( + isEmpty(conditionToBeEvaluated) || + actionDiffPath === FormDataPaths.COMMAND + ) { + conditionToBeEvaluated = evaluate( + actionConfiguration, + currentEvalState[formId], + actionDiffPath, + hasRouteChanged, + ) + } else { + conditionToBeEvaluated = { + ...conditionToBeEvaluated, + ...dynamicConditionsToBeFetched, + } + conditionToBeEvaluated = evaluate( + actionConfiguration, + conditionToBeEvaluated, + actionDiffPath, + hasRouteChanged, + ) + } + + currentEvalState[formId] = { + ...currentEvalState[formId], + ...conditionToBeEvaluated, + } + } + + return currentEvalState +} + +// Filter function to assign a function to the Action dispatched +export function setFormEvaluationSaga( + type: string, + payload: FormEvalActionPayload, + currentEvalState: FormEvaluationState, +) { + if (type === ReduxActionTypes.INIT_FORM_EVALUATION) { + finalEvalObj = {} + + // Config is extracted from the editor json first + if ( + 'editorConfig' in payload && + !!payload.editorConfig && + payload.editorConfig.length > 0 + ) { + payload.editorConfig.forEach((config: FormConfigType) => { + generateInitialEvalState(config) + }) + } + + // Then the form config is extracted from the settings json + if ( + 'settingConfig' in payload && + !!payload.settingConfig && + payload.settingConfig.length > 0 + ) { + payload.settingConfig.forEach((config: FormConfigType) => { + generateInitialEvalState(config) + }) + } + + // if the evaluations are empty, then the form is not valid, don't mutate the state + if (isEmpty(finalEvalObj)) { + return currentEvalState + } + + // This is the initial evaluation state, evaluations can now be run on top of this + return { [payload.formId]: finalEvalObj } + } else { + const { actionConfiguration, actionDiffPath, formId, hasRouteChanged } = + payload + // In case the formData is not ready or the form is not of type UQI, return empty state + if (!actionConfiguration || !actionConfiguration.formData) { + return currentEvalState + } else { + return getFormEvaluation( + formId, + actionConfiguration, + currentEvalState, + actionDiffPath, + hasRouteChanged, + ) + } + } +} diff --git a/auxiliaries/code-editor/src/CodeEditor/works/Evaluation/helpers.ts b/auxiliaries/code-editor/src/CodeEditor/works/Evaluation/helpers.ts new file mode 100644 index 0000000..a01b848 --- /dev/null +++ b/auxiliaries/code-editor/src/CodeEditor/works/Evaluation/helpers.ts @@ -0,0 +1,53 @@ +// Finds the first index which is a duplicate value +// Returns -1 if there are no duplicates +// Returns the index of the first duplicate entry it finds + +// Note: This "can" fail if the object entries don't have their properties in the +// same order. +export const findDuplicateIndex = (arr: unknown[]) => { + const _uniqSet = new Set() + let currSetSize = 0 + for (let i = 0; i < arr.length; i++) { + // JSON.stringify because value can be objects + _uniqSet.add(JSON.stringify(arr[i])) + if (_uniqSet.size > currSetSize) currSetSize = _uniqSet.size + else return i + } + return -1 +} + +/** Function that count occurrences of a substring in a string; + * @param {String} string The string + * @param {String} subString The sub string to search for + * @param {Boolean} [allowOverlapping] Optional. (Default:false) + * @param {Number | null} [maxLimit] Optional. (Default:null) + */ +export const countOccurrences = ( + string: string, + subString: string, + allowOverlapping = false, + maxLimit: number | null = null, +): number => { + string += '' + subString += '' + if (subString.length <= 0) return string.length + 1 + + let n = 0, // count of occurrences + pos = 0 // current position of the pointer + const step = allowOverlapping ? 1 : subString.length + + while (true) { + pos = string.indexOf(subString, pos) + if (pos >= 0) { + ++n + /** + * If you are only interested in knowing + * whether occurances count exceeds maxLimit, + * then break the loop. + */ + if (maxLimit && n > maxLimit) break + pos += step + } else break + } + return n +} diff --git a/auxiliaries/code-editor/src/CodeEditor/works/Evaluation/indirectEval.ts b/auxiliaries/code-editor/src/CodeEditor/works/Evaluation/indirectEval.ts new file mode 100644 index 0000000..4bf2920 --- /dev/null +++ b/auxiliaries/code-editor/src/CodeEditor/works/Evaluation/indirectEval.ts @@ -0,0 +1,5 @@ +export function indirectEval(script: string) { + /* Indirect eval to prevent local scope access. + Ref. - https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/eval#description */ + return (1, eval)(script) +} diff --git a/auxiliaries/code-editor/src/CodeEditor/works/Evaluation/types.ts b/auxiliaries/code-editor/src/CodeEditor/works/Evaluation/types.ts new file mode 100644 index 0000000..4d5abdf --- /dev/null +++ b/auxiliaries/code-editor/src/CodeEditor/works/Evaluation/types.ts @@ -0,0 +1,45 @@ +import { DataTree } from '@modou/code-editor/CodeEditor/common/editor-config' +import { ActionValidationConfigMap } from '@modou/code-editor/CodeEditor/constants/PropertyControlConstants' +import { AppTheme } from '@modou/code-editor/CodeEditor/entities/AppTheming' +import { UserLogObject } from '@modou/code-editor/CodeEditor/entities/AppsmithConsole' +import { UnEvalTree } from '@modou/code-editor/CodeEditor/entities/DataTree/dataTreeFactory' +import { CanvasWidgetsReduxState } from '@modou/code-editor/CodeEditor/reducers/entityReducers/canvasWidgetsReducer' +import { JSUpdate } from '@modou/code-editor/CodeEditor/utils/JSPaneUtils' +import { WidgetTypeConfigMap } from '@modou/code-editor/CodeEditor/utils/WidgetFactory' +import { EvalMetaUpdates } from '@modou/code-editor/CodeEditor/works/common/DataTreeEvaluator/types' +import { DataTreeDiff } from '@modou/code-editor/CodeEditor/works/common/DataTreeEvaluator/validationUtils' +import { WorkerRequest } from '@modou/code-editor/CodeEditor/works/common/types' + +import { + DependencyMap, + EVAL_WORKER_ACTIONS, + EvalError, +} from '../../utils/DynamicBindingUtils' + +export type EvalWorkerRequest = WorkerRequest +export type EvalWorkerResponse = EvalTreeResponseData | boolean | unknown + +export interface EvalTreeRequestData { + unevalTree: UnEvalTree + widgetTypeConfigMap: WidgetTypeConfigMap + widgets: CanvasWidgetsReduxState + theme: AppTheme + shouldReplay: boolean + allActionValidationConfig: { + [actionId: string]: ActionValidationConfigMap + } + requiresLinting: boolean +} + +export interface EvalTreeResponseData { + dataTree: DataTree + dependencies: DependencyMap + errors: EvalError[] + evalMetaUpdates: EvalMetaUpdates + evaluationOrder: string[] + jsUpdates: Record + logs: unknown[] + userLogs: UserLogObject[] + unEvalUpdates: DataTreeDiff[] + isCreateFirstTree: boolean +} diff --git a/auxiliaries/code-editor/src/CodeEditor/works/Evaluation/validations.ts b/auxiliaries/code-editor/src/CodeEditor/works/Evaluation/validations.ts new file mode 100644 index 0000000..a6c30c9 --- /dev/null +++ b/auxiliaries/code-editor/src/CodeEditor/works/Evaluation/validations.ts @@ -0,0 +1,1067 @@ +import _, { + compact, + get, + isArray, + isObject, + isPlainObject, + isRegExp, + isString, + toString, + uniq, +} from 'lodash' +import * as log from 'loglevel' +import moment from 'moment' + +import getIsSafeURL from '@modou/code-editor/CodeEditor/utils/validation/getIsSafeURL' +import { + countOccurrences, + findDuplicateIndex, +} from '@modou/code-editor/CodeEditor/works/Evaluation/helpers' + +import { ValidationConfig } from '../../constants/PropertyControlConstants' +import { + ValidationResponse, + ValidationTypes, + Validator, +} from '../../constants/WidgetValidation' +import evaluate from './evaluate' + +export const UNDEFINED_VALIDATION = 'UNDEFINED_VALIDATION' +export const VALIDATION_ERROR_COUNT_THRESHOLD = 10 +const MAX_ALLOWED_LINE_BREAKS = 1000 // Rendering performance deteriorates beyond this number. +const LINE_BREAKS_ERROR_MESSAGE = + `Warning: New lines in the text exceed ${MAX_ALLOWED_LINE_BREAKS}.` + + `The text displayed will not contain any new lines.` + +const flat = (array: Array>, uniqueParam: string) => { + let result: Array<{ value: string }> = [] + array.forEach((a) => { + result.push({ value: a[uniqueParam] }) + if (Array.isArray(a.children)) { + result = result.concat(flat(a.children, uniqueParam)) + } + }) + return result +} + +function getPropertyEntry( + obj: Record, + name: string, + ignoreCase = false, +) { + if (!ignoreCase) { + return name + } else { + const keys = Object.getOwnPropertyNames(obj) + return keys.find((key) => key.toLowerCase() === name.toLowerCase()) ?? name + } +} + +function validatePlainObject( + config: ValidationConfig, + value: Record, + props: Record, + propertyPath: string, +) { + if (config.params?.allowedKeys) { + let _valid = true + const _messages: string[] = [] + config.params.allowedKeys.forEach((entry) => { + const ignoreCase = !!entry.params?.ignoreCase + const entryName = getPropertyEntry(value, entry.name, ignoreCase) + + if (Reflect.has(value, entryName)) { + const { isValid, messages, parsed } = validate( + entry, + value[entryName], + props, + propertyPath, + ) + if (!isValid) { + value[entryName] = parsed + _valid = isValid + messages?.forEach((message) => { + _messages.push(`Value of key: ${entryName} is invalid: ${message}`) + }) + } + } else if (!!entry.params?.required || !!entry.params?.requiredKey) { + _valid = false + _messages.push(`Missing required key: ${entryName}`) + } + }) + if (_valid) { + return { + isValid: true, + parsed: value, + } + } + return { + isValid: false, + parsed: config.params?.default || value, + messages: _messages, + } + } + return { + isValid: true, + parsed: value, + } +} + +function validateArray( + config: ValidationConfig, + value: unknown[], + props: Record, + propertyPath: string, +) { + let _isValid = true // Let's first assume that this is valid + const _messages: string[] = [] // Initialise messages array + + // Values allowed in the array, converted into a set of unique values + // or an empty set + const allowedValues = new Set(config.params?.allowedValues ?? []) + + // Keys whose values are supposed to be unique across all values in all objects in the array + let uniqueKeys: string[] = [] + const allowedKeyConfigs = config.params?.children?.params?.allowedKeys + if ( + config.params?.children?.type === ValidationTypes.OBJECT && + Array.isArray(allowedKeyConfigs) && + allowedKeyConfigs.length + ) { + uniqueKeys = compact( + allowedKeyConfigs.map((allowedKeyConfig) => { + // TODO(abhinav): This is concerning, we now have two ways, + // in which we can define unique keys in an array of objects + // We need to disable one option. + + // If this key is supposed to be unique across all objects in the value array + // We include it in the uniqueKeys list + if (allowedKeyConfig.params?.unique) { + return allowedKeyConfig.name + } + return undefined + }), + ) + } + + // Concatenate unique keys from config.params?.unique + uniqueKeys = Array.isArray(config.params?.unique) + ? uniqueKeys.concat(config.params?.unique as string[]) + : uniqueKeys + + // Validation configuration for children + const childrenValidationConfig = config.params?.children + + // Should we validate against disallowed values in the value array? + const shouldVerifyAllowedValues = !!allowedValues.size // allowedValues is a set + + // Do we have validation config for array children? + const shouldValidateChildren = !!childrenValidationConfig + + // Should array values be unique? This should applies only to primitive values in array children + // If we have to validate children with their own validation config, this should be false (Needs verification) + // If this option is true, shouldArrayValuesHaveUniqueValuesForKeys will become false + const shouldArrayHaveUniqueEntries = config.params?.unique === true + + // Should we validate for unique values for properties in the array entries? + const shouldArrayValuesHaveUniqueValuesForKeys = + !!uniqueKeys.length && !shouldArrayHaveUniqueEntries + + // Verify if all values are unique + if (shouldArrayHaveUniqueEntries) { + // Find the index of a duplicate value in array + const duplicateIndex = findDuplicateIndex(value) + if (duplicateIndex !== -1) { + // Bail out early + // Because, we don't want to re-iterate, if this validation fails + return { + isValid: false, + parsed: config.params?.default || [], + messages: [ + `Array must be unique. Duplicate values found at index: ${duplicateIndex}`, + ], + } + } + } + + if (shouldArrayValuesHaveUniqueValuesForKeys) { + // Loop + // Get only unique entries from the value array + const uniqueEntries = _.uniqWith( + value as Array>, + (a: Record, b: Record) => { + // If any of the keys are the same, we fail the uniqueness test + return uniqueKeys.some((key) => a[key] === b[key]) + }, + ) + + if (uniqueEntries.length !== value.length) { + // Bail out early + // Because, we don't want to re-iterate, if this validation fails + return { + isValid: false, + parsed: config.params?.default || [], + messages: [ + `Duplicate values found for the following properties,` + + ` in the array entries, that must be unique -- ${uniqueKeys.join( + ',', + )}.`, + ], + } + } + } + + // Loop + value.every((entry, index) => { + // Validate for allowed values + if (shouldVerifyAllowedValues && !allowedValues.has(entry)) { + _messages.push(`Value is not allowed in this array: ${entry}`) + _isValid = false + } + + // validate using validation config + if (shouldValidateChildren && childrenValidationConfig) { + // Validate this entry + const childValidationResult = validate( + childrenValidationConfig, + entry, + props, + `${propertyPath}[${index}]`, + ) + + // If invalid, append to messages + if (!childValidationResult.isValid) { + _isValid = false + childValidationResult.messages?.forEach((message) => + _messages.push(`Invalid entry at index: ${index}. ${message}`), + ) + } + } + + // Bail out, if the error count threshold has been overcome + // This way, debugger will not have to render too many errors + if (_messages.length >= VALIDATION_ERROR_COUNT_THRESHOLD && !_isValid) { + return false + } + return true + }) + + return { + isValid: _isValid, + parsed: _isValid ? value : config.params?.default || [], + messages: _messages, + } +} + +function validateExcessLineBreaks(value: any): boolean { + /** + * Check if the value exceeds a threshold number of line breaks; + * beyond which the rendering performance starts deteriorating. + */ + const str: string = isObject(value) ? JSON.stringify(value, null, 2) : value + const lineBreakCount: number = countOccurrences( + str, + '\n', + false, + MAX_ALLOWED_LINE_BREAKS, + ) + return lineBreakCount > MAX_ALLOWED_LINE_BREAKS +} + +function validateExcessLength(text: string, maxLength: number): boolean { + /** + * Check if text is too long and without any line breaks. + */ + const lineBreakCount = countOccurrences(text, '\n', false, 0) + return lineBreakCount === 0 && text.length > maxLength +} + +/** + * Iterate through an object, + * Check for length of string values + * and trim them in case they are too long. + */ +function validateObjectValues(obj: any): any { + if (!obj) return + Object.keys(obj).forEach((key) => { + if (typeof obj[key] === 'string' && obj[key].length > 100000) { + obj[key] = obj[key].substring(0, 100000) + } else if (isObject(obj[key])) { + obj[key] = validateObjectValues(obj[key]) + } else if (isArray(obj[key])) { + obj[key] = obj[key].map((item: any) => validateObjectValues(item)) + } + }) + return obj +} + +// TODO: parameter props may not be in use +export const validate = ( + config: ValidationConfig, + value: unknown, + props: Record, + propertyPath = '', +): ValidationResponse => { + const _result = VALIDATORS[config.type as ValidationTypes]( + config, + value, + props, + propertyPath, + ) + + return _result +} + +export const WIDGET_TYPE_VALIDATION_ERROR = + 'This value does not evaluate to type' // TODO: Lot's of changes in validations.ts file + +export function getExpectedType(config?: ValidationConfig): string | undefined { + if (!config) return UNDEFINED_VALIDATION // basic fallback + switch (config.type) { + case ValidationTypes.FUNCTION: + return config.params?.expected?.type || 'unknown' + case ValidationTypes.TEXT: { + let result = 'string' + if (config.params?.allowedValues) { + const allowed = config.params.allowedValues.join(' | ') + result = result + ` ( ${allowed} )` + } + if (config.params?.regex) { + result = config.params?.regex.source + } + if (config.params?.expected?.type) result = config.params?.expected.type + return result + } + case ValidationTypes.REGEX: + return 'regExp' + case ValidationTypes.DATE_ISO_STRING: + return 'ISO 8601 date string' + case ValidationTypes.BOOLEAN: + return 'boolean' + case ValidationTypes.NUMBER: { + let validationType = 'number' + if (config.params?.min) { + validationType = `${validationType} Min: ${config.params?.min}` + } + if (config.params?.max) { + validationType = `${validationType} Max: ${config.params?.max}` + } + if (config.params?.required) { + validationType = `${validationType} Required` + } + + return validationType + } + case ValidationTypes.OBJECT: { + let objectType = 'Object' + if (config.params?.allowedKeys) { + objectType = '{' + config.params?.allowedKeys.forEach((allowedKeyConfig) => { + const _expected = getExpectedType(allowedKeyConfig) + objectType = `${objectType} "${allowedKeyConfig.name}": "${_expected}",` + }) + objectType = `${objectType.substring(0, objectType.length - 1)} }` + return objectType + } + return objectType + } + case ValidationTypes.ARRAY: + case ValidationTypes.NESTED_OBJECT_ARRAY: + if (config.params?.allowedValues) { + const allowed = config.params?.allowedValues.join("' | '") + return `Array<'${allowed}'>` + } + if (config.params?.children) { + const children = getExpectedType(config.params.children) + return `Array<${children}>` + } + return 'Array' + case ValidationTypes.OBJECT_ARRAY: + return `Array` + case ValidationTypes.IMAGE_URL: + return `base64 encoded image | data uri | image url` + case ValidationTypes.SAFE_URL: + return 'URL' + } +} + +export const VALIDATORS: Record = { + [ValidationTypes.TEXT]: ( + config: ValidationConfig, + value: unknown, + props: Record, + ): ValidationResponse => { + if (value === undefined || value === null || value === '') { + if (config.params?.required) { + return { + isValid: false, + parsed: config.params?.default || '', + messages: [ + `${WIDGET_TYPE_VALIDATION_ERROR} ${getExpectedType(config)}`, + ], + } + } + + return { + isValid: true, + parsed: config.params?.default || '', + } + } + let parsed = value + + if (isObject(value)) { + if (config.params?.limitLineBreaks && validateExcessLineBreaks(value)) { + return { + isValid: false, + parsed: JSON.stringify(validateObjectValues(value)), // Parse without line breaks + messages: [LINE_BREAKS_ERROR_MESSAGE], + } + } + return { + isValid: false, + parsed: JSON.stringify(validateObjectValues(value), null, 2), + messages: [ + `${WIDGET_TYPE_VALIDATION_ERROR} ${getExpectedType(config)}`, + ], + } + } + + const isValid = isString(parsed) + const stringValidationError = { + isValid: false, + parsed: config.params?.default || '', + messages: [`${WIDGET_TYPE_VALIDATION_ERROR} ${getExpectedType(config)}`], + } + if (!isValid) { + try { + if (!config.params?.strict) parsed = toString(parsed) + else return stringValidationError + } catch (e) { + return stringValidationError + } + } + if (config.params?.limitLineBreaks && validateExcessLineBreaks(value)) { + return { + isValid: false, + parsed: JSON.stringify(value), // Parse without line breaks + messages: [LINE_BREAKS_ERROR_MESSAGE], + } + } + if (config.params?.allowedValues) { + if (!config.params?.allowedValues.includes((parsed as string).trim())) { + return { + parsed: config.params?.default || '', + messages: [`Disallowed value: ${parsed}`], + isValid: false, + } + } + } + + if (validateExcessLength(parsed as string, 200000)) { + return { + parsed: (parsed as string)?.substring(0, 200000), + isValid: false, + messages: [ + 'Excessive text length without a line break. Rendering a substring to avoid app crash.', + ], + } + } + + if ( + config.params?.regex && + isRegExp(config.params?.regex) && + !config.params?.regex.test(parsed as string) + ) { + return { + parsed: config.params?.default || '', + messages: [ + `${WIDGET_TYPE_VALIDATION_ERROR} ${getExpectedType(config)}`, + ], + isValid: false, + } + } + + return { + isValid: true, + parsed, + } + }, + // TODO(abhinav): The original validation does not make sense fix this. + [ValidationTypes.REGEX]: ( + config: ValidationConfig, + value: unknown, + props: Record, + propertyPath: string, + ): ValidationResponse => { + const { isValid, messages, parsed } = VALIDATORS[ValidationTypes.TEXT]( + config, + value, + props, + propertyPath, + ) + + if (!isValid) { + return { + isValid: false, + parsed: new RegExp(parsed), + messages: [ + `${WIDGET_TYPE_VALIDATION_ERROR} ${getExpectedType(config)}`, + ], + } + } + + return { isValid, parsed, messages } + }, + [ValidationTypes.NUMBER]: ( + config: ValidationConfig, + value: unknown, + props: Record, + ): ValidationResponse => { + if (value === undefined || value === null || value === '') { + if (config.params?.required) { + return { + isValid: false, + parsed: config.params?.default || 0, + messages: ['This value is required'], + } + } + + if (value === '') { + return { + isValid: true, + parsed: config.params?.default || 0, + } + } + + return { + isValid: true, + parsed: value, + } + } + if (!Number.isFinite(value) && !isString(value)) { + return { + isValid: false, + parsed: config.params?.default || 0, + messages: [ + `${WIDGET_TYPE_VALIDATION_ERROR} ${getExpectedType(config)}`, + ], + } + } + + // check for min and max limits + let parsed: number = value as number + if (isString(value)) { + if (/^-?\d+\.?\d*$/.test(value)) { + parsed = Number(value) + } else { + return { + isValid: false, + parsed: value || config.params?.default || 0, + messages: [ + `${WIDGET_TYPE_VALIDATION_ERROR} ${getExpectedType(config)}`, + ], + } + } + } + + if ( + config.params?.min !== undefined && + Number.isFinite(config.params.min) + ) { + if (parsed < Number(config.params.min)) { + return { + isValid: false, + parsed: + // passThroughOnZero is introduced to resolve a bug and to not break existing apps + // Refer: https://github.com/appsmithorg/appsmith/issues/17472#issuecomment-1281818238 + config.params.passThroughOnZero === false + ? parsed || config.params.min || 0 + : parsed ?? config.params.min ?? 0, + messages: [`Minimum allowed value: ${config.params.min}`], + } + } + } + + if ( + config.params?.max !== undefined && + Number.isFinite(config.params.max) + ) { + if (parsed > Number(config.params.max)) { + return { + isValid: false, + parsed: config.params.max || parsed || 0, + messages: [`Maximum allowed value: ${config.params.max}`], + } + } + } + if (config.params?.natural && (parsed < 0 || !Number.isInteger(parsed))) { + return { + isValid: false, + parsed: config.params.default || parsed || 0, + messages: [`Value should be a positive integer`], + } + } + + return { + isValid: true, + parsed, + } + }, + [ValidationTypes.BOOLEAN]: ( + config: ValidationConfig, + value: unknown, + props: Record, + ): ValidationResponse => { + if (value === undefined || value === null || value === '') { + if (config.params?.required) { + return { + isValid: false, + parsed: !!config.params?.default, + messages: [ + `${WIDGET_TYPE_VALIDATION_ERROR} ${getExpectedType(config)}`, + ], + } + } + + if (value === '') { + return { + isValid: true, + parsed: config.params?.default || false, + } + } + + return { isValid: true, parsed: config.params?.default || value } + } + const isABoolean = value === true || value === false + const isStringTrueFalse = value === 'true' || value === 'false' + const isValid = isABoolean || isStringTrueFalse + + let parsed = value + if (isStringTrueFalse) parsed = value !== 'false' + + if (!isValid) { + return { + isValid: false, + parsed: config.params?.default || false, + messages: [ + `${WIDGET_TYPE_VALIDATION_ERROR} ${getExpectedType(config)}`, + ], + } + } + + return { isValid, parsed } + }, + [ValidationTypes.OBJECT]: ( + config: ValidationConfig, + value: unknown, + props: Record, + propertyPath: string, + ): ValidationResponse => { + if ( + value === undefined || + value === null || + (isString(value) && value.trim().length === 0) + ) { + if (config.params?.required) { + return { + isValid: false, + parsed: config.params?.default || {}, + messages: [ + `${WIDGET_TYPE_VALIDATION_ERROR}: ${getExpectedType(config)}`, + ], + } + } + return { + isValid: true, + parsed: config.params?.default || value, + } + } + + if (isPlainObject(value)) { + return validatePlainObject( + config, + value as Record, + props, + propertyPath, + ) + } + + try { + const result = { parsed: JSON.parse(value as string), isValid: true } + if (isPlainObject(result.parsed)) { + return validatePlainObject(config, result.parsed, props, propertyPath) + } + return { + isValid: false, + parsed: config.params?.default || {}, + messages: [ + `${WIDGET_TYPE_VALIDATION_ERROR}: ${getExpectedType(config)}`, + ], + } + } catch (e) { + return { + isValid: false, + parsed: config.params?.default || {}, + messages: [ + `${WIDGET_TYPE_VALIDATION_ERROR}: ${getExpectedType(config)}`, + ], + } + } + }, + [ValidationTypes.ARRAY]: ( + config: ValidationConfig, + value: unknown, + props: Record, + propertyPath: string, + ): ValidationResponse => { + const invalidResponse = { + isValid: false, + parsed: config.params?.default || [], + messages: [`${WIDGET_TYPE_VALIDATION_ERROR} ${getExpectedType(config)}`], + } + if (value === undefined || value === null || value === '') { + if (config.params?.required && !isArray(config.params.default)) { + invalidResponse.messages = [ + 'This property is required for the widget to function correctly', + ] + return invalidResponse + } + if (value === '') { + return { + isValid: true, + parsed: config.params?.default || [], + } + } + if (config.params && isArray(config.params.default)) { + return { + isValid: true, + parsed: config.params?.default, + } + } + + return { + isValid: true, + parsed: value, + } + } + + if (isString(value)) { + try { + const _value = JSON.parse(value) + if (Array.isArray(_value)) { + const result = validateArray(config, _value, props, propertyPath) + return result + } + } catch (e) { + return invalidResponse + } + } + + if (Array.isArray(value)) { + return validateArray(config, value, props, propertyPath) + } + + return invalidResponse + }, + [ValidationTypes.OBJECT_ARRAY]: ( + config: ValidationConfig, + value: unknown, + props: Record, + ): ValidationResponse => { + const invalidResponse = { + isValid: false, + parsed: config.params?.default || [{}], + messages: [`${WIDGET_TYPE_VALIDATION_ERROR} ${getExpectedType(config)}`], + } + if (value === undefined || value === null || value === '') { + if (config.params?.required) return invalidResponse + + if (value === '') { + return { + isValid: true, + parsed: config.params?.default || [{}], + } + } + + return { isValid: true, parsed: value } + } + if (!isString(value) && !Array.isArray(value)) { + return invalidResponse + } + + let parsed = value + + if (isString(value)) { + try { + parsed = JSON.parse(value) + } catch (e) { + return invalidResponse + } + } + + if (Array.isArray(parsed)) { + if (parsed.length === 0) { + if (config.params?.required) { + return invalidResponse + } else { + return { + isValid: true, + parsed: config.params?.default || [{}], + } + } + } + + for (const [index, parsedEntry] of parsed.entries()) { + if (!isPlainObject(parsedEntry)) { + return { + ...invalidResponse, + messages: [`Invalid object at index ${index}`], + } + } + } + return { isValid: true, parsed } + } + return invalidResponse + }, + + [ValidationTypes.NESTED_OBJECT_ARRAY]: ( + config: ValidationConfig, + value: unknown, + props: Record, + propertyPath: string, + ): ValidationResponse => { + let response: ValidationResponse = { + isValid: false, + parsed: config.params?.default || [], + messages: [`${WIDGET_TYPE_VALIDATION_ERROR} ${getExpectedType(config)}`], + } + response = VALIDATORS.ARRAY(config, value, props, propertyPath) + + if (!response.isValid) { + return response + } + // Check if all values and children values are unique + if (config.params?.unique && response.parsed.length) { + if (isArray(config.params?.unique)) { + for (const param of config.params?.unique) { + const flattenedArray = flat(response.parsed, param) + const shouldBeUnique = flattenedArray.map((entry) => + get(entry, param, ''), + ) + if (uniq(shouldBeUnique).length !== flattenedArray.length) { + response = { + ...response, + isValid: false, + messages: [ + `path:${param} must be unique. Duplicate values found`, + ], + } + } + } + } + } + return response + }, + [ValidationTypes.DATE_ISO_STRING]: ( + config: ValidationConfig, + value: unknown, + props: Record, + ): ValidationResponse => { + let isValid = false + let parsed = value + let message = '' + + if (_.isNil(value) || value === '') { + parsed = config.params?.default + + if (config.params?.required) { + isValid = false + message = `Value does not match: ${getExpectedType(config)}` + } else { + isValid = true + } + } else if (typeof value === 'object' && moment(value).isValid()) { + // Date and moment object + isValid = true + parsed = moment(value).toISOString(true) + } else if (isString(value)) { + // Date string + if ( + value === moment(value).toISOString() || + value === moment(value).toISOString(true) + ) { + return { + isValid: true, + parsed: value, + } + } else if (moment(value).isValid()) { + isValid = true + parsed = moment(value).toISOString(true) + } else { + isValid = false + message = `Value does not match: ${getExpectedType(config)}` + parsed = config.params?.default + } + } else { + isValid = false + message = `Value does not match: ${getExpectedType(config)}` + } + + const result: ValidationResponse = { + isValid, + parsed, + } + + if (message) { + result.messages = [message] + } + + return result + }, + [ValidationTypes.FUNCTION]: ( + config: ValidationConfig, + value: unknown, + props: Record, + propertyPath: string, + ): ValidationResponse => { + const invalidResponse = { + isValid: false, + parsed: undefined, + messages: ['Failed to validate'], + } + if (config.params?.fnString && isString(config.params?.fnString)) { + try { + const { result } = evaluate( + config.params.fnString, + {}, + {}, + false, + undefined, + [value, props, _, moment, propertyPath], + ) + return result + } catch (e) { + log.error('Validation function error: ', { e }) + } + } + return invalidResponse + }, + [ValidationTypes.IMAGE_URL]: ( + config: ValidationConfig, + value: unknown, + props: Record, + ): ValidationResponse => { + const invalidResponse = { + isValid: false, + parsed: config.params?.default || '', + messages: [`${WIDGET_TYPE_VALIDATION_ERROR}: ${getExpectedType(config)}`], + } + const base64Regex = + /^(?:[A-Za-z\d+/]{4})*?(?:[A-Za-z\d+/]{2}(?:==)?|[A-Za-z\d+/]{3}=?)?$/ + const base64ImageRegex = /^data:image\/.*;base64/ + const imageUrlRegex = + /(http(s?):)([/|.|\w|\s|-])*\.(?:jpeg|jpg|gif|png)??(?:&?[^=&]*=[^=&]*)*/ + if ( + value === undefined || + value === null || + (isString(value) && value.trim().length === 0) + ) { + if (config.params?.required) return invalidResponse + return { isValid: true, parsed: value } + } + if (isString(value)) { + if (imageUrlRegex.test(value.trim())) { + return { isValid: true, parsed: value.trim() } + } + if (base64ImageRegex.test(value)) { + return { + isValid: true, + parsed: value, + } + } + if (base64Regex.test(value) && btoa(atob(value)) === value) { + return { isValid: true, parsed: `data:image/png;base64,${value}` } + } + } + return invalidResponse + }, + [ValidationTypes.SAFE_URL]: ( + config: ValidationConfig, + value: unknown, + ): ValidationResponse => { + const invalidResponse = { + isValid: false, + parsed: config?.params?.default || '', + messages: [`${WIDGET_TYPE_VALIDATION_ERROR}: ${getExpectedType(config)}`], + } + + if (typeof value === 'string' && getIsSafeURL(value)) { + return { + isValid: true, + parsed: value, + } + } else { + return invalidResponse + } + }, + + /** + * + * ARRAY_OF_TYPE_OR_TYPE can be used in scenarios where we wanted to validate + * using ValidationTypes.ARRAY or ValidationTypes.* at the same time. + * + * This is needed in case of properties inside + * 1. Table widget where we use COMPUTE_VALUE + * 2. Menu button widget where we use MENU_BUTTON_DYNAMIC_ITEMS + * + * For more info: https://github.com/appsmithorg/appsmith/pull/9396 + */ + [ValidationTypes.ARRAY_OF_TYPE_OR_TYPE]: ( + config: ValidationConfig, + value: unknown, + props: Record, + propertyPath: string, + ): ValidationResponse => { + if (!config.params?.type) + return { + isValid: false, + parsed: undefined, + messages: ['Invalid validation'], + } + + // Validate when JS mode is disabled + const result = VALIDATORS[config.params.type as ValidationTypes]( + config.params as ValidationConfig, + value, + props, + propertyPath, + ) + if (result.isValid) return result + + // Validate when JS mode is enabled + const resultValue = [] + if (_.isArray(value)) { + for (const item of value) { + const result = VALIDATORS[config.params.type]( + config.params as ValidationConfig, + item, + props, + propertyPath, + ) + if (!result.isValid) return result + resultValue.push(result.parsed) + } + } else { + return { + isValid: false, + parsed: config.params?.params?.default, + messages: result.messages, + } + } + + return { + isValid: true, + parsed: resultValue, + } + }, +} diff --git a/auxiliaries/code-editor/src/CodeEditor/works/tern/tern.worker.ts b/auxiliaries/code-editor/src/CodeEditor/works/Tern/tern.worker.ts similarity index 100% rename from auxiliaries/code-editor/src/CodeEditor/works/tern/tern.worker.ts rename to auxiliaries/code-editor/src/CodeEditor/works/Tern/tern.worker.ts diff --git a/auxiliaries/code-editor/src/CodeEditor/works/common/DataTreeEvaluator/index.ts b/auxiliaries/code-editor/src/CodeEditor/works/common/DataTreeEvaluator/index.ts new file mode 100644 index 0000000..f8a145f --- /dev/null +++ b/auxiliaries/code-editor/src/CodeEditor/works/common/DataTreeEvaluator/index.ts @@ -0,0 +1,1395 @@ +import { Diff, applyChange, diff } from 'deep-diff' +import { klona } from 'klona/full' +import { + difference, + flatten, + get, + isEmpty, + isFunction, + isObject, + set, + union, + unset, +} from 'lodash' +import { error as logError } from 'loglevel' +import toposort from 'toposort' + +import { DataTree } from '@modou/code-editor/CodeEditor/common/editor-config' +import { + EXECUTION_PARAM_KEY, + EXECUTION_PARAM_REFERENCE_REGEX, + THIS_DOT_PARAMS_KEY, +} from '@modou/code-editor/CodeEditor/constants/AppsmithActionConstants/ActionConstants' +import { + ActionValidationConfigMap, + ValidationConfig, +} from '@modou/code-editor/CodeEditor/constants/PropertyControlConstants' +import { DATA_BIND_REGEX } from '@modou/code-editor/CodeEditor/constants/bindings' +import { APP_MODE } from '@modou/code-editor/CodeEditor/entities/App' +import { + Severity, + SourceEntity, + UserLogObject, +} from '@modou/code-editor/CodeEditor/entities/AppsmithConsole' +import { + DataTreeAction, + DataTreeEntity, + DataTreeJSAction, + DataTreeWidget, +} from '@modou/code-editor/CodeEditor/entities/DataTree/dataTreeFactory' +import { + ENTITY_TYPE, + EvaluationSubstitutionType, + PrivateWidgets, +} from '@modou/code-editor/CodeEditor/entities/DataTree/types' +import { + DataTreeEvaluationProps, + DependencyMap, + EvalError, + EvalErrorTypes, + EvaluationError, + PropertyEvaluationErrorType, + getDynamicBindings, + getEntityDynamicBindingPathList, + getEvalErrorPath, + getEvalValuePath, + isChildPropertyPath, + isPathADynamicBinding, + isPathDynamicTrigger, +} from '@modou/code-editor/CodeEditor/utils/DynamicBindingUtils' +import { JSUpdate } from '@modou/code-editor/CodeEditor/utils/JSPaneUtils' +import { WidgetTypeConfigMap } from '@modou/code-editor/CodeEditor/utils/WidgetFactory' +import { + getAppMode, + getJSEntities, + getUpdatedLocalUnEvalTreeAfterJSUpdates, + parseJSActions, + parseJSActionsForViewMode, + parseJSActionsWithDifferences, +} from '@modou/code-editor/CodeEditor/works/Evaluation/JSObject' +import { isJSObjectFunction } from '@modou/code-editor/CodeEditor/works/Evaluation/JSObject/utils' +// eslint-disable-next-line max-len +import { substituteDynamicBindingWithValues } from '@modou/code-editor/CodeEditor/works/Evaluation/evaluationSubstitution' +import { + addDependantsOfNestedPropertyPaths, + convertPathToString, + getEntityNameAndPropertyPath, + getImmediateParentsOfPropertyPaths, + isAction, + isDynamicLeaf, + isJSAction, + isValidEntity, + isWidget, + overrideWidgetProperties, + translateDiffEventToDataTreeDiffEvent, + trimDependantChangePaths, +} from '@modou/code-editor/CodeEditor/works/Evaluation/evaluationUtils' +import { EvalMetaUpdates } from '@modou/code-editor/CodeEditor/works/common/DataTreeEvaluator/types' +import { getFixedTimeDifference } from '@modou/code-editor/CodeEditor/works/common/DataTreeEvaluator/utils' +import { + DataTreeDiff, + addErrorToEntityProperty, + getAllPaths, + getValidatedTree, + validateActionProperty, + validateAndParseWidgetProperty, +} from '@modou/code-editor/CodeEditor/works/common/DataTreeEvaluator/validationUtils' +import { + createDependencyMap, + updateDependencyMap, +} from '@modou/code-editor/CodeEditor/works/common/DependencyMap' + +import evaluateSync, { + EvalResult, + EvaluateContext, + evaluateAsync, +} from '../../Evaluation/evaluate' + +type SortedDependencies = string[] +export class CrashingError extends Error {} + +export interface EvalProps { + [entityName: string]: DataTreeEvaluationProps +} + +export default class DataTreeEvaluator { + /** + * dependencyMap: Maintains map of + */ + dependencyMap: DependencyMap = {} + sortedDependencies: SortedDependencies = [] + inverseDependencyMap: DependencyMap = {} + widgetConfigMap: WidgetTypeConfigMap = {} + evalTree: DataTree = {} + /** + * This contains raw evaluated value without any validation or parsing. + * This is used for revalidation as we do not store the raw validated value. + */ + unParsedEvalTree: DataTree = {} + allKeys: Record = {} + privateWidgets: PrivateWidgets = {} + oldUnEvalTree: DataTree = {} + errors: EvalError[] = [] + resolvedFunctions: Record = {} + currentJSCollectionState: Record = {} + logs: unknown[] = [] + userLogs: UserLogObject[] = [] + allActionValidationConfig?: { + [actionId: string]: ActionValidationConfigMap + } + + triggerFieldDependencyMap: DependencyMap = {} + /** Keeps track of all invalid references in bindings throughout the Application + * E.g. For binding {{unknownEntity.name + Api1.name}} in Button1.text, + * where Api1 is present in dataTree but unknownEntity is not, + * the map has a key-value pair of + * { + * "Button1.text": [unknownEntity.name] + * } + */ + invalidReferencesMap: DependencyMap = {} + /** + * Maintains dependency of paths to re-validate on evaluation of particular property path. + */ + validationDependencyMap: DependencyMap = {} + sortedValidationDependencies: SortedDependencies = [] + inverseValidationDependencyMap: DependencyMap = {} + + /** + * Sanitized eval values and errors + */ + evalProps: EvalProps = {} + public hasCyclicalDependency = false + parseJsActionsConfig = { + [APP_MODE.EDIT]: parseJSActions, + [APP_MODE.PUBLISHED]: parseJSActionsForViewMode, + } + + constructor( + widgetConfigMap: WidgetTypeConfigMap, + allActionValidationConfig?: { + [actionId: string]: ActionValidationConfigMap + }, + ) { + this.allActionValidationConfig = allActionValidationConfig + this.widgetConfigMap = widgetConfigMap + } + + getEvalTree() { + return this.evalTree + } + + setEvalTree(evalTree: DataTree) { + console.log('setEvalTreesetEvalTree', evalTree) + this.evalTree = evalTree + } + + getUnParsedEvalTree() { + return this.unParsedEvalTree + } + + setUnParsedEvalTree(unParsedEvalTree: DataTree) { + this.unParsedEvalTree = unParsedEvalTree + } + + /** + * Method to create all data required for linting and + * evaluation of the first tree + */ + setupFirstTree(unEvalTree: DataTree): { + jsUpdates: Record + evalOrder: string[] + lintOrder: string[] + } { + const totalFirstTreeSetupStartTime = performance.now() + // cloneDeep will make sure not to omit key which has value as undefined. + const firstCloneStartTime = performance.now() + let localUnEvalTree = klona(unEvalTree) + const firstCloneEndTime = performance.now() + + let jsUpdates: Record = {} + // parse js collection to get functions + // save current state of js collection action and variables to be added to uneval tree + // save functions in resolveFunctions (as functions) to be executed as functions are not allowed in evalTree + // and functions are saved in dataTree as strings + const currentAppMode: APP_MODE = getAppMode(localUnEvalTree) + jsUpdates = + this.parseJsActionsConfig[currentAppMode](this, localUnEvalTree) || {} + localUnEvalTree = getUpdatedLocalUnEvalTreeAfterJSUpdates( + jsUpdates, + localUnEvalTree, + ) + const allKeysGenerationStartTime = performance.now() + // set All keys + this.allKeys = getAllPaths(localUnEvalTree) + const allKeysGenerationEndTime = performance.now() + + const createDependencyMapStartTime = performance.now() + // Create dependency map + const { + dependencyMap, + invalidReferencesMap, + triggerFieldDependencyMap, + validationDependencyMap, + } = createDependencyMap(this, localUnEvalTree) + const createDependencyMapEndTime = performance.now() + + this.dependencyMap = dependencyMap + this.triggerFieldDependencyMap = triggerFieldDependencyMap + this.invalidReferencesMap = invalidReferencesMap + this.validationDependencyMap = validationDependencyMap + const sortDependenciesStartTime = performance.now() + // Sort + this.sortedDependencies = this.sortDependencies(this.dependencyMap) + this.sortedValidationDependencies = this.sortDependencies( + validationDependencyMap, + ) + const sortDependenciesEndTime = performance.now() + + const inverseDependencyGenerationStartTime = performance.now() + // Inverse + this.inverseDependencyMap = this.getInverseDependencyTree({ + dependencyMap, + sortedDependencies: this.sortedDependencies, + }) + this.inverseValidationDependencyMap = this.getInverseDependencyTree({ + dependencyMap: validationDependencyMap, + sortedDependencies: this.sortedValidationDependencies, + }) + const inverseDependencyGenerationEndTime = performance.now() + + const secondCloneStartTime = performance.now() + this.oldUnEvalTree = klona(localUnEvalTree) + const secondCloneEndTime = performance.now() + + const totalFirstTreeSetupEndTime = performance.now() + + const timeTakenForSetupFirstTree = { + total: getFixedTimeDifference( + totalFirstTreeSetupEndTime, + totalFirstTreeSetupStartTime, + ), + clone: getFixedTimeDifference( + firstCloneEndTime + secondCloneEndTime, + firstCloneStartTime + secondCloneStartTime, + ), + allKeys: getFixedTimeDifference( + allKeysGenerationEndTime, + allKeysGenerationStartTime, + ), + createDependencyMap: getFixedTimeDifference( + createDependencyMapEndTime, + createDependencyMapStartTime, + ), + sortDependencies: getFixedTimeDifference( + sortDependenciesEndTime, + sortDependenciesStartTime, + ), + inverseDependency: getFixedTimeDifference( + inverseDependencyGenerationEndTime, + inverseDependencyGenerationStartTime, + ), + } + this.logs.push({ timeTakenForSetupFirstTree }) + + return { + jsUpdates, + evalOrder: this.sortedDependencies, + lintOrder: this.sortedDependencies, + } + } + + evalAndValidateFirstTree(): { + evalTree: DataTree + evalMetaUpdates: EvalMetaUpdates + } { + const evaluationStartTime = performance.now() + // Evaluate + const { evalMetaUpdates, evaluatedTree } = this.evaluateTree( + this.oldUnEvalTree, + this.resolvedFunctions, + this.sortedDependencies, + ) + const evaluationEndTime = performance.now() + + const validationStartTime = performance.now() + // Validate Widgets + this.setEvalTree( + getValidatedTree(evaluatedTree, { + evalProps: this.evalProps, + }), + ) + const validationEndTime = performance.now() + + const timeTakenForEvalAndValidateFirstTree = { + evaluation: getFixedTimeDifference( + evaluationEndTime, + evaluationStartTime, + ), + validation: getFixedTimeDifference( + validationEndTime, + validationStartTime, + ), + } + this.logs.push({ timeTakenForEvalAndValidateFirstTree }) + + return { + evalTree: this.getEvalTree(), + evalMetaUpdates, + } + } + + updateLocalUnEvalTree(dataTree: DataTree) { + // add functions and variables to unevalTree + Object.keys(this.currentJSCollectionState).forEach((update) => { + const updates = this.currentJSCollectionState[update] + if (dataTree[update]) { + Object.keys(updates).forEach((key) => { + const data = get(dataTree, `${update}.${key}.data`, undefined) + if (isJSObjectFunction(dataTree, update, key)) { + set(dataTree, `${update}.${key}`, String(updates[key])) + set(dataTree, `${update}.${key}.data`, data) + } else { + set(dataTree, `${update}.${key}`, updates[key]) + } + }) + } + }) + } + + /** + * Method to create all data required for linting and + * evaluation of the updated tree + */ + + setupUpdateTree(unEvalTree: DataTree): { + unEvalUpdates: DataTreeDiff[] + evalOrder: string[] + lintOrder: string[] + jsUpdates: Record + nonDynamicFieldValidationOrder: string[] + } { + const totalUpdateTreeSetupStartTime = performance.now() + + let localUnEvalTree = Object.assign({}, unEvalTree) + let jsUpdates: Record = {} + const diffCheckTimeStartTime = performance.now() + // update uneval tree from previously saved current state of collection + this.updateLocalUnEvalTree(localUnEvalTree) + // get difference in js collection body to be parsed + const oldUnEvalTreeJSCollections = getJSEntities(this.oldUnEvalTree) + const localUnEvalTreeJSCollection = getJSEntities(localUnEvalTree) + const jsDifferences: Array< + Diff, Record> + > = diff(oldUnEvalTreeJSCollections, localUnEvalTreeJSCollection) ?? [] + const jsTranslatedDiffs = flatten( + jsDifferences.map((diff) => + translateDiffEventToDataTreeDiffEvent(diff, localUnEvalTree), + ), + ) + // save parsed functions in resolveJSFunctions, update current state of js collection + jsUpdates = + (!!jsTranslatedDiffs && !!this.oldUnEvalTree + ? parseJSActionsWithDifferences( + this, + localUnEvalTree, + jsTranslatedDiffs, + ) + : parseJSActions(this, localUnEvalTree)) || {} + // update local data tree if js body has updated (remove/update/add js functions or variables) + localUnEvalTree = getUpdatedLocalUnEvalTreeAfterJSUpdates( + jsUpdates || {}, + localUnEvalTree, + ) + + const differences: Array> = + diff(this.oldUnEvalTree, localUnEvalTree) ?? [] + // Since eval tree is listening to possible events that don't cause differences + // We want to check if no diffs are present and bail out early + if (differences.length === 0) { + return { + unEvalUpdates: [], + evalOrder: [], + lintOrder: [], + jsUpdates: {}, + nonDynamicFieldValidationOrder: [], + } + } + // find all differences which can lead to updating of dependency map + const translatedDiffs = flatten( + differences.map((diff) => + translateDiffEventToDataTreeDiffEvent(diff, localUnEvalTree), + ), + ) + const diffCheckTimeStopTime = performance.now() + this.logs.push({ + differences, + translatedDiffs, + }) + const updateDependencyStartTime = performance.now() + // Find all the paths that have changed as part of the difference and update the + // global dependency map if an existing dynamic binding has now become legal + const { dependenciesOfRemovedPaths, extraPathsToLint, removedPaths } = + updateDependencyMap({ + dataTreeEvalRef: this, + translatedDiffs, + unEvalDataTree: localUnEvalTree, + }) + const updateDependencyEndTime = performance.now() + + this.applyDifferencesToEvalTree({ differences, localUnEvalTree }) + + const calculateSortOrderStartTime = performance.now() + const subTreeSortOrder: string[] = this.calculateSubTreeSortOrder( + differences, + dependenciesOfRemovedPaths, + removedPaths, + localUnEvalTree, + ) + const calculateSortOrderEndTime = performance.now() + // Remove anything from the sort order that is not a dynamic leaf since only those need evaluation + const evaluationOrder: string[] = [] + let nonDynamicFieldValidationOrderSet = new Set() + + subTreeSortOrder.forEach((propertyPath) => { + // We are setting all values from our uneval tree to the old eval tree we have + // So that the actual uneval value can be evaluated + if (isDynamicLeaf(localUnEvalTree, propertyPath)) { + const unEvalPropValue = get(localUnEvalTree, propertyPath) + const evalPropValue = get(this.evalTree, propertyPath) + if (!isFunction(evalPropValue)) { + set(this.evalTree, propertyPath, unEvalPropValue) + } + evaluationOrder.push(propertyPath) + } else { + /** + * if the non dynamic value changes that should trigger revalidation like tabs. + * tabsObj then we store it in nonDynamicFieldValidationOrderSet + */ + if (this.inverseValidationDependencyMap[propertyPath]) { + nonDynamicFieldValidationOrderSet = new Set([ + ...nonDynamicFieldValidationOrderSet, + propertyPath, + ]) + } + } + }) + + this.logs.push({ + sortedDependencies: this.sortedDependencies, + inverse: this.inverseDependencyMap, + updatedDependencyMap: this.dependencyMap, + evaluationOrder, + }) + + // Remove any deleted paths from the eval tree + removedPaths.forEach((removedPath) => { + unset(this.evalTree, removedPath) + }) + + const cloneStartTime = performance.now() + // TODO: For some reason we are passing some reference which are getting mutated. + // Need to check why big api responses are getting split between two eval runs + this.oldUnEvalTree = klona(localUnEvalTree) + const cloneEndTime = performance.now() + + const totalUpdateTreeSetupEndTime = performance.now() + + const timeTakenForSetupUpdateTree = { + total: getFixedTimeDifference( + totalUpdateTreeSetupEndTime, + totalUpdateTreeSetupStartTime, + ), + updateDependencyMap: getFixedTimeDifference( + updateDependencyEndTime, + updateDependencyStartTime, + ), + calculateSubTreeSortOrder: getFixedTimeDifference( + calculateSortOrderEndTime, + calculateSortOrderStartTime, + ), + findDifferences: getFixedTimeDifference( + diffCheckTimeStopTime, + diffCheckTimeStartTime, + ), + clone: getFixedTimeDifference(cloneEndTime, cloneStartTime), + } + + this.logs.push({ timeTakenForSetupUpdateTree }) + + return { + unEvalUpdates: translatedDiffs, + evalOrder: evaluationOrder, + lintOrder: union(evaluationOrder, extraPathsToLint), + jsUpdates, + nonDynamicFieldValidationOrder: Array.from( + nonDynamicFieldValidationOrderSet, + ), + } + } + + evalAndValidateSubTree( + evaluationOrder: string[], + nonDynamicFieldValidationOrder: string[], + ): { + evalMetaUpdates: EvalMetaUpdates + } { + const evaluationStartTime = performance.now() + const { evalMetaUpdates, evaluatedTree: newEvalTree } = this.evaluateTree( + this.evalTree, + this.resolvedFunctions, + evaluationOrder, + { skipRevalidation: false }, + ) + const evaluationEndTime = performance.now() + const reValidateStartTime = performance.now() + this.reValidateTree(nonDynamicFieldValidationOrder, newEvalTree) + const reValidateEndTime = performance.now() + this.setEvalTree(newEvalTree) + const timeTakenForEvalAndValidateSubTree = { + evaluation: getFixedTimeDifference( + evaluationEndTime, + evaluationStartTime, + ), + revalidation: getFixedTimeDifference( + reValidateEndTime, + reValidateStartTime, + ), + } + this.logs.push({ timeTakenForEvalAndValidateSubTree }) + return { + evalMetaUpdates, + } + } + + getCompleteSortOrder(changes: string[], inverseMap: DependencyMap): string[] { + let finalSortOrder: string[] = [] + let computeSortOrder = true + // Initialize parents with the current sent of property paths that need to be evaluated + let parents = changes + let subSortOrderArray: string[] + while (computeSortOrder) { + // Get all the nodes that would be impacted by the evaluation of the nodes in parents array in sorted order + subSortOrderArray = this.getEvaluationSortOrder(parents, inverseMap) + + // Add all the sorted nodes in the final list + finalSortOrder = [...finalSortOrder, ...subSortOrderArray] + + parents = getImmediateParentsOfPropertyPaths(subSortOrderArray) + // If we find parents of the property paths in the sorted array, + // we should continue finding all the nodes dependent + // on the parents + computeSortOrder = parents.length > 0 + } + + // Remove duplicates from this list. Since we explicitly walk down + // the tree and implicitly (by fetching parents) walk + // up the tree, there are bound to be many duplicates. + const uniqueKeysInSortOrder = new Set(finalSortOrder) + + // if a property path evaluation gets triggered by diff top order changes + // this could lead to incorrect sort order in spite of the bfs traversal + const sortOrderPropertyPaths: string[] = [] + this.sortedDependencies.forEach((path) => { + if (uniqueKeysInSortOrder.has(path)) { + sortOrderPropertyPaths.push(path) + // remove from the uniqueKeysInSortOrder + uniqueKeysInSortOrder.delete(path) + } + }) + // Add any remaining paths in the uniqueKeysInSortOrder + const completeSortOrder = [ + ...Array.from(uniqueKeysInSortOrder), + ...sortOrderPropertyPaths, + ] + + // Trim this list to now remove the property paths which are simply entity names + const finalSortOrderArray: string[] = [] + completeSortOrder.forEach((propertyPath) => { + const lastIndexOfDot = propertyPath.lastIndexOf('.') + // Only do this for property paths and not the entity themselves + if (lastIndexOfDot !== -1) { + finalSortOrderArray.push(propertyPath) + } + }) + + return finalSortOrderArray + } + + getEvaluationSortOrder( + changes: string[], + inverseMap: DependencyMap, + ): string[] { + const sortOrder: string[] = [...changes] + let iterator = 0 + while (iterator < sortOrder.length) { + // Find all the nodes who are to be evaluated when sortOrder[iterator] changes + const newNodes = inverseMap[sortOrder[iterator]] + + // If we find more nodes that would be impacted by the evaluation of the node being investigated + // we add these to the sort order. + if (newNodes) { + newNodes.forEach((toBeEvaluatedNode) => { + // Only add the nodes if they haven't been already added for evaluation in the list. Since we are doing + // breadth first traversal, we should be safe in not changing the evaluation order and adding this now at this + // point instead of the previous index found. + if (!sortOrder.includes(toBeEvaluatedNode)) { + sortOrder.push(toBeEvaluatedNode) + } + }) + } + iterator++ + } + return sortOrder + } + + getPrivateWidgets(dataTree: DataTree): PrivateWidgets { + let privateWidgets: PrivateWidgets = {} + Object.keys(dataTree).forEach((entityName) => { + const entity = dataTree[entityName] + if (isWidget(entity) && !isEmpty(entity.privateWidgets)) { + privateWidgets = { + ...privateWidgets, + ...entity.privateWidgets, + } + } + }) + return privateWidgets + } + + evaluateTree( + oldUnevalTree: DataTree, + resolvedFunctions: Record, + sortedDependencies: string[], + option = { skipRevalidation: true }, + ): { + evaluatedTree: DataTree + evalMetaUpdates: EvalMetaUpdates + } { + const tree = klona(oldUnevalTree) + const evalMetaUpdates: EvalMetaUpdates = [] + try { + const evaluatedTree = sortedDependencies.reduce( + (currentTree: DataTree, fullPropertyPath: string) => { + const { entityName, propertyPath } = + getEntityNameAndPropertyPath(fullPropertyPath) + const entity = currentTree[entityName] as + | DataTreeWidget + | DataTreeAction + const unEvalPropertyValue = get(currentTree as any, fullPropertyPath) + + const isADynamicBindingPath = + (isAction(entity) || isWidget(entity) || isJSAction(entity)) && + isPathADynamicBinding(entity, propertyPath) + const isATriggerPath = + isWidget(entity) && isPathDynamicTrigger(entity, propertyPath) + let evalPropertyValue + const requiresEval = + isADynamicBindingPath && + !isATriggerPath && + (isDynamicValue(unEvalPropertyValue) || isJSAction(entity)) + if (propertyPath) { + set(this.evalProps, getEvalErrorPath(fullPropertyPath), []) + } + if (requiresEval) { + const evaluationSubstitutionType = + entity.reactivePaths[propertyPath] || + EvaluationSubstitutionType.TEMPLATE + + const contextData: EvaluateContext = {} + if (isAction(entity)) { + contextData.thisContext = { + params: {}, + } + } + try { + evalPropertyValue = this.getDynamicValue( + unEvalPropertyValue, + currentTree, + resolvedFunctions, + evaluationSubstitutionType, + contextData, + undefined, + fullPropertyPath, + ) + } catch (error) { + this.errors.push({ + type: EvalErrorTypes.EVAL_PROPERTY_ERROR, + message: (error as Error).message, + context: { + propertyPath: fullPropertyPath, + }, + }) + evalPropertyValue = undefined + } + } else { + evalPropertyValue = unEvalPropertyValue + } + if (isWidget(entity) && !isATriggerPath) { + if (propertyPath) { + const parsedValue = validateAndParseWidgetProperty({ + fullPropertyPath, + widget: entity, + currentTree, + evalPropertyValue, + unEvalPropertyValue, + evalProps: this.evalProps, + }) + + this.setParsedValue({ + currentTree, + entity, + evalMetaUpdates, + fullPropertyPath, + parsedValue, + propertyPath, + evalPropertyValue, + }) + + if (!option.skipRevalidation) { + this.reValidateWidgetDependentProperty({ + fullPropertyPath, + widget: entity, + currentTree, + }) + } + + return currentTree + } + return set(currentTree, fullPropertyPath, evalPropertyValue) + } else if (isATriggerPath) { + return currentTree + } else if (isAction(entity)) { + if (this.allActionValidationConfig) { + const configProperty = propertyPath.replace( + 'config', + 'actionConfiguration', + ) + const validationConfig = + !!this.allActionValidationConfig[entity.actionId] && + this.allActionValidationConfig[entity.actionId][configProperty] + if (!!validationConfig && !isEmpty(validationConfig)) { + this.validateActionProperty( + fullPropertyPath, + entity, + currentTree, + evalPropertyValue, + unEvalPropertyValue, + validationConfig, + ) + } + } + + if (!propertyPath) return currentTree + set( + this.evalProps, + getEvalValuePath(fullPropertyPath), + evalPropertyValue, + ) + set(currentTree, fullPropertyPath, evalPropertyValue) + return currentTree + } else if (isJSAction(entity)) { + const variableList: string[] = get(entity, 'variables') || [] + if (variableList.includes(propertyPath)) { + const currentEvaluatedValue = get( + this.evalProps, + getEvalValuePath(fullPropertyPath, { + isPopulated: true, + fullPath: true, + }), + ) + if (!currentEvaluatedValue) { + set( + this.evalProps, + getEvalValuePath(fullPropertyPath, { + isPopulated: true, + fullPath: true, + }), + evalPropertyValue, + ) + set(currentTree, fullPropertyPath, evalPropertyValue) + } else { + set(currentTree, fullPropertyPath, currentEvaluatedValue) + } + } + return currentTree + } else { + return set(currentTree, fullPropertyPath, evalPropertyValue) + } + }, + tree, + ) + return { evaluatedTree, evalMetaUpdates } + } catch (error) { + this.errors.push({ + type: EvalErrorTypes.EVAL_TREE_ERROR, + message: (error as Error).message, + }) + return { evaluatedTree: tree, evalMetaUpdates } + } + } + + setAllActionValidationConfig(allActionValidationConfig: { + [actionId: string]: ActionValidationConfigMap + }): void { + this.allActionValidationConfig = allActionValidationConfig + } + + sortDependencies( + dependencyMap: DependencyMap, + diffs?: Array, + ): string[] { + /** + * dependencyTree : Array<[Node, dependentNode]> + */ + const dependencyTree: Array<[string, string]> = [] + Object.keys(dependencyMap).forEach((key: string) => { + if (dependencyMap[key].length) { + dependencyMap[key].forEach((dep) => dependencyTree.push([key, dep])) + } else { + // Set no dependency + dependencyTree.push([key, '']) + } + }) + + try { + return toposort(dependencyTree) + .reverse() + .filter((d) => !!d) + } catch (error) { + // Cyclic dependency found. Extract all node and entity type + const cyclicNodes = (error as Error).message.match( + /Cyclic dependency, node was:"(.*)"/, + ) + + const node = cyclicNodes?.length ? cyclicNodes[1] : '' + + let entityType = 'UNKNOWN' + const entityName = node.split('.')[0] + const entity = get(this.oldUnEvalTree, entityName) + if (entity && isWidget(entity)) { + entityType = entity.type + } else if (entity && isAction(entity)) { + entityType = entity.pluginType + } else if (entity && isJSAction(entity)) { + entityType = entity.ENTITY_TYPE + } + this.errors.push({ + type: EvalErrorTypes.CYCLICAL_DEPENDENCY_ERROR, + message: 'Cyclic dependency found while evaluating.', + context: { + node, + entityType, + dependencyMap, + diffs, + }, + }) + logError('CYCLICAL DEPENDENCY MAP', dependencyMap) + this.hasCyclicalDependency = true + throw new CrashingError((error as Error).message) + } + } + + getDynamicValue( + dynamicBinding: string, + data: DataTree, + resolvedFunctions: Record, + evaluationSubstitutionType: EvaluationSubstitutionType, + contextData?: EvaluateContext, + callBackData?: any[], + fullPropertyPath?: string, + ) { + // Get the {{binding}} bound values + let entity: DataTreeEntity | undefined + let propertyPath: string + if (fullPropertyPath) { + const entityName = fullPropertyPath.split('.')[0] + propertyPath = fullPropertyPath.split('.')[1] + entity = data[entityName] + } + // Get the {{binding}} bound values + const { jsSnippets, stringSegments } = getDynamicBindings( + dynamicBinding, + entity, + ) + console.log( + 'getDynamicBindingsgetDynamicBindings', + jsSnippets, + stringSegments, + ) + if (stringSegments.length) { + // Get the Data Tree value of those "binding "paths + const values = jsSnippets.map((jsSnippet, index) => { + const toBeSentForEval = + entity && isJSAction(entity) && propertyPath === 'body' + ? jsSnippet.replace(/export default/g, '') + : jsSnippet + if (jsSnippet) { + const result = this.evaluateDynamicBoundValue( + toBeSentForEval, + data, + resolvedFunctions, + !!entity && isJSAction(entity), + contextData, + callBackData, + fullPropertyPath?.includes('body') ?? + !toBeSentForEval.includes('console.'), + ) + if (fullPropertyPath && result.errors.length) { + addErrorToEntityProperty({ + errors: result.errors, + evalProps: this.evalProps, + fullPropertyPath, + dataTree: data, + }) + } + // if there are any console outputs found from the evaluation, extract them and add them to the logs array + if ( + !!entity && + !!result.logs && + result.logs.length > 0 && + !propertyPath.includes('body') + ) { + let type = ENTITY_TYPE.WIDGET + let id = '' + + // extracting the id and type of the entity from the entity for logs object + if (isWidget(entity)) { + type = ENTITY_TYPE.WIDGET + id = entity.widgetId + } else if (isAction(entity)) { + type = ENTITY_TYPE.ACTION + id = entity.actionId + } else if (isJSAction(entity)) { + type = ENTITY_TYPE.JSACTION + id = entity.actionId + } + + // This is the object that will help to associate the log with the origin entity + const source: SourceEntity = { + type, + name: fullPropertyPath?.split('.')[0] ?? 'Widget', + id, + } + this.userLogs.push({ + logObject: result.logs, + source, + }) + } + return result.result + } else { + return stringSegments[index] + } + }) + + // We don't need to substitute template of the result if only one binding exists + // But it should not be of prepared statements since that does need a string + if ( + stringSegments.length === 1 && + evaluationSubstitutionType !== EvaluationSubstitutionType.PARAMETER + ) { + return values[0] + } + try { + // else return a combined value according to the evaluation type + return substituteDynamicBindingWithValues( + dynamicBinding, + stringSegments, + values, + evaluationSubstitutionType, + ) + } catch (error) { + if (fullPropertyPath) { + addErrorToEntityProperty({ + errors: [ + { + raw: dynamicBinding, + errorType: PropertyEvaluationErrorType.PARSE, + errorMessage: (error as Error).message, + severity: Severity.ERROR, + }, + ], + evalProps: this.evalProps, + fullPropertyPath, + dataTree: data, + }) + } + return undefined + } + } + return undefined + } + + async evaluateTriggers( + userScript: string, + dataTree: DataTree, + requestId: string, + resolvedFunctions: Record, + callbackData: unknown[], + context?: EvaluateContext, + ) { + const { jsSnippets } = getDynamicBindings(userScript) + return evaluateAsync( + jsSnippets[0] || userScript, + dataTree, + requestId, + resolvedFunctions, + context, + callbackData, + ) + } + + // Paths are expected to have "{name}.{path}" signature + // Also returns any action triggers found after evaluating value + evaluateDynamicBoundValue( + js: string, + data: DataTree, + resolvedFunctions: Record, + createGlobalData: boolean, + contextData?: EvaluateContext, + callbackData?: any[], + skipUserLogsOperations = false, + ): EvalResult { + try { + return evaluateSync( + js, + data, + resolvedFunctions, + createGlobalData, + contextData, + callbackData, + skipUserLogsOperations, + ) + } catch (error) { + return { + result: undefined, + errors: [ + { + errorType: PropertyEvaluationErrorType.PARSE, + raw: js, + severity: Severity.ERROR, + errorMessage: (error as Error).message, + }, + ], + } + } + } + + setParsedValue({ + currentTree, + entity, + evalMetaUpdates, + evalPropertyValue, + fullPropertyPath, + parsedValue, + propertyPath, + }: { + currentTree: DataTree + entity: DataTreeWidget + evalMetaUpdates: EvalMetaUpdates + fullPropertyPath: string + parsedValue: unknown + propertyPath: string + evalPropertyValue: unknown + }) { + const overwriteObj = overrideWidgetProperties({ + entity, + propertyPath, + value: parsedValue, + currentTree, + evalMetaUpdates, + }) + + if (overwriteObj?.overwriteParsedValue) { + parsedValue = overwriteObj.newValue + } + // setting parseValue in dataTree + set(currentTree, fullPropertyPath, parsedValue) + // setting evalPropertyValue in unParsedEvalTree + set(this.getUnParsedEvalTree(), fullPropertyPath, evalPropertyValue) + } + + reValidateWidgetDependentProperty({ + currentTree, + fullPropertyPath, + widget, + }: { + fullPropertyPath: string + widget: DataTreeWidget + currentTree: DataTree + }) { + if (this.inverseValidationDependencyMap[fullPropertyPath]) { + const pathsToRevalidate = + this.inverseValidationDependencyMap[fullPropertyPath] + pathsToRevalidate.forEach((fullPath) => { + validateAndParseWidgetProperty({ + fullPropertyPath: fullPath, + widget, + currentTree, + // we supply non-transformed evaluated value + evalPropertyValue: get(this.getUnParsedEvalTree(), fullPath), + unEvalPropertyValue: get( + this.oldUnEvalTree, + fullPath, + ) as unknown as string, + evalProps: this.evalProps, + }) + }) + } + } + + reValidateTree( + nonDynamicFieldValidationOrder: string[], + currentTree: DataTree, + ) { + nonDynamicFieldValidationOrder.forEach((fullPropertyPath) => { + const { entityName, propertyPath } = + getEntityNameAndPropertyPath(fullPropertyPath) + const entity = currentTree[entityName] + if (isWidget(entity) && !isPathDynamicTrigger(entity, propertyPath)) { + this.reValidateWidgetDependentProperty({ + widget: entity, + fullPropertyPath, + currentTree, + }) + } + }) + } + + // validates the user input saved as action property based on a validationConfig + validateActionProperty( + fullPropertyPath: string, + action: DataTreeAction, + currentTree: DataTree, + evalPropertyValue: any, + unEvalPropertyValue: string, + validationConfig: ValidationConfig, + ) { + if (evalPropertyValue && validationConfig) { + // runs VALIDATOR function and returns errors + const { isValid, messages } = validateActionProperty( + validationConfig, + evalPropertyValue, + ) + if (!isValid) { + const evalErrors: EvaluationError[] = + messages?.map((message: string) => { + return { + raw: unEvalPropertyValue, + errorMessage: message || '', + errorType: PropertyEvaluationErrorType.VALIDATION, + severity: Severity.ERROR, + } + }) ?? [] + // saves error in dataTree at fullPropertyPath + // Later errors can consumed by the forms and debugger + addErrorToEntityProperty({ + errors: evalErrors, + evalProps: this.evalProps, + fullPropertyPath, + dataTree: currentTree, + }) + } + } + } + + /** + * Update the entity config set as prototype according to + * latest unEvalTree changes else code would consume stale configs. + * + * Example scenario: On addition of a JS binding to widget, + * it's dynamicBindingPathList changes and needs to be updated. + */ + updateConfigForModifiedEntity(unEvalTree: DataTree, entityName: string) { + const unEvalEntity = unEvalTree[entityName] + // skip entity if entity is not present in the evalTree or is not a valid entity + if (!this.evalTree[entityName] || !isValidEntity(this.evalTree[entityName])) + return + const entityConfig = Object.getPrototypeOf(unEvalEntity) + const newEntityObject = Object.create(entityConfig) + this.evalTree[entityName] = Object.assign(newEntityObject, { + ...this.evalTree[entityName], + }) + } + + applyDifferencesToEvalTree({ + differences, + localUnEvalTree, + }: { + differences: Array> + localUnEvalTree: DataTree + }) { + for (const d of differences) { + if (!Array.isArray(d.path) || d.path.length === 0) continue // Null check for typescript + // Apply the changes into the evalTree so that it gets the latest changes + applyChange(this.evalTree, undefined, d) + const { entityName } = getEntityNameAndPropertyPath(d.path.join('.')) + this.updateConfigForModifiedEntity(localUnEvalTree, entityName) + } + } + + calculateSubTreeSortOrder( + differences: Array>, + dependenciesOfRemovedPaths: string[], + removedPaths: string[], + unEvalTree: DataTree, + ) { + const changePaths: Set = new Set(dependenciesOfRemovedPaths) + for (const d of differences) { + if (!Array.isArray(d.path) || d.path.length === 0) continue // Null check for typescript + changePaths.add(convertPathToString(d.path)) + // If this is a property path change, simply add for evaluation and move on + if (!isDynamicLeaf(unEvalTree, convertPathToString(d.path))) { + // A parent level property has been added or deleted + /** + * We want to add all pre-existing dynamic and static bindings in dynamic paths + * of this entity to get evaluated and validated. + * Example: + * - Table1.tableData = {{Api1.data}} + * - Api1 gets created. + * - This function gets called with a diff {path:["Api1"]} + * We want to add `Api.data` to changedPaths so that `Table1.tableData` can be discovered below. + */ + const entityName = d.path[0] + const entity = unEvalTree[entityName] + if (!entity) { + continue + } + if (!isAction(entity) && !isWidget(entity) && !isJSAction(entity)) { + continue + } + let entityDynamicBindingPaths: string[] = [] + if (isAction(entity)) { + const entityDynamicBindingPathList = + getEntityDynamicBindingPathList(entity) + entityDynamicBindingPaths = entityDynamicBindingPathList.map( + (path) => { + return path.key + }, + ) + } + const parentPropertyPath = convertPathToString(d.path) + Object.keys(entity.reactivePaths).forEach((relativePath) => { + const childPropertyPath = `${entityName}.${relativePath}` + // Check if relative path has dynamic binding + if ( + entityDynamicBindingPaths?.length && + entityDynamicBindingPaths.includes(relativePath) + ) { + changePaths.add(childPropertyPath) + } + if (isChildPropertyPath(parentPropertyPath, childPropertyPath)) { + changePaths.add(childPropertyPath) + } + }) + } + } + + // If a nested property path has changed and someone (say x) is dependent on the parent of the said property, + // x must also be evaluated. For example, the following relationship exists in dependency map: + // < "Input1.defaultText" : ["Table1.selectedRow.email"] > + // If Table1.selectedRow has changed, then Input1.defaultText must also be + // evaluated because Table1.selectedRow.email + // is a nested property of Table1.selectedRow + const changePathsWithNestedDependants = addDependantsOfNestedPropertyPaths( + Array.from(changePaths), + this.inverseDependencyMap, + ) + + const trimmedChangedPaths = trimDependantChangePaths( + changePathsWithNestedDependants, + this.dependencyMap, + ) + + // Now that we have all the root nodes which have to be evaluated, recursively find all the other paths which + // would get impacted because they are dependent on the said root nodes and add them in order + const completeSortOrder = this.getCompleteSortOrder( + trimmedChangedPaths, + this.inverseDependencyMap, + ) + // Remove any paths that do not exist in the data tree anymore + return difference(completeSortOrder, removedPaths) + } + + getInverseDependencyTree( + params = { + dependencyMap: this.dependencyMap, + sortedDependencies: this.sortedDependencies, + }, + ): DependencyMap { + const { dependencyMap, sortedDependencies } = params + const inverseDependencyMap: DependencyMap = {} + sortedDependencies.forEach((propertyPath) => { + const incomingEdges: string[] = dependencyMap[propertyPath] + if (incomingEdges) { + incomingEdges.forEach((edge) => { + const node = inverseDependencyMap[edge] + if (node) { + node.push(propertyPath) + } else { + inverseDependencyMap[edge] = [propertyPath] + } + }) + } + }) + return inverseDependencyMap + } + + evaluateActionBindings( + bindings: string[], + executionParams?: Record | string, + ) { + // We might get execution params as an object or as a string. + // If the user has added a proper object (valid case) it will be an object + // If they have not added any execution params or not an object + // it would be a string (invalid case) + let evaluatedExecutionParams: Record = {} + if (executionParams && isObject(executionParams)) { + evaluatedExecutionParams = this.getDynamicValue( + `{{${JSON.stringify(executionParams)}}}`, + this.evalTree, + this.resolvedFunctions, + EvaluationSubstitutionType.TEMPLATE, + ) + } + + return bindings.map((binding) => { + // Replace any reference of 'this.params' to 'executionParams' (backwards compatibility) + // also helps with dealing with IIFE which are normal functions (not arrow) + // because normal functions won't retain 'this' context (when executed elsewhere) + const replacedBinding = binding.replace( + EXECUTION_PARAM_REFERENCE_REGEX, + EXECUTION_PARAM_KEY, + ) + return this.getDynamicValue( + `{{${replacedBinding}}}`, + this.evalTree, + this.resolvedFunctions, + EvaluationSubstitutionType.TEMPLATE, + // params can be accessed via "this.params" or "executionParams" + { + thisContext: { + [THIS_DOT_PARAMS_KEY]: evaluatedExecutionParams, + }, + globalContext: { + [EXECUTION_PARAM_KEY]: evaluatedExecutionParams, + }, + }, + ) + }) + } + + clearErrors() { + this.errors = [] + } + + clearLogs() { + this.logs = [] + this.userLogs = [] + } +} + +// TODO cryptic comment below. Dont know if we still need this. Duplicate function +// referencing DATA_BIND_REGEX fails for the value "{{Table1.tableData[Table1.selectedRowIndex]}}" +// if you run it multiple times and don't recreate +const isDynamicValue = (value: string): boolean => DATA_BIND_REGEX.test(value) diff --git a/auxiliaries/code-editor/src/CodeEditor/works/common/DataTreeEvaluator/types.ts b/auxiliaries/code-editor/src/CodeEditor/works/common/DataTreeEvaluator/types.ts new file mode 100644 index 0000000..c2d7713 --- /dev/null +++ b/auxiliaries/code-editor/src/CodeEditor/works/common/DataTreeEvaluator/types.ts @@ -0,0 +1,5 @@ +export type EvalMetaUpdates = Array<{ + widgetId: string + metaPropertyPath: string[] + value: unknown +}> diff --git a/auxiliaries/code-editor/src/CodeEditor/works/common/DataTreeEvaluator/utils.ts b/auxiliaries/code-editor/src/CodeEditor/works/common/DataTreeEvaluator/utils.ts new file mode 100644 index 0000000..0a6c8bc --- /dev/null +++ b/auxiliaries/code-editor/src/CodeEditor/works/common/DataTreeEvaluator/utils.ts @@ -0,0 +1,3 @@ +export function getFixedTimeDifference(endTime: number, startTime: number) { + return (endTime - startTime).toFixed(2) + ' ms' +} diff --git a/auxiliaries/code-editor/src/CodeEditor/works/common/DataTreeEvaluator/validationUtils.ts b/auxiliaries/code-editor/src/CodeEditor/works/common/DataTreeEvaluator/validationUtils.ts new file mode 100644 index 0000000..bed3eb5 --- /dev/null +++ b/auxiliaries/code-editor/src/CodeEditor/works/common/DataTreeEvaluator/validationUtils.ts @@ -0,0 +1,246 @@ +import { get, isEmpty, isUndefined, set } from 'lodash' + +import { DataTree } from '@modou/code-editor/CodeEditor/common/editor-config' +import { ValidationConfig } from '@modou/code-editor/CodeEditor/constants/PropertyControlConstants' +import { Severity } from '@modou/code-editor/CodeEditor/entities/AppsmithConsole' +import { DataTreeWidget } from '@modou/code-editor/CodeEditor/entities/DataTree/dataTreeFactory' +import { PrivateWidgets } from '@modou/code-editor/CodeEditor/entities/DataTree/types' +import { + EVAL_ERROR_PATH, + EvaluationError, + PropertyEvaluationErrorType, + getEvalErrorPath, + getEvalValuePath, + isPathDynamicTrigger, +} from '@modou/code-editor/CodeEditor/utils/DynamicBindingUtils' +import { + getEntityNameAndPropertyPath, + isWidget, + resetValidationErrorsForEntityProperty, +} from '@modou/code-editor/CodeEditor/works/Evaluation/evaluationUtils' +import { validate } from '@modou/code-editor/CodeEditor/works/Evaluation/validations' +import { EvalProps } from '@modou/code-editor/CodeEditor/works/common/DataTreeEvaluator/index' + +export enum DataTreeDiffEvent { + NEW = 'NEW', + DELETE = 'DELETE', + EDIT = 'EDIT', + NOOP = 'NOOP', +} +export interface DataTreeDiff { + payload: { + propertyPath: string + value?: string + } + event: DataTreeDiffEvent +} +export const getAllPrivateWidgetsInDataTree = ( + dataTree: DataTree, +): PrivateWidgets => { + let privateWidgets: PrivateWidgets = {} + + Object.keys(dataTree).forEach((entityName) => { + const entity = dataTree[entityName] + if (isWidget(entity) && !isEmpty(entity.privateWidgets)) { + privateWidgets = { ...privateWidgets, ...entity.privateWidgets } + } + }) + + return privateWidgets +} + +export const addErrorToEntityProperty = ({ + dataTree, + errors, + evalProps, + fullPropertyPath, +}: { + errors: EvaluationError[] + dataTree: DataTree + fullPropertyPath: string + evalProps: EvalProps +}) => { + const { entityName, propertyPath } = + getEntityNameAndPropertyPath(fullPropertyPath) + const isPrivateEntityPath = + getAllPrivateWidgetsInDataTree(dataTree)[entityName] + const logBlackList = get(dataTree, `${entityName}.logBlackList`, {}) + if (propertyPath && !(propertyPath in logBlackList) && !isPrivateEntityPath) { + const errorPath = `${entityName}.${EVAL_ERROR_PATH}['${propertyPath}']` + const existingErrors = get(evalProps, errorPath, []) as EvaluationError[] + set(evalProps, errorPath, existingErrors.concat(errors)) + } + + return dataTree +} +export function validateAndParseWidgetProperty({ + currentTree, + evalPropertyValue, + evalProps, + fullPropertyPath, + unEvalPropertyValue, + widget, +}: { + fullPropertyPath: string + widget: DataTreeWidget + currentTree: DataTree + evalPropertyValue: unknown + unEvalPropertyValue: string + evalProps: EvalProps +}): unknown { + const { propertyPath } = getEntityNameAndPropertyPath(fullPropertyPath) + if (isPathDynamicTrigger(widget, propertyPath)) { + // TODO find a way to validate triggers + return unEvalPropertyValue + } + const validation = widget.validationPaths[propertyPath] + + const { isValid, messages, parsed, transformed } = validateWidgetProperty( + validation, + evalPropertyValue, + widget, + propertyPath, + ) + + let evaluatedValue + if (isValid) { + evaluatedValue = parsed + // remove validation errors is already present + resetValidationErrorsForEntityProperty({ + evalProps, + fullPropertyPath, + }) + } else { + evaluatedValue = isUndefined(transformed) ? evalPropertyValue : transformed + + const evalErrors: EvaluationError[] = + messages?.map((message) => { + return { + raw: unEvalPropertyValue, + errorMessage: message || '', + errorType: PropertyEvaluationErrorType.VALIDATION, + severity: Severity.ERROR, + } + }) ?? [] + // Add validation errors + addErrorToEntityProperty({ + errors: evalErrors, + evalProps, + fullPropertyPath, + dataTree: currentTree, + }) + } + set( + evalProps, + getEvalValuePath(fullPropertyPath, { + isPopulated: false, + fullPath: true, + }), + evaluatedValue, + ) + + return parsed +} + +export function validateWidgetProperty( + config: ValidationConfig, + value: unknown, + props: Record, + propertyPath: string, +) { + if (!config) { + return { + isValid: true, + parsed: value, + } + } + return validate(config, value, props, propertyPath) +} + +export function validateActionProperty( + config: ValidationConfig, + value: unknown, +) { + if (!config) { + return { + isValid: true, + parsed: value, + } + } + return validate(config, value, {}, '') +} + +export function getValidatedTree( + tree: DataTree, + option: { evalProps: EvalProps }, +) { + const { evalProps } = option + return Object.keys(tree).reduce((tree, entityKey: string) => { + const parsedEntity = tree[entityKey] + if (!isWidget(parsedEntity)) { + return tree + } + + Object.entries(parsedEntity.validationPaths).forEach( + ([property, validation]) => { + const value = get(parsedEntity, property) + // Pass it through parse + const { isValid, messages, parsed, transformed } = + validateWidgetProperty(validation, value, parsedEntity, property) + set(parsedEntity, property, parsed) + const evaluatedValue = isValid + ? parsed + : isUndefined(transformed) + ? value + : transformed + set( + evalProps, + getEvalValuePath(`${entityKey}.${property}`, { + isPopulated: false, + fullPath: true, + }), + evaluatedValue, + ) + if (!isValid) { + const evalErrors: EvaluationError[] = + messages?.map((message) => ({ + errorType: PropertyEvaluationErrorType.VALIDATION, + errorMessage: message, + severity: Severity.ERROR, + raw: value, + })) ?? [] + addErrorToEntityProperty({ + errors: evalErrors, + evalProps, + fullPropertyPath: getEvalErrorPath(`${entityKey}.${property}`, { + isPopulated: false, + fullPath: true, + }), + dataTree: tree, + }) + } + }, + ) + return { ...tree, [entityKey]: parsedEntity } + }, tree) +} +export const getAllPaths = ( + records: any, + curKey = '', + result: Record = {}, +): Record => { + // Add the key if it exists + if (curKey) result[curKey] = true + if (Array.isArray(records)) { + for (let i = 0; i < records.length; i++) { + const tempKey = curKey ? `${curKey}[${i}]` : `${i}` + getAllPaths(records[i], tempKey, result) + } + } else if (typeof records === 'object' && records) { + for (const key of Object.keys(records)) { + const tempKey = curKey ? `${curKey}.${key}` : `${key}` + getAllPaths(records[key], tempKey, result) + } + } + return result +} diff --git a/auxiliaries/code-editor/src/CodeEditor/works/common/DependencyMap/index.ts b/auxiliaries/code-editor/src/CodeEditor/works/common/DependencyMap/index.ts new file mode 100644 index 0000000..764cb91 --- /dev/null +++ b/auxiliaries/code-editor/src/CodeEditor/works/common/DependencyMap/index.ts @@ -0,0 +1,774 @@ +import { difference } from 'lodash' + +import { DataTree } from '@modou/code-editor/CodeEditor/common/editor-config' +import { + DataTreeAction, + DataTreeJSAction, + DataTreeWidget, +} from '@modou/code-editor/CodeEditor/entities/DataTree/dataTreeFactory' +import { + DependencyMap, + getDynamicBindings, + getPropertyPath, + isChildPropertyPath, + isPathADynamicBinding, + isPathDynamicTrigger, +} from '@modou/code-editor/CodeEditor/utils/DynamicBindingUtils' +import DataTreeEvaluator from '@modou/code-editor/CodeEditor/works/common/DataTreeEvaluator' +import { + DataTreeDiff, + DataTreeDiffEvent, + getAllPaths, +} from '@modou/code-editor/CodeEditor/works/common/DataTreeEvaluator/validationUtils' + +import { + getEntityNameAndPropertyPath, + isAction, + isDynamicLeaf, + isJSAction, + isValidEntity, + isWidget, + makeParentsDependOnChildren, +} from '../../Evaluation/evaluationUtils' +import { + extractInfoFromBindings, + extractInfoFromReferences, + listEntityDependencies, + listTriggerFieldDependencies, + listValidationDependencies, + mergeArrays, +} from './utils' + +interface CreateDependencyMap { + dependencyMap: DependencyMap + triggerFieldDependencyMap: DependencyMap + /** Keeps track of all invalid references present in bindings throughout the page. + * We keep this list so that we don't have to traverse the entire dataTree when + * a new entity or path is added to the datatree in order to determine if an old invalid reference has become valid + * because an entity or path is newly added. + * */ + invalidReferencesMap: DependencyMap + validationDependencyMap: DependencyMap +} + +export function createDependencyMap( + dataTreeEvalRef: DataTreeEvaluator, + unEvalTree: DataTree, +): CreateDependencyMap { + let dependencyMap: DependencyMap = {} + let triggerFieldDependencyMap: DependencyMap = {} + let validationDependencyMap: DependencyMap = {} + const invalidReferencesMap: DependencyMap = {} + Object.keys(unEvalTree).forEach((entityName) => { + const entity = unEvalTree[entityName] + if (isAction(entity) || isWidget(entity) || isJSAction(entity)) { + const entityListedDependencies = listEntityDependencies( + entity, + entityName, + dataTreeEvalRef.allKeys, + ) + dependencyMap = { ...dependencyMap, ...entityListedDependencies } + } + if (isWidget(entity)) { + // only widgets have trigger paths + triggerFieldDependencyMap = { + ...triggerFieldDependencyMap, + ...listTriggerFieldDependencies(entity, entityName), + } + // only widgets have validation paths + validationDependencyMap = { + ...validationDependencyMap, + ...listValidationDependencies(entity, entityName), + } + } + }) + + Object.keys(dependencyMap).forEach((key) => { + const { errors, invalidReferences, validReferences } = + extractInfoFromBindings(dependencyMap[key], dataTreeEvalRef.allKeys) + dependencyMap[key] = validReferences + // To keep invalidReferencesMap as minimal as possible, only paths with invalid references + // are stored. + if (invalidReferences.length) { + invalidReferencesMap[key] = invalidReferences + } + errors.forEach((error) => { + dataTreeEvalRef.errors.push(error) + }) + }) + + // extract references from bindings in trigger fields + Object.keys(triggerFieldDependencyMap).forEach((key) => { + const { errors, invalidReferences, validReferences } = + extractInfoFromBindings( + triggerFieldDependencyMap[key], + dataTreeEvalRef.allKeys, + ) + triggerFieldDependencyMap[key] = validReferences + // To keep invalidReferencesMap as minimal as possible, only paths with invalid references + // are stored. + if (invalidReferences.length) { + invalidReferencesMap[key] = invalidReferences + } + errors.forEach((error) => { + dataTreeEvalRef.errors.push(error) + }) + }) + + dependencyMap = makeParentsDependOnChildren( + dependencyMap, + dataTreeEvalRef.allKeys, + ) + + return { + dependencyMap, + triggerFieldDependencyMap, + invalidReferencesMap, + validationDependencyMap, + } +} + +interface UpdateDependencyMap { + dependenciesOfRemovedPaths: string[] + removedPaths: string[] + /** Some paths do not need to go through evaluation, but require linting + * For example: + * 1. For changes in paths that trigger fields depend on, the triggerFields need to be "linted" but not evaluated. + * 2. Paths containing invalid references - Eg. for binding {{Api1.unknown}} in button.text, although Api1.unknown + * is not a valid reference, when Api1 is deleted button.text needs to be linted + */ + extraPathsToLint: string[] +} +export const updateDependencyMap = ({ + dataTreeEvalRef, + translatedDiffs, + unEvalDataTree, +}: { + dataTreeEvalRef: DataTreeEvaluator + translatedDiffs: DataTreeDiff[] + unEvalDataTree: DataTree +}): UpdateDependencyMap => { + const diffCalcStart = performance.now() + let didUpdateDependencyMap = false + let didUpdateValidationDependencyMap = false + const dependenciesOfRemovedPaths: string[] = [] + const removedPaths: string[] = [] + const extraPathsToLint = new Set() + + // This is needed for NEW and DELETE events below. + // In worst case, it tends to take ~12.5% of entire diffCalc (8 ms out of 67ms for 132 array of NEW) + // TODO: Optimise by only getting paths of changed node + dataTreeEvalRef.allKeys = getAllPaths(unEvalDataTree) + // Transform the diff library events to Appsmith evaluator events + translatedDiffs.forEach((dataTreeDiff) => { + const { entityName } = getEntityNameAndPropertyPath( + dataTreeDiff.payload.propertyPath, + ) + let entity = unEvalDataTree[entityName] + if (dataTreeDiff.event === DataTreeDiffEvent.DELETE) { + entity = dataTreeEvalRef.oldUnEvalTree[entityName] + } + const entityType = isValidEntity(entity) ? entity.ENTITY_TYPE : 'noop' + + if (entityType !== 'noop') { + switch (dataTreeDiff.event) { + case DataTreeDiffEvent.NEW: { + // If a new entity/property was added, + // add all the internal bindings for this entity to the global dependency map + if ( + (isWidget(entity) || isAction(entity) || isJSAction(entity)) && + !isDynamicLeaf(unEvalDataTree, dataTreeDiff.payload.propertyPath) + ) { + const entityDependencyMap: DependencyMap = listEntityDependencies( + entity, + entityName, + dataTreeEvalRef.allKeys, + ) + if (Object.keys(entityDependencyMap).length) { + didUpdateDependencyMap = true + + // The entity might already have some dependencies, + // so we just want to update those + Object.entries(entityDependencyMap).forEach( + ([entityDependent, entityDependencies]) => { + const { errors, invalidReferences, validReferences } = + extractInfoFromBindings( + entityDependencies, + dataTreeEvalRef.allKeys, + ) + // Update dependencyMap + dataTreeEvalRef.dependencyMap[entityDependent] = mergeArrays( + dataTreeEvalRef.dependencyMap[entityDependent], + validReferences, + ) + // Update invalidReferencesMap + if (invalidReferences.length) { + dataTreeEvalRef.invalidReferencesMap[entityDependent] = + invalidReferences + } else { + Reflect.deleteProperty( + dataTreeEvalRef.invalidReferencesMap, + entityDependent, + ) + } + errors.forEach((error) => { + dataTreeEvalRef.errors.push(error) + }) + }, + ) + } + + if (isWidget(entity)) { + // For widgets, + // we need to update the triggerField dependencyMap and validation dependencyMap + const triggerFieldDependencies = listTriggerFieldDependencies( + entity, + entityName, + ) + Object.entries(triggerFieldDependencies).forEach( + ([triggerFieldDependent, triggerFieldDependencies]) => { + const { errors, invalidReferences, validReferences } = + extractInfoFromBindings( + triggerFieldDependencies, + dataTreeEvalRef.allKeys, + ) + // Update triggerfield dependencyMap + dataTreeEvalRef.triggerFieldDependencyMap[ + triggerFieldDependent + ] = mergeArrays( + dataTreeEvalRef.triggerFieldDependencyMap[ + triggerFieldDependent + ], + validReferences, + ) + // Update invalidReferencesMap + if (invalidReferences.length) { + dataTreeEvalRef.invalidReferencesMap[ + triggerFieldDependent + ] = invalidReferences + } else { + Reflect.deleteProperty( + dataTreeEvalRef.invalidReferencesMap, + triggerFieldDependent, + ) + } + errors.forEach((error) => { + dataTreeEvalRef.errors.push(error) + }) + }, + ) + + // update validation dependencies + dataTreeEvalRef.validationDependencyMap = { + ...dataTreeEvalRef.validationDependencyMap, + ...listValidationDependencies(entity, entityName), + } + didUpdateValidationDependencyMap = true + } + } + // Either a new entity or a new property path has been added. Go through the list of invalid references and + // find out if a new dependency has to be created because the property path used in the binding just became + // eligible (a previously invalid reference has become valid because a new entity/path got added). + + const newlyValidReferencesMap: DependencyMap = {} + Object.keys(dataTreeEvalRef.invalidReferencesMap).forEach((path) => { + dataTreeEvalRef.invalidReferencesMap[path].forEach( + (invalidReference) => { + if ( + isChildPropertyPath( + dataTreeDiff.payload.propertyPath, + invalidReference, + ) + ) { + newlyValidReferencesMap[invalidReference] = mergeArrays( + newlyValidReferencesMap[invalidReference], + [path], + ) + if (!dataTreeEvalRef.dependencyMap[invalidReference]) { + extraPathsToLint.add(path) + } + } + }, + ) + }) + + // We have found some bindings which are related to the new property path and hence should be added to the + // global dependency map + if (Object.keys(newlyValidReferencesMap).length) { + didUpdateDependencyMap = true + Object.keys(newlyValidReferencesMap).forEach((reference) => { + const { validReferences } = extractInfoFromReferences( + [reference], + dataTreeEvalRef.allKeys, + ) + newlyValidReferencesMap[reference].forEach((path) => { + const { entityName, propertyPath } = + getEntityNameAndPropertyPath(path) + const entity = unEvalDataTree[entityName] + if (validReferences.length) { + // For trigger paths, update the triggerfield dependency map + // For other paths, update the dependency map + if ( + isWidget(entity) && + isPathDynamicTrigger(entity, propertyPath) + ) { + dataTreeEvalRef.triggerFieldDependencyMap[path] = + mergeArrays( + dataTreeEvalRef.triggerFieldDependencyMap[path], + validReferences, + ) + } else { + dataTreeEvalRef.dependencyMap[path] = mergeArrays( + dataTreeEvalRef.dependencyMap[path], + validReferences, + ) + } + // Since the previously invalid reference has become valid, + // remove it from the invalidReferencesMap + if (dataTreeEvalRef.invalidReferencesMap[path]) { + const newInvalidReferences = + dataTreeEvalRef.invalidReferencesMap[path].filter( + (invalidReference) => + // FIXME:(LiuLei) + // eslint-disable-next-line no-self-compare + invalidReference !== invalidReference, + ) + if (newInvalidReferences.length) { + dataTreeEvalRef.invalidReferencesMap[path] = + newInvalidReferences + } else { + Reflect.deleteProperty( + dataTreeEvalRef.invalidReferencesMap, + path, + ) + } + } + } + }) + }) + } + + // Add trigger paths that depend on the added path/entity to "extrapathstolint" + Object.keys(dataTreeEvalRef.triggerFieldDependencyMap).forEach( + (triggerPath) => { + dataTreeEvalRef.triggerFieldDependencyMap[triggerPath].forEach( + (triggerPathDependency) => { + if ( + isChildPropertyPath( + dataTreeDiff.payload.propertyPath, + triggerPathDependency, + ) + ) { + extraPathsToLint.add(triggerPath) + } + }, + ) + }, + ) + break + } + case DataTreeDiffEvent.DELETE: { + // Add to removedPaths as they have been deleted from the evalTree + removedPaths.push(dataTreeDiff.payload.propertyPath) + // If an existing entity was deleted, remove all the bindings from the global dependency map + if ( + (isWidget(entity) || isAction(entity) || isJSAction(entity)) && + dataTreeDiff.payload.propertyPath === entityName + ) { + const entityDependencies = listEntityDependencies( + entity, + entityName, + dataTreeEvalRef.allKeys, + ) + Object.keys(entityDependencies).forEach((widgetDep) => { + didUpdateDependencyMap = true + Reflect.deleteProperty(dataTreeEvalRef.dependencyMap, widgetDep) + Reflect.deleteProperty( + dataTreeEvalRef.invalidReferencesMap, + widgetDep, + ) + }) + + if (isWidget(entity)) { + const triggerFieldDependencies = listTriggerFieldDependencies( + entity, + entityName, + ) + Object.keys(triggerFieldDependencies).forEach((triggerDep) => { + Reflect.deleteProperty( + dataTreeEvalRef.triggerFieldDependencyMap, + triggerDep, + ) + Reflect.deleteProperty( + dataTreeEvalRef.invalidReferencesMap, + triggerDep, + ) + }) + + // remove validation dependencies + const validationDependencies = listValidationDependencies( + entity, + entityName, + ) + Object.keys(validationDependencies).forEach((validationDep) => { + Reflect.deleteProperty( + dataTreeEvalRef.validationDependencyMap, + validationDep, + ) + }) + didUpdateValidationDependencyMap = true + } + } + // Either an existing entity or an existing property path has been deleted. Update the global dependency map + // by removing the bindings from the same. + Object.keys(dataTreeEvalRef.dependencyMap).forEach( + (dependencyPath) => { + didUpdateDependencyMap = true + if ( + isChildPropertyPath( + dataTreeDiff.payload.propertyPath, + dependencyPath, + ) + ) { + Reflect.deleteProperty( + dataTreeEvalRef.dependencyMap, + dependencyPath, + ) + + Reflect.deleteProperty( + dataTreeEvalRef.invalidReferencesMap, + dependencyPath, + ) + } else { + const toRemove: string[] = [] + dataTreeEvalRef.dependencyMap[dependencyPath].forEach( + (dependantPath) => { + if ( + isChildPropertyPath( + dataTreeDiff.payload.propertyPath, + dependantPath, + ) + ) { + dependenciesOfRemovedPaths.push(dependencyPath) + toRemove.push(dependantPath) + } + }, + ) + dataTreeEvalRef.dependencyMap[dependencyPath] = difference( + dataTreeEvalRef.dependencyMap[dependencyPath], + toRemove, + ) + // If we find any invalid reference (untracked in the dependency map) for this path, + // which is a child of the deleted path, add it to the of paths to lint. + // Example scenario => For {{Api1.unknown}} in button.text, + // if Api1 is deleted, we need to lint button.text + // Although, "Api1.unknown" is not a valid reference + + if (dataTreeEvalRef.invalidReferencesMap[dependencyPath]) { + dataTreeEvalRef.invalidReferencesMap[dependencyPath].forEach( + (invalidReference) => { + if ( + isChildPropertyPath( + dataTreeDiff.payload.propertyPath, + invalidReference, + ) + ) { + extraPathsToLint.add(dependencyPath) + } + }, + ) + } + + // Since we are removing previously valid references, + // We also update the invalidReferenceMap for this path + if (toRemove.length) { + dataTreeEvalRef.invalidReferencesMap[dependencyPath] = + mergeArrays( + dataTreeEvalRef.invalidReferencesMap[dependencyPath], + toRemove, + ) + } + } + }, + ) + Object.keys(dataTreeEvalRef.triggerFieldDependencyMap).forEach( + (dependencyPath) => { + if ( + isChildPropertyPath( + dataTreeDiff.payload.propertyPath, + dependencyPath, + ) + ) { + Reflect.deleteProperty( + dataTreeEvalRef.triggerFieldDependencyMap, + dependencyPath, + ) + Reflect.deleteProperty( + dataTreeEvalRef.invalidReferencesMap, + dependencyPath, + ) + } else { + const toRemove: string[] = [] + dataTreeEvalRef.triggerFieldDependencyMap[ + dependencyPath + ].forEach((dependantPath) => { + if ( + isChildPropertyPath( + dataTreeDiff.payload.propertyPath, + dependantPath, + ) + ) { + toRemove.push(dependantPath) + } + }) + dataTreeEvalRef.triggerFieldDependencyMap[dependencyPath] = + difference( + dataTreeEvalRef.triggerFieldDependencyMap[dependencyPath], + toRemove, + ) + if (toRemove.length) { + dataTreeEvalRef.invalidReferencesMap[dependencyPath] = + mergeArrays( + dataTreeEvalRef.invalidReferencesMap[dependencyPath], + toRemove, + ) + } + if (dataTreeEvalRef.invalidReferencesMap[dependencyPath]) { + dataTreeEvalRef.invalidReferencesMap[dependencyPath].forEach( + (invalidReference) => { + if ( + isChildPropertyPath( + dataTreeDiff.payload.propertyPath, + invalidReference, + ) + ) { + extraPathsToLint.add(dependencyPath) + } + }, + ) + } + } + }, + ) + + break + } + case DataTreeDiffEvent.EDIT: { + // We only care if the difference is in dynamic bindings since static values do not need + // an evaluation. + if ( + (isWidget(entity) || isAction(entity) || isJSAction(entity)) && + typeof dataTreeDiff.payload.value === 'string' + ) { + const entity: DataTreeAction | DataTreeWidget | DataTreeJSAction = + unEvalDataTree[entityName] as + | DataTreeAction + | DataTreeWidget + | DataTreeJSAction + const fullPropertyPath = dataTreeDiff.payload.propertyPath + const entityPropertyPath = getPropertyPath(fullPropertyPath) + const isADynamicBindingPath = isPathADynamicBinding( + entity, + entityPropertyPath, + ) + if (isADynamicBindingPath) { + didUpdateDependencyMap = true + + const { jsSnippets } = getDynamicBindings( + dataTreeDiff.payload.value, + entity, + ) + const correctSnippets = jsSnippets.filter( + (jsSnippet) => !!jsSnippet, + ) + const { errors, invalidReferences, validReferences } = + extractInfoFromBindings( + correctSnippets, + dataTreeEvalRef.allKeys, + ) + + if (invalidReferences.length) { + dataTreeEvalRef.invalidReferencesMap[fullPropertyPath] = + invalidReferences + } else { + Reflect.deleteProperty( + dataTreeEvalRef.invalidReferencesMap, + fullPropertyPath, + ) + } + errors.forEach((error) => { + dataTreeEvalRef.errors.push(error) + }) + + // We found a new dynamic binding for this property path. We update the dependency map by overwriting the + // dependencies for this property path with the newly found dependencies + + if (correctSnippets.length) { + dataTreeEvalRef.dependencyMap[fullPropertyPath] = + validReferences + } else { + // The dependency on this property path has been removed. Delete this property path from the global + // dependency map + Reflect.deleteProperty( + dataTreeEvalRef.dependencyMap, + fullPropertyPath, + ) + } + if (isAction(entity) || isJSAction(entity)) { + // Actions have a defined dependency map that should always be maintained + if (entityPropertyPath in entity.dependencyMap) { + const entityDependenciesName = entity.dependencyMap[ + entityPropertyPath + ].map((dep) => `${entityName}.${dep}`) + + const { errors, invalidReferences, validReferences } = + extractInfoFromBindings( + entityDependenciesName, + dataTreeEvalRef.allKeys, + ) + + if (invalidReferences.length) { + dataTreeEvalRef.invalidReferencesMap[ + dataTreeDiff.payload.propertyPath + ] = invalidReferences + } else { + Reflect.deleteProperty( + dataTreeEvalRef.invalidReferencesMap, + dataTreeDiff.payload.propertyPath, + ) + } + + errors.forEach((error) => { + dataTreeEvalRef.errors.push(error) + }) + + // Now assign these existing dependent paths to the property path in dependencyMap + if (fullPropertyPath in dataTreeEvalRef.dependencyMap) { + dataTreeEvalRef.dependencyMap[fullPropertyPath] = + dataTreeEvalRef.dependencyMap[fullPropertyPath].concat( + validReferences, + ) + } else { + dataTreeEvalRef.dependencyMap[fullPropertyPath] = + validReferences + } + } + } + } + // If the whole binding was removed, then the value at this path would be a string without any bindings. + // In this case, if the path exists in the dependency map and is a bindingPath, then remove it. + else if ( + entity.bindingPaths[entityPropertyPath] && + fullPropertyPath in dataTreeEvalRef.dependencyMap + ) { + didUpdateDependencyMap = true + Reflect.deleteProperty( + dataTreeEvalRef.dependencyMap, + fullPropertyPath, + ) + Reflect.deleteProperty( + dataTreeEvalRef.invalidReferencesMap, + fullPropertyPath, + ) + } + } + if ( + isWidget(entity) && + isPathDynamicTrigger( + entity, + getPropertyPath(dataTreeDiff.payload.propertyPath), + ) + ) { + const { jsSnippets } = getDynamicBindings( + dataTreeDiff.payload.value || '', + entity, + ) + const entityDependencies = jsSnippets.filter( + (jsSnippet) => !!jsSnippet, + ) + + const { errors, invalidReferences, validReferences } = + extractInfoFromBindings( + entityDependencies, + dataTreeEvalRef.allKeys, + ) + + errors.forEach((error) => { + dataTreeEvalRef.errors.push(error) + }) + + if (invalidReferences.length) { + dataTreeEvalRef.invalidReferencesMap[ + dataTreeDiff.payload.propertyPath + ] = invalidReferences + } else { + Reflect.deleteProperty( + dataTreeEvalRef.invalidReferencesMap, + dataTreeDiff.payload.propertyPath, + ) + } + + dataTreeEvalRef.triggerFieldDependencyMap[ + dataTreeDiff.payload.propertyPath + ] = validReferences + } + break + } + default: { + break + } + } + } + }) + const diffCalcEnd = performance.now() + const subDepCalcStart = performance.now() + if (didUpdateDependencyMap) { + dataTreeEvalRef.dependencyMap = makeParentsDependOnChildren( + dataTreeEvalRef.dependencyMap, + dataTreeEvalRef.allKeys, + ) + } + const subDepCalcEnd = performance.now() + const updateChangedDependenciesStart = performance.now() + // If the global dependency map has changed, re-calculate the sort order for all entities and the + // global inverse dependency map + if (didUpdateDependencyMap) { + // This is being called purely to test for new circular dependencies that might have been added + dataTreeEvalRef.sortedDependencies = dataTreeEvalRef.sortDependencies( + dataTreeEvalRef.dependencyMap, + translatedDiffs, + ) + dataTreeEvalRef.inverseDependencyMap = + dataTreeEvalRef.getInverseDependencyTree() + } + + if (didUpdateValidationDependencyMap) { + // This is being called purely to test for new circular dependencies that might have been added + dataTreeEvalRef.sortedValidationDependencies = + dataTreeEvalRef.sortDependencies( + dataTreeEvalRef.validationDependencyMap, + translatedDiffs, + ) + + dataTreeEvalRef.inverseValidationDependencyMap = + dataTreeEvalRef.getInverseDependencyTree({ + dependencyMap: dataTreeEvalRef.validationDependencyMap, + sortedDependencies: dataTreeEvalRef.sortedValidationDependencies, + }) + } + + const updateChangedDependenciesStop = performance.now() + dataTreeEvalRef.logs.push({ + diffCalcDeps: (diffCalcEnd - diffCalcStart).toFixed(2), + subDepCalc: (subDepCalcEnd - subDepCalcStart).toFixed(2), + updateChangedDependencies: ( + updateChangedDependenciesStop - updateChangedDependenciesStart + ).toFixed(2), + }) + + return { + dependenciesOfRemovedPaths, + removedPaths, + extraPathsToLint: Array.from(extraPathsToLint), + } +} diff --git a/auxiliaries/code-editor/src/CodeEditor/works/common/DependencyMap/utils.ts b/auxiliaries/code-editor/src/CodeEditor/works/common/DependencyMap/utils.ts new file mode 100644 index 0000000..b894682 --- /dev/null +++ b/auxiliaries/code-editor/src/CodeEditor/works/common/DependencyMap/utils.ts @@ -0,0 +1,293 @@ +import { get, union } from 'lodash' +import toPath from 'lodash/toPath' + +import { extractIdentifierInfoFromCode } from '@modou/ast' +// eslint-disable-next-line max-len +import { APPSMITH_GLOBAL_FUNCTIONS } from '@modou/code-editor/CodeEditor/components/editorComponents/ActionCreator/constants' +import { + DEDICATED_WORKER_GLOBAL_SCOPE_IDENTIFIERS, + JAVASCRIPT_KEYWORDS, +} from '@modou/code-editor/CodeEditor/constants/WidgetValidation' +import { + DataTreeAction, + DataTreeJSAction, + DataTreeWidget, +} from '@modou/code-editor/CodeEditor/entities/DataTree/dataTreeFactory' +import { + DependencyMap, + EvalError, + EvalErrorTypes, + extraLibrariesNames, + getDynamicBindings, + getEntityDynamicBindingPathList, +} from '@modou/code-editor/CodeEditor/utils/DynamicBindingUtils' + +import { + addWidgetPropertyDependencies, + convertPathToString, + isAction, + isJSAction, + isWidget, +} from '../../Evaluation/evaluationUtils' + +/** This function extracts validReferences and invalidReferences from a binding {{}} + * @param script + * @param allPaths + * @returns validReferences - Valid references from bindings + * invalidReferences- References which are currently invalid + * @example - For binding {{unknownEntity.name + Api1.name}}, it returns + * { + * validReferences:[Api1.name], + * invalidReferences: [unknownEntity.name] + * } + */ +export const extractInfoFromBinding = ( + script: string, + allPaths: Record, +): { validReferences: string[]; invalidReferences: string[] } => { + const { references } = extractIdentifierInfoFromCode( + script, + self.evaluationVersion, + invalidEntityIdentifiers, + ) + return extractInfoFromReferences(references, allPaths) +} + +/** This function extracts validReferences and invalidReferences from an Array of Identifiers + * @param references + * @param allPaths + * @returns validReferences - Valid references from bindings + * invalidReferences- References which are currently invalid + * @example - For identifiers [unknownEntity.name , Api1.name], it returns + * { + * validReferences:[Api1.name], + * invalidReferences: [unknownEntity.name] + * } + */ +export const extractInfoFromReferences = ( + references: string[], + allPaths: Record, +): { + validReferences: string[] + invalidReferences: string[] +} => { + const validReferences: Set = new Set() + const invalidReferences: string[] = [] + references.forEach((reference: string) => { + // If the identifier exists directly, add it and return + if (Reflect.has(allPaths, reference)) { + validReferences.add(reference) + return + } + const subpaths = toPath(reference) + let current = '' + // We want to keep going till we reach top level, but not add top level + // Eg: Input1.text should not depend on entire Table1 unless it explicitly asked for that. + // This is mainly to avoid a lot of unnecessary evals, if we feel this is wrong + // we can remove the length requirement, and it will still work + while (subpaths.length > 1) { + current = convertPathToString(subpaths) + // We've found the dep, add it and return + if (Reflect.has(allPaths, current)) { + validReferences.add(current) + return + } + subpaths.pop() + } + // If no valid reference is derived, add it to the list of invalidReferences + invalidReferences.push(reference) + }) + return { validReferences: Array.from(validReferences), invalidReferences } +} + +interface BindingsInfo { + validReferences: string[] + invalidReferences: string[] + errors: EvalError[] +} +export const extractInfoFromBindings = ( + bindings: string[], + allPaths: Record, +) => { + return bindings.reduce( + (bindingsInfo: BindingsInfo, binding) => { + try { + const { invalidReferences, validReferences } = extractInfoFromBinding( + binding, + allPaths, + ) + return { + ...bindingsInfo, + validReferences: union(bindingsInfo.validReferences, validReferences), + invalidReferences: union( + bindingsInfo.invalidReferences, + invalidReferences, + ), + } + } catch (error) { + const newEvalError: EvalError = { + type: EvalErrorTypes.EXTRACT_DEPENDENCY_ERROR, + message: (error as Error).message, + context: { + script: binding, + }, + } + return { + ...bindingsInfo, + errors: union(bindingsInfo.errors, [newEvalError]), + } + } + }, + { validReferences: [], invalidReferences: [], errors: [] }, + ) +} + +export function listTriggerFieldDependencies( + entity: DataTreeWidget, + entityName: string, +): DependencyMap { + const triggerFieldDependency: DependencyMap = {} + if (isWidget(entity)) { + const dynamicTriggerPathlist = entity.dynamicTriggerPathList + if (dynamicTriggerPathlist?.length) { + dynamicTriggerPathlist.forEach((dynamicPath) => { + const propertyPath = dynamicPath.key + const unevalPropValue = get(entity, propertyPath) + const { jsSnippets } = getDynamicBindings(unevalPropValue) + const existingDeps = + triggerFieldDependency[`${entityName}.${propertyPath}`] || [] + triggerFieldDependency[`${entityName}.${propertyPath}`] = + existingDeps.concat(jsSnippets.filter((jsSnippet) => !!jsSnippet)) + }) + } + } + return triggerFieldDependency +} + +export function listValidationDependencies( + entity: DataTreeWidget, + entityName: string, +): DependencyMap { + const validationDependency: DependencyMap = {} + if (isWidget(entity)) { + const { validationPaths } = entity + + Object.entries(validationPaths).forEach( + ([propertyPath, validationConfig]) => { + if (validationConfig.dependentPaths) { + const dependencyArray = validationConfig.dependentPaths.map( + (path) => `${entityName}.${path}`, + ) + validationDependency[`${entityName}.${propertyPath}`] = + dependencyArray + } + }, + ) + } + return validationDependency +} + +/** This function returns a unique array containing a merge of both arrays + * @param currentArr + * @param updateArr + * @returns A unique array containing a merge of both arrays + */ +export const mergeArrays = (currentArr: T[], updateArr: T[]): T[] => { + if (!currentArr) return updateArr + return union(currentArr, updateArr) +} + +/** + * Identifiers which can not be valid names of entities and are not dynamic in nature. + * therefore should be removed from the list of references extracted from code. + * NB: DATA_TREE_KEYWORDS in app/client/src/constants/WidgetValidation.ts isn't included, + * although they are not valid entity names, + * they can refer to potentially dynamic entities. + * E.g. "appsmith" + */ +const invalidEntityIdentifiers: Record = { + ...JAVASCRIPT_KEYWORDS, + ...APPSMITH_GLOBAL_FUNCTIONS, + ...DEDICATED_WORKER_GLOBAL_SCOPE_IDENTIFIERS, + ...extraLibrariesNames, +} + +export function listEntityDependencies( + entity: DataTreeWidget | DataTreeAction | DataTreeJSAction, + entityName: string, + allPaths: Record, +): DependencyMap { + let dependencies: DependencyMap = {} + + if (isWidget(entity)) { + // Adding the dynamic triggers in the dependency list as they need linting whenever updated + // we don't make it dependent on anything else + if (entity.dynamicTriggerPathList) { + Object.values(entity.dynamicTriggerPathList).forEach(({ key }) => { + dependencies[`${entityName}.${key}`] = [] + }) + } + const widgetDependencies = addWidgetPropertyDependencies({ + entity, + entityName, + }) + + dependencies = { + ...dependencies, + ...widgetDependencies, + } + } + + if (isAction(entity) || isJSAction(entity)) { + Object.entries(entity.dependencyMap).forEach( + ([path, entityDependencies]) => { + const actionDependentPaths: string[] = [] + const mainPath = `${entityName}.${path}` + // Only add dependencies for paths which exist at the moment in appsmith world + if (Reflect.has(allPaths, mainPath)) { + // Only add dependent paths which exist in the data tree. Skip all the other paths to avoid creating + // a cyclical dependency. + entityDependencies.forEach((dependentPath) => { + const completePath = `${entityName}.${dependentPath}` + if (Reflect.has(allPaths, completePath)) { + actionDependentPaths.push(completePath) + } + }) + dependencies[mainPath] = actionDependentPaths + } + }, + ) + } + if (isJSAction(entity)) { + // making functions dependent on their function body entities + if (entity.reactivePaths) { + Object.keys(entity.reactivePaths).forEach((propertyPath) => { + const existingDeps = dependencies[`${entityName}.${propertyPath}`] || [] + const unevalPropValue = get(entity, propertyPath) + const unevalPropValueString = + !!unevalPropValue && unevalPropValue.toString() + const { jsSnippets } = getDynamicBindings(unevalPropValueString, entity) + dependencies[`${entityName}.${propertyPath}`] = existingDeps.concat( + jsSnippets.filter((jsSnippet) => !!jsSnippet), + ) + }) + } + } + + if (isAction(entity) || isWidget(entity)) { + // add the dynamic binding paths to the dependency map + const dynamicBindingPathList = getEntityDynamicBindingPathList(entity) + if (dynamicBindingPathList.length) { + dynamicBindingPathList.forEach((dynamicPath) => { + const propertyPath = dynamicPath.key + const unevalPropValue = get(entity, propertyPath) + const { jsSnippets } = getDynamicBindings(unevalPropValue) + const existingDeps = dependencies[`${entityName}.${propertyPath}`] || [] + dependencies[`${entityName}.${propertyPath}`] = existingDeps.concat( + jsSnippets.filter((jsSnippet) => !!jsSnippet), + ) + }) + } + } + return dependencies +} diff --git a/auxiliaries/code-editor/src/CodeEditor/works/common/types.ts b/auxiliaries/code-editor/src/CodeEditor/works/common/types.ts new file mode 100644 index 0000000..9b33ea0 --- /dev/null +++ b/auxiliaries/code-editor/src/CodeEditor/works/common/types.ts @@ -0,0 +1,14 @@ +export enum AppsmithWorkers { + LINT_WORKER = 'LINT_WORKER', + EVALUATION_WORKER = 'EVALUATION_WORKER', + SETUP_WORKER = 'SETUP_WORKER', +} +export enum WorkerErrorTypes { + CLONE_ERROR = 'CLONE_ERROR', +} + +export interface WorkerRequest { + method: TActions + requestData: TData + requestId: string +} diff --git a/auxiliaries/code-editor/src/CodeEditor/works/evaluatiopn/evaluationUtils.ts b/auxiliaries/code-editor/src/CodeEditor/works/evaluatiopn/evaluationUtils.ts deleted file mode 100644 index d13d0c1..0000000 --- a/auxiliaries/code-editor/src/CodeEditor/works/evaluatiopn/evaluationUtils.ts +++ /dev/null @@ -1,18 +0,0 @@ -export const getEntityNameAndPropertyPath = ( - fullPath: string, -): { - entityName: string - propertyPath: string -} => { - const indexOfFirstDot = fullPath.indexOf('.') - if (indexOfFirstDot === -1) { - // No dot was found so path is the entity name itself - return { - entityName: fullPath, - propertyPath: '', - } - } - const entityName = fullPath.substring(0, indexOfFirstDot) - const propertyPath = fullPath.substring(indexOfFirstDot + 1) - return { entityName, propertyPath } -} diff --git a/package.json b/package.json index 54dca3c..a61ca46 100644 --- a/package.json +++ b/package.json @@ -61,7 +61,8 @@ "reactflow": "^11.3.0", "recoil": "^0.7.5", "recoil-sync": "^0.1.1", - "ts-pattern": "^4.0.5" + "ts-pattern": "^4.0.5", + "nanoid": "^4.0.0" }, "config": { "commitizen": { diff --git a/packages/core/package.json b/packages/core/package.json index f1622fe..22cb78a 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -9,7 +9,5 @@ "keywords": [], "author": "", "license": "ISC", - "dependencies": { - "nanoid": "^4.0.0" - } + "dependencies": {} } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 1bc5f21..9759784 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -44,6 +44,7 @@ importers: immer: ^9.0.15 immutability-helper: ^3.1.1 lodash: ^4.17.21 + nanoid: ^4.0.0 prettier: ^2.8.1 rc-tree: ^5.7.2 react: ^18.2.0 @@ -76,6 +77,7 @@ importers: immer: 9.0.15 immutability-helper: 3.1.1 lodash: 4.17.21 + nanoid: 4.0.0 react: 18.2.0 react-dnd: 16.0.1_iapumuv4e6jcjznwuxpf4tt22e react-dnd-html5-backend: 16.0.1_kfajotxmcqjtgrepibrwmj5zwu @@ -112,30 +114,104 @@ importers: typescript: 4.8.4 vite: 3.1.8 + auxiliaries/ast: + specifiers: + '@babel/preset-typescript': ^7.17.12 + '@rollup/plugin-commonjs': ^22.0.0 + '@types/jest': 29.0.3 + '@types/lodash': ^4.14.120 + '@typescript-eslint/eslint-plugin': ^5.25.0 + '@typescript-eslint/parser': ^5.25.0 + acorn: ^8.8.0 + acorn-walk: ^8.2.0 + astring: ^1.7.5 + jest: 29.0.3 + lodash: ^4.17.21 + rollup: ^2.77.0 + rollup-plugin-generate-package-json: ^3.2.0 + rollup-plugin-peer-deps-external: ^2.2.4 + rollup-plugin-typescript2: ^0.32.0 + ts-jest: 29.0.1 + typescript: 4.5.5 + unescape-js: ^1.1.4 + dependencies: + acorn: 8.8.0 + acorn-walk: 8.2.0 + astring: 1.8.3 + lodash: 4.17.21 + rollup: 2.78.1 + typescript: 4.5.5 + unescape-js: 1.1.4 + devDependencies: + '@babel/preset-typescript': 7.18.6 + '@rollup/plugin-commonjs': 22.0.2_rollup@2.78.1 + '@types/jest': 29.0.3 + '@types/lodash': 4.14.186 + '@typescript-eslint/eslint-plugin': 5.36.1_pydmdwhbhioitmd4vyrmo4rkou + '@typescript-eslint/parser': 5.40.0_typescript@4.5.5 + jest: 29.0.3 + rollup-plugin-generate-package-json: 3.2.0_rollup@2.78.1 + rollup-plugin-peer-deps-external: 2.2.4_rollup@2.78.1 + rollup-plugin-typescript2: 0.32.1_2hrmc4tvghoalmdywjmqyits4a + ts-jest: 29.0.1_3bi5c5acf6iiidhyy65zjxahau + publishDirectory: build + auxiliaries/code-editor: specifiers: + '@modou/ast': workspace:^1.0.0 + '@sentry/react': ^7.28.1 '@types/codemirror': ^5.60.5 + '@types/deep-diff': ^1.0.2 + '@types/node-forge': ^1.3.1 '@types/tern': ^0.23.4 + '@types/toposort': ^2.0.3 + '@types/unescape-js': ^1.0.0 '@umijs/lint': ^4.0.40 codemirror: ^5.65.10 + deep-diff: ^1.0.2 dumi: ^2.0.16 fast-deep-equal: ^3.1.3 + fast-xml-parser: ^4.0.12 father: ^4.1.0 husky: ^8.0.1 + klona: ^2.0.5 lint-staged: ^13.0.3 loglevel: ^1.8.1 + moment: ^2.29.4 + moment-timezone: ^0.5.40 + node-forge: ^1.3.1 react: ^18.2.0 react-dom: ^18.2.0 + react-toastify: ^9.1.1 tern: ^0.24.3 + toposort: ^2.0.2 + unescape-js: ^1.1.4 webpack: ^5.75.0 + yjs: ^13.5.43 dependencies: + '@modou/ast': link:../ast + '@sentry/react': 7.28.1_react@18.2.0 codemirror: 5.65.10 + deep-diff: 1.0.2 fast-deep-equal: 3.1.3 + fast-xml-parser: 4.0.12 + klona: 2.0.5 loglevel: 1.8.1 + moment: 2.29.4 + moment-timezone: 0.5.40 + node-forge: 1.3.1 + react-toastify: 9.1.1_biqbaboplfbrettd7655fr4n2y tern: 0.24.3 + toposort: 2.0.2 + unescape-js: 1.1.4 + yjs: 13.5.43 devDependencies: '@types/codemirror': 5.60.5 + '@types/deep-diff': 1.0.2 + '@types/node-forge': 1.3.1 '@types/tern': 0.23.4 + '@types/toposort': 2.0.3 + '@types/unescape-js': 1.0.0 '@umijs/lint': 4.0.40 dumi: 2.0.16_sjkfibpvkg33iagsiij4zgoske father: 4.1.1_webpack@5.75.0 @@ -145,38 +221,6 @@ importers: react-dom: 18.2.0_react@18.2.0 webpack: 5.75.0 - auxiliaries/demo: - specifiers: - '@commitlint/cli': ^17.1.2 - '@commitlint/config-conventional': ^17.1.0 - '@umijs/lint': ^4.0.0 - dumi: ^2.0.2 - eslint: ^8.23.0 - father: ^4.1.0 - husky: ^8.0.1 - lint-staged: ^13.0.3 - prettier: ^2.7.1 - prettier-plugin-organize-imports: ^3.0.0 - prettier-plugin-packagejson: ^2.2.18 - react: ^18.0.0 - react-dom: ^18.0.0 - stylelint: ^14.9.1 - devDependencies: - '@commitlint/cli': 17.3.0 - '@commitlint/config-conventional': 17.3.0 - '@umijs/lint': 4.0.40_nc6yd4bwypbhcidfskuo37yccq - dumi: 2.0.16_hahnn56zypgjpv523dkkei53ve - eslint: 8.25.0 - father: 4.1.1 - husky: 8.0.2 - lint-staged: 13.1.0 - prettier: 2.8.1 - prettier-plugin-organize-imports: 3.2.1_prettier@2.8.1 - prettier-plugin-packagejson: 2.3.0_prettier@2.8.1 - react: 18.2.0 - react-dom: 18.2.0_react@18.2.0 - stylelint: 14.16.0 - designers/canvas: specifiers: {} @@ -653,9 +697,9 @@ packages: '@babel/helper-module-transforms': 7.19.0 '@babel/helpers': 7.19.4 '@babel/parser': 7.19.4 - '@babel/template': 7.18.10 - '@babel/traverse': 7.19.4 - '@babel/types': 7.19.4 + '@babel/template': 7.20.7 + '@babel/traverse': 7.20.10 + '@babel/types': 7.20.7 convert-source-map: 1.9.0 debug: 4.3.4 gensync: 1.0.0-beta.2 @@ -665,20 +709,6 @@ packages: - supports-color dev: true - /@babel/eslint-parser/7.18.9_442gibva36idvxayifa5d4cjjm: - resolution: {integrity: sha512-KzSGpMBggz4fKbRbWLNyPVTuQr6cmCcBhOyXTw/fieOVaw5oYAwcAj4a7UKcDYCPxQq+CG1NCDZH9e2JTXquiQ==} - engines: {node: ^10.13.0 || ^12.13.0 || >=14.0.0} - peerDependencies: - '@babel/core': '>=7.11.0' - eslint: ^7.5.0 || ^8.0.0 - dependencies: - '@babel/core': 7.18.9 - eslint: 8.25.0 - eslint-scope: 5.1.1 - eslint-visitor-keys: 2.1.0 - semver: 6.3.0 - dev: true - /@babel/eslint-parser/7.18.9_@babel+core@7.18.9: resolution: {integrity: sha512-KzSGpMBggz4fKbRbWLNyPVTuQr6cmCcBhOyXTw/fieOVaw5oYAwcAj4a7UKcDYCPxQq+CG1NCDZH9e2JTXquiQ==} engines: {node: ^10.13.0 || ^12.13.0 || >=14.0.0} @@ -705,7 +735,16 @@ packages: resolution: {integrity: sha512-DxbNz9Lz4aMZ99qPpO1raTbcrI1ZeYh+9NR9qhfkQIbFtVEqotHojEBxHzmxhVONkGt6VyrqVQcgpefMy9pqcg==} engines: {node: '>=6.9.0'} dependencies: - '@babel/types': 7.19.4 + '@babel/types': 7.20.7 + '@jridgewell/gen-mapping': 0.3.2 + jsesc: 2.5.2 + dev: true + + /@babel/generator/7.20.7: + resolution: {integrity: sha512-7wqMOJq8doJMZmP4ApXTzLxSr7+oO2jroJURrVEp6XShrQUObV8Tq/D0NCcoYg2uHqUrjzO0zwBjoYzelxK+sw==} + engines: {node: '>=6.9.0'} + dependencies: + '@babel/types': 7.20.7 '@jridgewell/gen-mapping': 0.3.2 jsesc: 2.5.2 dev: true @@ -756,6 +795,23 @@ packages: semver: 6.3.0 dev: true + /@babel/helper-create-class-features-plugin/7.20.7: + resolution: {integrity: sha512-LtoWbDXOaidEf50hmdDqn9g8VEzsorMexoWMQdQODbvmqYmaF23pBP5VNPAGIFHsFQCIeKokDiz3CH5Y2jlY6w==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + dependencies: + '@babel/helper-annotate-as-pure': 7.18.6 + '@babel/helper-environment-visitor': 7.18.9 + '@babel/helper-function-name': 7.19.0 + '@babel/helper-member-expression-to-functions': 7.20.7 + '@babel/helper-optimise-call-expression': 7.18.6 + '@babel/helper-replace-supers': 7.20.7 + '@babel/helper-split-export-declaration': 7.18.6 + transitivePeerDependencies: + - supports-color + dev: true + /@babel/helper-environment-visitor/7.18.9: resolution: {integrity: sha512-3r/aACDJ3fhQ/EVgFy0hpj8oHyHpQc+LPtJoY9SzTThAsStm4Ptegq92vqKoE3vD706ZVFWITnMnxucw+S9Ipg==} engines: {node: '>=6.9.0'} @@ -773,14 +829,21 @@ packages: resolution: {integrity: sha512-UlJQPkFqFULIcyW5sbzgbkxn2FKRgwWiRexcuaR8RNJRy8+LLveqPjwZV/bwrLZCN0eUHD/x8D0heK1ozuoo6Q==} engines: {node: '>=6.9.0'} dependencies: - '@babel/types': 7.19.4 + '@babel/types': 7.20.7 + dev: true + + /@babel/helper-member-expression-to-functions/7.20.7: + resolution: {integrity: sha512-9J0CxJLq315fEdi4s7xK5TQaNYjZw+nDVpVqr1axNGKzdrdwYBD5b4uKv3n75aABG0rCCTK8Im8Ww7eYfMrZgw==} + engines: {node: '>=6.9.0'} + dependencies: + '@babel/types': 7.20.7 dev: true /@babel/helper-module-imports/7.18.6: resolution: {integrity: sha512-0NFvs3VkuSYbFi1x2Vd6tKrywq+z/cLeYC/RJNFrIX/30Bf5aiGYbtvGXolEktzJH8o5E5KJ3tT+nkxuuZFVlA==} engines: {node: '>=6.9.0'} dependencies: - '@babel/types': 7.19.4 + '@babel/types': 7.20.7 /@babel/helper-module-transforms/7.19.0: resolution: {integrity: sha512-3HBZ377Fe14RbLIA+ac3sY4PTgpxHVkFrESaWhoI5PuyXPBBX8+C34qblV9G89ZtycGJCmCI/Ut+VUDK4bltNQ==} @@ -791,29 +854,55 @@ packages: '@babel/helper-simple-access': 7.19.4 '@babel/helper-split-export-declaration': 7.18.6 '@babel/helper-validator-identifier': 7.19.1 - '@babel/template': 7.18.10 - '@babel/traverse': 7.19.4 - '@babel/types': 7.19.4 + '@babel/template': 7.20.7 + '@babel/traverse': 7.20.10 + '@babel/types': 7.20.7 transitivePeerDependencies: - supports-color dev: true + /@babel/helper-optimise-call-expression/7.18.6: + resolution: {integrity: sha512-HP59oD9/fEHQkdcbgFCnbmgH5vIQTJbxh2yf+CdM89/glUNnuzr87Q8GIjGEnOktTROemO0Pe0iPAYbqZuOUiA==} + engines: {node: '>=6.9.0'} + dependencies: + '@babel/types': 7.19.4 + dev: true + /@babel/helper-plugin-utils/7.19.0: resolution: {integrity: sha512-40Ryx7I8mT+0gaNxm8JGTZFUITNqdLAgdg0hXzeVZxVD6nFsdhQvip6v8dqkRHzsz1VFpFAaOCHNn0vKBL7Czw==} engines: {node: '>=6.9.0'} + dev: true + + /@babel/helper-plugin-utils/7.20.2: + resolution: {integrity: sha512-8RvlJG2mj4huQ4pZ+rU9lqKi9ZKiRmuvGuM2HlWmkmgOhbs6zEAw6IEiJ5cQqGbDzGZOhwuOQNtZMi/ENLjZoQ==} + engines: {node: '>=6.9.0'} + + /@babel/helper-replace-supers/7.20.7: + resolution: {integrity: sha512-vujDMtB6LVfNW13jhlCrp48QNslK6JXi7lQG736HVbHz/mbf4Dc7tIRh1Xf5C0rF7BP8iiSxGMCmY6Ci1ven3A==} + engines: {node: '>=6.9.0'} + dependencies: + '@babel/helper-environment-visitor': 7.18.9 + '@babel/helper-member-expression-to-functions': 7.20.7 + '@babel/helper-optimise-call-expression': 7.18.6 + '@babel/template': 7.20.7 + '@babel/traverse': 7.20.10 + '@babel/types': 7.20.7 + transitivePeerDependencies: + - supports-color + dev: true /@babel/helper-simple-access/7.19.4: resolution: {integrity: sha512-f9Xq6WqBFqaDfbCzn2w85hwklswz5qsKlh7f08w4Y9yhJHpnNC0QemtSkK5YyOY8kPGvyiwdzZksGUhnGdaUIg==} engines: {node: '>=6.9.0'} dependencies: - '@babel/types': 7.19.4 + '@babel/types': 7.20.7 dev: true /@babel/helper-split-export-declaration/7.18.6: resolution: {integrity: sha512-bde1etTx6ZyTmobl9LLMMQsaizFVZrquTEHOqKeQESMKo4PlObf+8+JA25ZsIpZhT/WEd39+vOdLXAFG/nELpA==} engines: {node: '>=6.9.0'} dependencies: - '@babel/types': 7.19.4 + '@babel/types': 7.20.7 dev: true /@babel/helper-string-parser/7.19.4: @@ -833,9 +922,9 @@ packages: resolution: {integrity: sha512-G+z3aOx2nfDHwX/kyVii5fJq+bgscg89/dJNWpYeKeBv3v9xX8EIabmx1k6u9LS04H7nROFVRVK+e3k0VHp+sw==} engines: {node: '>=6.9.0'} dependencies: - '@babel/template': 7.18.10 - '@babel/traverse': 7.19.4 - '@babel/types': 7.19.4 + '@babel/template': 7.20.7 + '@babel/traverse': 7.20.10 + '@babel/types': 7.20.7 transitivePeerDependencies: - supports-color dev: true @@ -861,7 +950,15 @@ packages: engines: {node: '>=6.0.0'} hasBin: true dependencies: - '@babel/types': 7.19.4 + '@babel/types': 7.20.7 + dev: true + + /@babel/parser/7.20.7: + resolution: {integrity: sha512-T3Z9oHybU+0vZlY9CiDSJQTD5ZapcW18ZctFMi0MOAl/4BjFF4ul7NVSARLdbGO5vDqy9eQiGTV0LtKfvCYvcg==} + engines: {node: '>=6.0.0'} + hasBin: true + dependencies: + '@babel/types': 7.20.7 dev: true /@babel/plugin-syntax-async-generators/7.8.4: @@ -869,7 +966,16 @@ packages: peerDependencies: '@babel/core': ^7.0.0-0 dependencies: - '@babel/helper-plugin-utils': 7.19.0 + '@babel/helper-plugin-utils': 7.20.2 + dev: true + + /@babel/plugin-syntax-async-generators/7.8.4_@babel+core@7.19.3: + resolution: {integrity: sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw==} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.19.3 + '@babel/helper-plugin-utils': 7.20.2 dev: true /@babel/plugin-syntax-bigint/7.8.3: @@ -877,7 +983,16 @@ packages: peerDependencies: '@babel/core': ^7.0.0-0 dependencies: - '@babel/helper-plugin-utils': 7.19.0 + '@babel/helper-plugin-utils': 7.20.2 + dev: true + + /@babel/plugin-syntax-bigint/7.8.3_@babel+core@7.19.3: + resolution: {integrity: sha512-wnTnFlG+YxQm3vDxpGE57Pj0srRU4sHE/mDkt1qv2YJJSeUAec2ma4WLUnUPeKjyrfntVwe/N6dCXpU+zL3Npg==} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.19.3 + '@babel/helper-plugin-utils': 7.20.2 dev: true /@babel/plugin-syntax-class-properties/7.12.13: @@ -885,7 +1000,16 @@ packages: peerDependencies: '@babel/core': ^7.0.0-0 dependencies: - '@babel/helper-plugin-utils': 7.19.0 + '@babel/helper-plugin-utils': 7.20.2 + dev: true + + /@babel/plugin-syntax-class-properties/7.12.13_@babel+core@7.19.3: + resolution: {integrity: sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA==} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.19.3 + '@babel/helper-plugin-utils': 7.20.2 dev: true /@babel/plugin-syntax-import-meta/7.10.4: @@ -893,7 +1017,16 @@ packages: peerDependencies: '@babel/core': ^7.0.0-0 dependencies: - '@babel/helper-plugin-utils': 7.19.0 + '@babel/helper-plugin-utils': 7.20.2 + dev: true + + /@babel/plugin-syntax-import-meta/7.10.4_@babel+core@7.19.3: + resolution: {integrity: sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g==} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.19.3 + '@babel/helper-plugin-utils': 7.20.2 dev: true /@babel/plugin-syntax-json-strings/7.8.3: @@ -901,7 +1034,16 @@ packages: peerDependencies: '@babel/core': ^7.0.0-0 dependencies: - '@babel/helper-plugin-utils': 7.19.0 + '@babel/helper-plugin-utils': 7.20.2 + dev: true + + /@babel/plugin-syntax-json-strings/7.8.3_@babel+core@7.19.3: + resolution: {integrity: sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA==} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.19.3 + '@babel/helper-plugin-utils': 7.20.2 dev: true /@babel/plugin-syntax-jsx/7.18.6: @@ -910,7 +1052,7 @@ packages: peerDependencies: '@babel/core': ^7.0.0-0 dependencies: - '@babel/helper-plugin-utils': 7.19.0 + '@babel/helper-plugin-utils': 7.20.2 /@babel/plugin-syntax-jsx/7.18.6_@babel+core@7.19.3: resolution: {integrity: sha512-6mmljtAedFGTWu2p/8WIORGwy+61PLgOMPOdazc7YoJ9ZCWUyFy3A6CpPkRKLKD1ToAesxX8KGEViAiLo9N+7Q==} @@ -919,7 +1061,7 @@ packages: '@babel/core': ^7.0.0-0 dependencies: '@babel/core': 7.19.3 - '@babel/helper-plugin-utils': 7.19.0 + '@babel/helper-plugin-utils': 7.20.2 dev: true /@babel/plugin-syntax-logical-assignment-operators/7.10.4: @@ -927,7 +1069,16 @@ packages: peerDependencies: '@babel/core': ^7.0.0-0 dependencies: - '@babel/helper-plugin-utils': 7.19.0 + '@babel/helper-plugin-utils': 7.20.2 + dev: true + + /@babel/plugin-syntax-logical-assignment-operators/7.10.4_@babel+core@7.19.3: + resolution: {integrity: sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig==} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.19.3 + '@babel/helper-plugin-utils': 7.20.2 dev: true /@babel/plugin-syntax-nullish-coalescing-operator/7.8.3: @@ -935,7 +1086,16 @@ packages: peerDependencies: '@babel/core': ^7.0.0-0 dependencies: - '@babel/helper-plugin-utils': 7.19.0 + '@babel/helper-plugin-utils': 7.20.2 + dev: true + + /@babel/plugin-syntax-nullish-coalescing-operator/7.8.3_@babel+core@7.19.3: + resolution: {integrity: sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ==} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.19.3 + '@babel/helper-plugin-utils': 7.20.2 dev: true /@babel/plugin-syntax-numeric-separator/7.10.4: @@ -943,7 +1103,16 @@ packages: peerDependencies: '@babel/core': ^7.0.0-0 dependencies: - '@babel/helper-plugin-utils': 7.19.0 + '@babel/helper-plugin-utils': 7.20.2 + dev: true + + /@babel/plugin-syntax-numeric-separator/7.10.4_@babel+core@7.19.3: + resolution: {integrity: sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug==} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.19.3 + '@babel/helper-plugin-utils': 7.20.2 dev: true /@babel/plugin-syntax-object-rest-spread/7.8.3: @@ -951,7 +1120,16 @@ packages: peerDependencies: '@babel/core': ^7.0.0-0 dependencies: - '@babel/helper-plugin-utils': 7.19.0 + '@babel/helper-plugin-utils': 7.20.2 + dev: true + + /@babel/plugin-syntax-object-rest-spread/7.8.3_@babel+core@7.19.3: + resolution: {integrity: sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA==} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.19.3 + '@babel/helper-plugin-utils': 7.20.2 dev: true /@babel/plugin-syntax-optional-catch-binding/7.8.3: @@ -959,7 +1137,16 @@ packages: peerDependencies: '@babel/core': ^7.0.0-0 dependencies: - '@babel/helper-plugin-utils': 7.19.0 + '@babel/helper-plugin-utils': 7.20.2 + dev: true + + /@babel/plugin-syntax-optional-catch-binding/7.8.3_@babel+core@7.19.3: + resolution: {integrity: sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q==} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.19.3 + '@babel/helper-plugin-utils': 7.20.2 dev: true /@babel/plugin-syntax-optional-chaining/7.8.3: @@ -967,7 +1154,16 @@ packages: peerDependencies: '@babel/core': ^7.0.0-0 dependencies: - '@babel/helper-plugin-utils': 7.19.0 + '@babel/helper-plugin-utils': 7.20.2 + dev: true + + /@babel/plugin-syntax-optional-chaining/7.8.3_@babel+core@7.19.3: + resolution: {integrity: sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg==} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.19.3 + '@babel/helper-plugin-utils': 7.20.2 dev: true /@babel/plugin-syntax-top-level-await/7.14.5: @@ -976,7 +1172,36 @@ packages: peerDependencies: '@babel/core': ^7.0.0-0 dependencies: - '@babel/helper-plugin-utils': 7.19.0 + '@babel/helper-plugin-utils': 7.20.2 + dev: true + + /@babel/plugin-syntax-top-level-await/7.14.5_@babel+core@7.19.3: + resolution: {integrity: sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.19.3 + '@babel/helper-plugin-utils': 7.20.2 + dev: true + + /@babel/plugin-syntax-typescript/7.20.0: + resolution: {integrity: sha512-rd9TkG+u1CExzS4SM1BlMEhMXwFLKVjOAFFCDx9PbX5ycJWDoWMcwdJH9RhkPu1dOgn5TrxLot/Gx6lWFuAUNQ==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/helper-plugin-utils': 7.20.2 + dev: true + + /@babel/plugin-syntax-typescript/7.20.0_@babel+core@7.19.3: + resolution: {integrity: sha512-rd9TkG+u1CExzS4SM1BlMEhMXwFLKVjOAFFCDx9PbX5ycJWDoWMcwdJH9RhkPu1dOgn5TrxLot/Gx6lWFuAUNQ==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.19.3 + '@babel/helper-plugin-utils': 7.20.2 dev: true /@babel/plugin-transform-modules-commonjs/7.18.6: @@ -986,7 +1211,7 @@ packages: '@babel/core': ^7.0.0-0 dependencies: '@babel/helper-module-transforms': 7.19.0 - '@babel/helper-plugin-utils': 7.19.0 + '@babel/helper-plugin-utils': 7.20.2 '@babel/helper-simple-access': 7.19.4 babel-plugin-dynamic-import-node: 2.3.3 transitivePeerDependencies: @@ -1010,7 +1235,7 @@ packages: '@babel/core': ^7.0.0-0 dependencies: '@babel/core': 7.19.3 - '@babel/helper-plugin-utils': 7.19.0 + '@babel/helper-plugin-utils': 7.20.2 dev: true /@babel/plugin-transform-react-jsx-source/7.18.6_@babel+core@7.19.3: @@ -1020,7 +1245,7 @@ packages: '@babel/core': ^7.0.0-0 dependencies: '@babel/core': 7.19.3 - '@babel/helper-plugin-utils': 7.19.0 + '@babel/helper-plugin-utils': 7.20.2 dev: true /@babel/plugin-transform-react-jsx/7.19.0: @@ -1031,7 +1256,7 @@ packages: dependencies: '@babel/helper-annotate-as-pure': 7.18.6 '@babel/helper-module-imports': 7.18.6 - '@babel/helper-plugin-utils': 7.19.0 + '@babel/helper-plugin-utils': 7.20.2 '@babel/plugin-syntax-jsx': 7.18.6 '@babel/types': 7.19.4 dev: true @@ -1045,11 +1270,37 @@ packages: '@babel/core': 7.19.3 '@babel/helper-annotate-as-pure': 7.18.6 '@babel/helper-module-imports': 7.18.6 - '@babel/helper-plugin-utils': 7.19.0 + '@babel/helper-plugin-utils': 7.20.2 '@babel/plugin-syntax-jsx': 7.18.6_@babel+core@7.19.3 '@babel/types': 7.19.4 dev: true + /@babel/plugin-transform-typescript/7.20.7: + resolution: {integrity: sha512-m3wVKEvf6SoszD8pu4NZz3PvfKRCMgk6D6d0Qi9hNnlM5M6CFS92EgF4EiHVLKbU0r/r7ty1hg7NPZwE7WRbYw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/helper-create-class-features-plugin': 7.20.7 + '@babel/helper-plugin-utils': 7.20.2 + '@babel/plugin-syntax-typescript': 7.20.0 + transitivePeerDependencies: + - supports-color + dev: true + + /@babel/preset-typescript/7.18.6: + resolution: {integrity: sha512-s9ik86kXBAnD760aybBucdpnLsAt0jK1xqJn2juOn9lkOvSHV60os5hxoVJsPzMQxvnUJFAlkont2DvvaYEBtQ==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/helper-plugin-utils': 7.19.0 + '@babel/helper-validator-option': 7.18.6 + '@babel/plugin-transform-typescript': 7.20.7 + transitivePeerDependencies: + - supports-color + dev: true + /@babel/runtime/7.18.9: resolution: {integrity: sha512-lkqXDcvlFT5rvEjiu6+QYO+1GXrEHRo2LOtS7E4GtX5ESIZOgepqsZBVIj6Pv+a6zqsya9VCgiK1KAK4BvJDAw==} engines: {node: '>=6.9.0'} @@ -1068,7 +1319,16 @@ packages: dependencies: '@babel/code-frame': 7.18.6 '@babel/parser': 7.19.4 - '@babel/types': 7.19.4 + '@babel/types': 7.20.7 + dev: true + + /@babel/template/7.20.7: + resolution: {integrity: sha512-8SegXApWe6VoNw0r9JHpSteLKTpTiLZ4rMlGIm9JQ18KiCtyQiAMEazujAHrUS5flrcqYZa75ukev3P6QmUwUw==} + engines: {node: '>=6.9.0'} + dependencies: + '@babel/code-frame': 7.18.6 + '@babel/parser': 7.20.7 + '@babel/types': 7.20.7 dev: true /@babel/traverse/7.17.3: @@ -1100,7 +1360,25 @@ packages: '@babel/helper-hoist-variables': 7.18.6 '@babel/helper-split-export-declaration': 7.18.6 '@babel/parser': 7.19.4 - '@babel/types': 7.19.4 + '@babel/types': 7.20.7 + debug: 4.3.4 + globals: 11.12.0 + transitivePeerDependencies: + - supports-color + dev: true + + /@babel/traverse/7.20.10: + resolution: {integrity: sha512-oSf1juCgymrSez8NI4A2sr4+uB/mFd9MXplYGPEBnfAuWmmyeVcHa6xLPiaRBcXkcb/28bgxmQLTVwFKE1yfsg==} + engines: {node: '>=6.9.0'} + dependencies: + '@babel/code-frame': 7.18.6 + '@babel/generator': 7.20.7 + '@babel/helper-environment-visitor': 7.18.9 + '@babel/helper-function-name': 7.19.0 + '@babel/helper-hoist-variables': 7.18.6 + '@babel/helper-split-export-declaration': 7.18.6 + '@babel/parser': 7.20.7 + '@babel/types': 7.20.7 debug: 4.3.4 globals: 11.12.0 transitivePeerDependencies: @@ -1122,6 +1400,19 @@ packages: '@babel/helper-string-parser': 7.19.4 '@babel/helper-validator-identifier': 7.19.1 to-fast-properties: 2.0.0 + dev: true + + /@babel/types/7.20.7: + resolution: {integrity: sha512-69OnhBxSSgK0OzTJai4kyPDiKTIe3j+ctaHdIGVbRahTLAT7L3R9oeXHC2aVSuGYt3cVnoAMDmOCgJ2yaiLMvg==} + engines: {node: '>=6.9.0'} + dependencies: + '@babel/helper-string-parser': 7.19.4 + '@babel/helper-validator-identifier': 7.19.1 + to-fast-properties: 2.0.0 + + /@bcoe/v8-coverage/0.2.3: + resolution: {integrity: sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==} + dev: true /@bloomberg/record-tuple-polyfill/0.0.4: resolution: {integrity: sha512-h0OYmPR3A5Dfbetra/GzxBAzQk8sH7LhRkRUTdagX6nrtlUgJGYCTv4bBK33jsTQw9HDd8PE2x1Ma+iRKEDUsw==} @@ -1137,33 +1428,6 @@ packages: tinycolor2: 1.4.2 dev: false - /@commitlint/cli/17.3.0: - resolution: {integrity: sha512-/H0md7TsKflKzVPz226VfXzVafJFO1f9+r2KcFvmBu08V0T56lZU1s8WL7/xlxqLMqBTVaBf7Ixtc4bskdEEZg==} - engines: {node: '>=v14'} - hasBin: true - dependencies: - '@commitlint/format': 17.0.0 - '@commitlint/lint': 17.3.0 - '@commitlint/load': 17.3.0 - '@commitlint/read': 17.2.0 - '@commitlint/types': 17.0.0 - execa: 5.1.1 - lodash.isfunction: 3.0.9 - resolve-from: 5.0.0 - resolve-global: 1.0.0 - yargs: 17.6.0 - transitivePeerDependencies: - - '@swc/core' - - '@swc/wasm' - dev: true - - /@commitlint/config-conventional/17.3.0: - resolution: {integrity: sha512-hgI+fN5xF8nhS9uG/V06xyT0nlcyvHHMkq0kwRSr96vl5BFlRGaL2C0/YY4kQagfU087tmj01bJkG9Ek98Wllw==} - engines: {node: '>=v14'} - dependencies: - conventional-changelog-conventionalcommits: 5.0.0 - dev: true - /@commitlint/config-validator/17.1.0: resolution: {integrity: sha512-Q1rRRSU09ngrTgeTXHq6ePJs2KrI+axPTgkNYDWSJIuS1Op4w3J30vUfSXjwn5YEJHklK3fSqWNHmBhmTR7Vdg==} engines: {node: '>=v14'} @@ -1171,49 +1435,13 @@ packages: '@commitlint/types': 17.0.0 ajv: 8.11.0 dev: true - - /@commitlint/ensure/17.3.0: - resolution: {integrity: sha512-kWbrQHDoW5veIUQx30gXoLOCjWvwC6OOEofhPCLl5ytRPBDAQObMbxTha1Bt2aSyNE/IrJ0s0xkdZ1Gi3wJwQg==} - engines: {node: '>=v14'} - dependencies: - '@commitlint/types': 17.0.0 - lodash.camelcase: 4.3.0 - lodash.kebabcase: 4.1.1 - lodash.snakecase: 4.1.1 - lodash.startcase: 4.4.0 - lodash.upperfirst: 4.3.1 - dev: true + optional: true /@commitlint/execute-rule/17.0.0: resolution: {integrity: sha512-nVjL/w/zuqjCqSJm8UfpNaw66V9WzuJtQvEnCrK4jDw6qKTmZB+1JQ8m6BQVZbNBcwfYdDNKnhIhqI0Rk7lgpQ==} engines: {node: '>=v14'} dev: true - - /@commitlint/format/17.0.0: - resolution: {integrity: sha512-MZzJv7rBp/r6ZQJDEodoZvdRM0vXu1PfQvMTNWFb8jFraxnISMTnPBWMMjr2G/puoMashwaNM//fl7j8gGV5lA==} - engines: {node: '>=v14'} - dependencies: - '@commitlint/types': 17.0.0 - chalk: 4.1.2 - dev: true - - /@commitlint/is-ignored/17.2.0: - resolution: {integrity: sha512-rgUPUQraHxoMLxiE8GK430HA7/R2vXyLcOT4fQooNrZq9ERutNrP6dw3gdKLkq22Nede3+gEHQYUzL4Wu75ndg==} - engines: {node: '>=v14'} - dependencies: - '@commitlint/types': 17.0.0 - semver: 7.3.7 - dev: true - - /@commitlint/lint/17.3.0: - resolution: {integrity: sha512-VilOTPg0i9A7CCWM49E9bl5jytfTvfTxf9iwbWAWNjxJ/A5mhPKbm3sHuAdwJ87tDk1k4j8vomYfH23iaY+1Rw==} - engines: {node: '>=v14'} - dependencies: - '@commitlint/is-ignored': 17.2.0 - '@commitlint/parse': 17.2.0 - '@commitlint/rules': 17.3.0 - '@commitlint/types': 17.0.0 - dev: true + optional: true /@commitlint/load/17.3.0: resolution: {integrity: sha512-u/pV6rCAJrCUN+HylBHLzZ4qj1Ew3+eN9GBPhNi9otGxtOfA8b+8nJSxaNbcC23Ins/kcpjGf9zPSVW7628Umw==} @@ -1238,31 +1466,7 @@ packages: - '@swc/core' - '@swc/wasm' dev: true - - /@commitlint/message/17.2.0: - resolution: {integrity: sha512-/4l2KFKxBOuoEn1YAuuNNlAU05Zt7sNsC9H0mPdPm3chOrT4rcX0pOqrQcLtdMrMkJz0gC7b3SF80q2+LtdL9Q==} - engines: {node: '>=v14'} - dev: true - - /@commitlint/parse/17.2.0: - resolution: {integrity: sha512-vLzLznK9Y21zQ6F9hf8D6kcIJRb2haAK5T/Vt1uW2CbHYOIfNsR/hJs0XnF/J9ctM20Tfsqv4zBitbYvVw7F6Q==} - engines: {node: '>=v14'} - dependencies: - '@commitlint/types': 17.0.0 - conventional-changelog-angular: 5.0.13 - conventional-commits-parser: 3.2.4 - dev: true - - /@commitlint/read/17.2.0: - resolution: {integrity: sha512-bbblBhrHkjxra3ptJNm0abxu7yeAaxumQ8ZtD6GIVqzURCETCP7Dm0tlVvGRDyXBuqX6lIJxh3W7oyKqllDsHQ==} - engines: {node: '>=v14'} - dependencies: - '@commitlint/top-level': 17.0.0 - '@commitlint/types': 17.0.0 - fs-extra: 10.1.0 - git-raw-commits: 2.0.11 - minimist: 1.2.6 - dev: true + optional: true /@commitlint/resolve-extends/17.3.0: resolution: {integrity: sha512-Lf3JufJlc5yVEtJWC8o4IAZaB8FQAUaVlhlAHRACd0TTFizV2Lk2VH70et23KgvbQNf7kQzHs/2B4QZalBv6Cg==} @@ -1275,29 +1479,7 @@ packages: resolve-from: 5.0.0 resolve-global: 1.0.0 dev: true - - /@commitlint/rules/17.3.0: - resolution: {integrity: sha512-s2UhDjC5yP2utx3WWqsnZRzjgzAX8BMwr1nltC0u0p8T/nzpkx4TojEfhlsOUj1t7efxzZRjUAV0NxNwdJyk+g==} - engines: {node: '>=v14'} - dependencies: - '@commitlint/ensure': 17.3.0 - '@commitlint/message': 17.2.0 - '@commitlint/to-lines': 17.0.0 - '@commitlint/types': 17.0.0 - execa: 5.1.1 - dev: true - - /@commitlint/to-lines/17.0.0: - resolution: {integrity: sha512-nEi4YEz04Rf2upFbpnEorG8iymyH7o9jYIVFBG1QdzebbIFET3ir+8kQvCZuBE5pKCtViE4XBUsRZz139uFrRQ==} - engines: {node: '>=v14'} - dev: true - - /@commitlint/top-level/17.0.0: - resolution: {integrity: sha512-dZrEP1PBJvodNWYPOYiLWf6XZergdksKQaT6i1KSROLdjf5Ai0brLOv5/P+CPxBeoj3vBxK4Ax8H1Pg9t7sHIQ==} - engines: {node: '>=v14'} - dependencies: - find-up: 5.0.0 - dev: true + optional: true /@commitlint/types/17.0.0: resolution: {integrity: sha512-hBAw6U+SkAT5h47zDMeOu3HSiD0SODw4Aq7rRNh1ceUmL7GyLKYhPbUvlRWqZ65XjBLPHZhFyQlRaPNz8qvUyQ==} @@ -1305,6 +1487,7 @@ packages: dependencies: chalk: 4.1.2 dev: true + optional: true /@cspotcode/source-map-support/0.8.1: resolution: {integrity: sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==} @@ -1312,6 +1495,7 @@ packages: dependencies: '@jridgewell/trace-mapping': 0.3.9 dev: true + optional: true /@csstools/postcss-color-function/1.1.1: resolution: {integrity: sha512-Bc0f62WmHdtRDjf5f3e2STwRAl89N2CLb+9iAwzrv4L2hncrbDwnQD9PCq0gtAt7pOI2leIV08HIBUd4jxD8cw==} @@ -1875,6 +2059,148 @@ packages: engines: {node: '>=8'} dev: true + /@jest/console/29.3.1: + resolution: {integrity: sha512-IRE6GD47KwcqA09RIWrabKdHPiKDGgtAL31xDxbi/RjQMsr+lY+ppxmHwY0dUEV3qvvxZzoe5Hl0RXZJOjQNUg==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + dependencies: + '@jest/types': 29.3.1 + '@types/node': 17.0.45 + chalk: 4.1.2 + jest-message-util: 29.3.1 + jest-util: 29.3.1 + slash: 3.0.0 + dev: true + + /@jest/core/29.3.1: + resolution: {integrity: sha512-0ohVjjRex985w5MmO5L3u5GR1O30DexhBSpuwx2P+9ftyqHdJXnk7IUWiP80oHMvt7ubHCJHxV0a0vlKVuZirw==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + peerDependencies: + node-notifier: ^8.0.1 || ^9.0.0 || ^10.0.0 + peerDependenciesMeta: + node-notifier: + optional: true + dependencies: + '@jest/console': 29.3.1 + '@jest/reporters': 29.3.1 + '@jest/test-result': 29.3.1 + '@jest/transform': 29.3.1 + '@jest/types': 29.3.1 + '@types/node': 17.0.45 + ansi-escapes: 4.3.2 + chalk: 4.1.2 + ci-info: 3.7.0 + exit: 0.1.2 + graceful-fs: 4.2.10 + jest-changed-files: 29.2.0 + jest-config: 29.3.1_@types+node@17.0.45 + jest-haste-map: 29.3.1 + jest-message-util: 29.3.1 + jest-regex-util: 29.2.0 + jest-resolve: 29.3.1 + jest-resolve-dependencies: 29.3.1 + jest-runner: 29.3.1 + jest-runtime: 29.3.1 + jest-snapshot: 29.3.1 + jest-util: 29.3.1 + jest-validate: 29.3.1 + jest-watcher: 29.3.1 + micromatch: 4.0.5 + pretty-format: 29.3.1 + slash: 3.0.0 + strip-ansi: 6.0.1 + transitivePeerDependencies: + - supports-color + - ts-node + dev: true + + /@jest/environment/29.3.1: + resolution: {integrity: sha512-pMmvfOPmoa1c1QpfFW0nXYtNLpofqo4BrCIk6f2kW4JFeNlHV2t3vd+3iDLf31e2ot2Mec0uqZfmI+U0K2CFag==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + dependencies: + '@jest/fake-timers': 29.3.1 + '@jest/types': 29.3.1 + '@types/node': 17.0.45 + jest-mock: 29.3.1 + dev: true + + /@jest/expect-utils/29.3.1: + resolution: {integrity: sha512-wlrznINZI5sMjwvUoLVk617ll/UYfGIZNxmbU+Pa7wmkL4vYzhV9R2pwVqUh4NWWuLQWkI8+8mOkxs//prKQ3g==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + dependencies: + jest-get-type: 29.2.0 + dev: true + + /@jest/expect/29.3.1: + resolution: {integrity: sha512-QivM7GlSHSsIAWzgfyP8dgeExPRZ9BIe2LsdPyEhCGkZkoyA+kGsoIzbKAfZCvvRzfZioKwPtCZIt5SaoxYCvg==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + dependencies: + expect: 29.3.1 + jest-snapshot: 29.3.1 + transitivePeerDependencies: + - supports-color + dev: true + + /@jest/fake-timers/29.3.1: + resolution: {integrity: sha512-iHTL/XpnDlFki9Tq0Q1GGuVeQ8BHZGIYsvCO5eN/O/oJaRzofG9Xndd9HuSDBI/0ZS79pg0iwn07OMTQ7ngF2A==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + dependencies: + '@jest/types': 29.3.1 + '@sinonjs/fake-timers': 9.1.2 + '@types/node': 17.0.45 + jest-message-util: 29.3.1 + jest-mock: 29.3.1 + jest-util: 29.3.1 + dev: true + + /@jest/globals/29.3.1: + resolution: {integrity: sha512-cTicd134vOcwO59OPaB6AmdHQMCtWOe+/DitpTZVxWgMJ+YvXL1HNAmPyiGbSHmF/mXVBkvlm8YYtQhyHPnV6Q==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + dependencies: + '@jest/environment': 29.3.1 + '@jest/expect': 29.3.1 + '@jest/types': 29.3.1 + jest-mock: 29.3.1 + transitivePeerDependencies: + - supports-color + dev: true + + /@jest/reporters/29.3.1: + resolution: {integrity: sha512-GhBu3YFuDrcAYW/UESz1JphEAbvUjaY2vShRZRoRY1mxpCMB3yGSJ4j9n0GxVlEOdCf7qjvUfBCrTUUqhVfbRA==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + peerDependencies: + node-notifier: ^8.0.1 || ^9.0.0 || ^10.0.0 + peerDependenciesMeta: + node-notifier: + optional: true + dependencies: + '@bcoe/v8-coverage': 0.2.3 + '@jest/console': 29.3.1 + '@jest/test-result': 29.3.1 + '@jest/transform': 29.3.1 + '@jest/types': 29.3.1 + '@jridgewell/trace-mapping': 0.3.17 + '@types/node': 17.0.45 + chalk: 4.1.2 + collect-v8-coverage: 1.0.1 + exit: 0.1.2 + glob: 7.2.3 + graceful-fs: 4.2.10 + istanbul-lib-coverage: 3.2.0 + istanbul-lib-instrument: 5.2.1 + istanbul-lib-report: 3.0.0 + istanbul-lib-source-maps: 4.0.1 + istanbul-reports: 3.1.5 + jest-message-util: 29.3.1 + jest-util: 29.3.1 + jest-worker: 29.3.1 + slash: 3.0.0 + string-length: 4.0.2 + strip-ansi: 6.0.1 + v8-to-istanbul: 9.0.1 + transitivePeerDependencies: + - supports-color + dev: true + /@jest/schemas/28.1.3: resolution: {integrity: sha512-/l/VWsdt/aBXgjshLWOFyFt3IVdYypu5y2Wn2rOO1un6nkqIn8SLXzgIMYXFyYsRWDyF5EthmKJMIdJvk08grg==} engines: {node: ^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0} @@ -1882,21 +2208,80 @@ packages: '@sinclair/typebox': 0.24.51 dev: true + /@jest/schemas/29.0.0: + resolution: {integrity: sha512-3Ab5HgYIIAnS0HjqJHQYZS+zXc4tUmTmBH3z83ajI6afXp8X3ZtdLX+nXx+I7LNkJD7uN9LAVhgnjDgZa2z0kA==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + dependencies: + '@sinclair/typebox': 0.24.51 + dev: true + + /@jest/source-map/29.2.0: + resolution: {integrity: sha512-1NX9/7zzI0nqa6+kgpSdKPK+WU1p+SJk3TloWZf5MzPbxri9UEeXX5bWZAPCzbQcyuAzubcdUHA7hcNznmRqWQ==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + dependencies: + '@jridgewell/trace-mapping': 0.3.17 + callsites: 3.1.0 + graceful-fs: 4.2.10 + dev: true + + /@jest/test-result/29.3.1: + resolution: {integrity: sha512-qeLa6qc0ddB0kuOZyZIhfN5q0e2htngokyTWsGriedsDhItisW7SDYZ7ceOe57Ii03sL988/03wAcBh3TChMGw==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + dependencies: + '@jest/console': 29.3.1 + '@jest/types': 29.3.1 + '@types/istanbul-lib-coverage': 2.0.4 + collect-v8-coverage: 1.0.1 + dev: true + + /@jest/test-sequencer/29.3.1: + resolution: {integrity: sha512-IqYvLbieTv20ArgKoAMyhLHNrVHJfzO6ARZAbQRlY4UGWfdDnLlZEF0BvKOMd77uIiIjSZRwq3Jb3Fa3I8+2UA==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + dependencies: + '@jest/test-result': 29.3.1 + graceful-fs: 4.2.10 + jest-haste-map: 29.3.1 + slash: 3.0.0 + dev: true + /@jest/transform/28.1.3: resolution: {integrity: sha512-u5dT5di+oFI6hfcLOHGTAfmUxFRrjK+vnaP0kkVow9Md/M7V/MxqQMOz/VV25UZO8pzeA9PjfTpOu6BDuwSPQA==} engines: {node: ^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0} dependencies: '@babel/core': 7.19.3 - '@jest/types': 28.1.3 + '@jest/types': 28.1.3 + '@jridgewell/trace-mapping': 0.3.17 + babel-plugin-istanbul: 6.1.1 + chalk: 4.1.2 + convert-source-map: 1.9.0 + fast-json-stable-stringify: 2.1.0 + graceful-fs: 4.2.10 + jest-haste-map: 28.1.3 + jest-regex-util: 28.0.2 + jest-util: 28.1.3 + micromatch: 4.0.5 + pirates: 4.0.5 + slash: 3.0.0 + write-file-atomic: 4.0.2 + transitivePeerDependencies: + - supports-color + dev: true + + /@jest/transform/29.3.1: + resolution: {integrity: sha512-8wmCFBTVGYqFNLWfcOWoVuMuKYPUBTnTMDkdvFtAYELwDOl9RGwOsvQWGPFxDJ8AWY9xM/8xCXdqmPK3+Q5Lug==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + dependencies: + '@babel/core': 7.19.3 + '@jest/types': 29.3.1 '@jridgewell/trace-mapping': 0.3.17 babel-plugin-istanbul: 6.1.1 chalk: 4.1.2 - convert-source-map: 1.9.0 + convert-source-map: 2.0.0 fast-json-stable-stringify: 2.1.0 graceful-fs: 4.2.10 - jest-haste-map: 28.1.3 - jest-regex-util: 28.0.2 - jest-util: 28.1.3 + jest-haste-map: 29.3.1 + jest-regex-util: 29.2.0 + jest-util: 29.3.1 micromatch: 4.0.5 pirates: 4.0.5 slash: 3.0.0 @@ -1928,6 +2313,18 @@ packages: chalk: 4.1.2 dev: true + /@jest/types/29.3.1: + resolution: {integrity: sha512-d0S0jmmTpjnhCmNpApgX3jrUZgZ22ivKJRvL2lli5hpCRoNnp1f85r2/wpKfXuYu8E7Jjh1hGfhPyup1NM5AmA==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + dependencies: + '@jest/schemas': 29.0.0 + '@types/istanbul-lib-coverage': 2.0.4 + '@types/istanbul-reports': 3.0.1 + '@types/node': 17.0.45 + '@types/yargs': 17.0.17 + chalk: 4.1.2 + dev: true + /@jridgewell/gen-mapping/0.1.1: resolution: {integrity: sha512-sQXCasFk+U8lWYEe66WxRDOE9PjVz4vSM51fTu3Hw+ClTpUSQb718772vH3pyS5pShp6lvQM7SxgIDXXXmOX7w==} engines: {node: '>=6.0.0'} @@ -1979,6 +2376,7 @@ packages: '@jridgewell/resolve-uri': 3.1.0 '@jridgewell/sourcemap-codec': 1.4.14 dev: true + optional: true /@jsdevtools/ono/7.1.3: resolution: {integrity: sha512-4JQNk+3mVzK3xh2rqd6RB4J46qUR19azEHBneZyTZM+c456qOrbbM/5xcR8huNCCcbVt7+UmizG6GuUvPvKUYg==} @@ -2484,6 +2882,34 @@ packages: engines: {node: '>=14'} dev: false + /@rollup/plugin-commonjs/22.0.2_rollup@2.78.1: + resolution: {integrity: sha512-//NdP6iIwPbMTcazYsiBMbJW7gfmpHom33u1beiIoHDEM0Q9clvtQB1T0efvMqHeKsGohiHo97BCPCkBXdscwg==} + engines: {node: '>= 12.0.0'} + peerDependencies: + rollup: ^2.68.0 + dependencies: + '@rollup/pluginutils': 3.1.0_rollup@2.78.1 + commondir: 1.0.1 + estree-walker: 2.0.2 + glob: 7.2.3 + is-reference: 1.2.1 + magic-string: 0.25.9 + resolve: 1.22.1 + rollup: 2.78.1 + dev: true + + /@rollup/pluginutils/3.1.0_rollup@2.78.1: + resolution: {integrity: sha512-GksZ6pr6TpIjHm8h9lSQ8pi8BE9VeubNT0OMJ3B5uZJ8pz73NPiqOtCog/x2/QzM1ENChPKxMDhiQuRHsqc+lg==} + engines: {node: '>= 8.0.0'} + peerDependencies: + rollup: ^1.20.0||^2.0.0 + dependencies: + '@types/estree': 0.0.39 + estree-walker: 1.0.1 + picomatch: 2.3.1 + rollup: 2.78.1 + dev: true + /@rollup/pluginutils/4.2.1: resolution: {integrity: sha512-iKnFXr7NkdZAIHiIWE+BX5ULi/ucVFYWD6TbAV+rZctiRTY2PL6tsIKhoIOaoskiWAkgu+VsbXgUVDNLHf+InQ==} engines: {node: '>= 8.0.0'} @@ -2528,10 +2954,81 @@ packages: selderee: 0.6.0 dev: true + /@sentry/browser/7.28.1: + resolution: {integrity: sha512-N8j93IcrWKWorfJ5D+RSKVAvcR4S5tIcZ/HvFPMrQWnfVa/jtJcrKThdjZYteA0wjmPiy8/D3KA8nB91yulBPA==} + engines: {node: '>=8'} + dependencies: + '@sentry/core': 7.28.1 + '@sentry/replay': 7.28.1_@sentry+browser@7.28.1 + '@sentry/types': 7.28.1 + '@sentry/utils': 7.28.1 + tslib: 1.14.1 + dev: false + + /@sentry/core/7.28.1: + resolution: {integrity: sha512-7wvnuvn/mrAfcugWoCG/3pqDIrUgH5t+HisMJMGw0h9Tc33KqrmqMDCQVvjlrr2pWrw/vuUCFdm8CbUHJ832oQ==} + engines: {node: '>=8'} + dependencies: + '@sentry/types': 7.28.1 + '@sentry/utils': 7.28.1 + tslib: 1.14.1 + dev: false + + /@sentry/react/7.28.1_react@18.2.0: + resolution: {integrity: sha512-sFKK7uDREh84GyJcXDNuiQQ5VhLx7XJTOAdELxLv4HEI6BxbBRz0zNRQiKamTRkz9NmL7bZtld5TfbpOo9kijg==} + engines: {node: '>=8'} + peerDependencies: + react: 15.x || 16.x || 17.x || 18.x + dependencies: + '@sentry/browser': 7.28.1 + '@sentry/types': 7.28.1 + '@sentry/utils': 7.28.1 + hoist-non-react-statics: 3.3.2 + react: 18.2.0 + tslib: 1.14.1 + dev: false + + /@sentry/replay/7.28.1_@sentry+browser@7.28.1: + resolution: {integrity: sha512-Os0PzMjKlwtHwzTU0kfVzGzsi4Vaj3g2arCl4Qnr3b6kYTb9WOFZo/n/v56ss7Z+nZG3K8W5PisoD4MRsRJRig==} + engines: {node: '>=12'} + peerDependencies: + '@sentry/browser': '>=7.24.0' + dependencies: + '@sentry/browser': 7.28.1 + '@sentry/core': 7.28.1 + '@sentry/types': 7.28.1 + '@sentry/utils': 7.28.1 + dev: false + + /@sentry/types/7.28.1: + resolution: {integrity: sha512-DvSplMVrVEmOzR2M161V5+B8Up3vR71xMqJOpWTzE9TqtFJRGPtqT/5OBsNJJw1+/j2ssMcnKwbEo9Q2EGeS6g==} + engines: {node: '>=8'} + dev: false + + /@sentry/utils/7.28.1: + resolution: {integrity: sha512-75/jzLUO9HH09iC9TslNimGbxOP3jgn89P+q7uR+rp2fJfRExHVeKJZQdK0Ij4/SmE7TJ3Uh2r154N0INZEx1g==} + engines: {node: '>=8'} + dependencies: + '@sentry/types': 7.28.1 + tslib: 1.14.1 + dev: false + /@sinclair/typebox/0.24.51: resolution: {integrity: sha512-1P1OROm/rdubP5aFDSZQILU0vrLCJ4fvHt6EoqHEM+2D/G5MK3bIaymUKLit8Js9gbns5UyJnkP/TZROLw4tUA==} dev: true + /@sinonjs/commons/1.8.6: + resolution: {integrity: sha512-Ky+XkAkqPZSm3NLBeUng77EBQl3cmeJhITaGHdYH8kjVB+aun3S4XBRti2zt17mtt0mIUDiNxYeoJm6drVvBJQ==} + dependencies: + type-detect: 4.0.8 + dev: true + + /@sinonjs/fake-timers/9.1.2: + resolution: {integrity: sha512-BPS4ynJW/o92PUR4wgriz2Ud5gpST5vz6GQfMixEDK0Z8ZCUv2M7SkBLykH56T++Xs+8ln9zTGbOvNGIe02/jw==} + dependencies: + '@sinonjs/commons': 1.8.6 + dev: true + /@stackblitz/sdk/1.8.1: resolution: {integrity: sha512-hjYfjOLQBNDzqPD5AkGJOD7j+uvFwGocDTmwNqmLCWR5EY7BDtcOtDY2M+91v/twYWWNvI7N8UNIgojCPNgzMA==} dev: true @@ -2666,7 +3163,7 @@ packages: resolution: {integrity: sha512-PPy94U/EiPQ2dY0b4jEqj4QOdDRq6DG7aTHjpGaL8HlKSHkpU1DpjfywCXTJqtOdCo2FywjWvg0U2FhqMeUJaA==} engines: {node: '>=10'} dependencies: - '@babel/types': 7.19.4 + '@babel/types': 7.20.7 entities: 4.4.0 dev: true @@ -2834,18 +3331,22 @@ packages: /@tsconfig/node10/1.0.9: resolution: {integrity: sha512-jNsYVVxU8v5g43Erja32laIDHXeoNvFEpX33OK4d6hljo3jDhCBDhx5dhCCTMWUojscpAagGiRkBKxpdl9fxqA==} dev: true + optional: true /@tsconfig/node12/1.0.11: resolution: {integrity: sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==} dev: true + optional: true /@tsconfig/node14/1.0.3: resolution: {integrity: sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==} dev: true + optional: true /@tsconfig/node16/1.0.3: resolution: {integrity: sha512-yOlFc+7UtL/89t2ZhjPvvB/DeAr3r+Dq58IgzsFkOAvVC6NMJXmCGjbptdXdR9qsX7pKcTL+s87FtYREi2dEEQ==} dev: true + optional: true /@types/argparse/1.0.38: resolution: {integrity: sha512-ebDJ9b0e702Yr7pWgB0jzm+CX4Srzz8RcXtLJDJB+BSccqMa36uyH/zUsSYao5+BD1ytv3k3rPYCq4mAE1hsXA==} @@ -2854,8 +3355,8 @@ packages: /@types/babel__core/7.1.19: resolution: {integrity: sha512-WEOTgRsbYkvA/KCsDwVEGkd7WAr1e3g31VHQ8zy5gul/V1qKullU/BU5I68X5v7V3GnB9eotmom4v5a5gjxorw==} dependencies: - '@babel/parser': 7.19.4 - '@babel/types': 7.19.4 + '@babel/parser': 7.20.7 + '@babel/types': 7.20.7 '@types/babel__generator': 7.6.4 '@types/babel__template': 7.4.1 '@types/babel__traverse': 7.18.2 @@ -2864,20 +3365,20 @@ packages: /@types/babel__generator/7.6.4: resolution: {integrity: sha512-tFkciB9j2K755yrTALxD44McOrk+gfpIpvC3sxHjRawj6PfnQxrse4Clq5y/Rq+G3mrBurMax/lG8Qn2t9mSsg==} dependencies: - '@babel/types': 7.19.4 + '@babel/types': 7.20.7 dev: true /@types/babel__template/7.4.1: resolution: {integrity: sha512-azBFKemX6kMg5Io+/rdGT0dkGreboUVR0Cdm3fz9QJWpaQGJRQXl7C+6hOTCZcMll7KFyEQpgbYI2lHdsS4U7g==} dependencies: - '@babel/parser': 7.19.4 - '@babel/types': 7.19.4 + '@babel/parser': 7.20.7 + '@babel/types': 7.20.7 dev: true /@types/babel__traverse/7.18.2: resolution: {integrity: sha512-FcFaxOr2V5KZCviw1TnutEMVUVsGt4D2hP1TAfXZAMKuHYW3xQhe3jTxNPWutgCJ3/X1c5yX8ZoGVEItxKbwBg==} dependencies: - '@babel/types': 7.19.4 + '@babel/types': 7.20.7 dev: true /@types/codemirror/5.60.5: @@ -3071,6 +3572,10 @@ packages: '@types/ms': 0.7.31 dev: true + /@types/deep-diff/1.0.2: + resolution: {integrity: sha512-WD2O611C7Oz7RSwKbSls8LaznKfWfXh39CHY9Amd8FhQz+NJRe20nUHhYpOopVq9M2oqDZd4L6AzqJIXQycxiA==} + dev: true + /@types/eslint-scope/3.7.4: resolution: {integrity: sha512-9K4zoImiZc3HlIp6AVUDE4CWYx22a+lhSZMYNpbjW04+YF0KWj4pJXnEMjdnFTiQibFFmElcsasJXDbdI/EPhA==} dependencies: @@ -3091,6 +3596,10 @@ packages: '@types/estree': 1.0.0 dev: true + /@types/estree/0.0.39: + resolution: {integrity: sha512-EYNwp3bU+98cpU4lAWYYL7Zz+2gryWH1qbdDTidVd6hkiR6weksdbMadyXKXNPEkQFhXM+hVO9ZygomHXp+AIw==} + dev: true + /@types/estree/0.0.51: resolution: {integrity: sha512-CuPgU6f3eT/XgKKPqKd/gLZV1Xmvf1a2R5POBOGQa6uv82xpls89HU5zKeVoyR8XzHd1RGNOlQlvUe3CFkjWNQ==} dev: true @@ -3153,6 +3662,13 @@ packages: '@types/istanbul-lib-report': 3.0.0 dev: true + /@types/jest/29.0.3: + resolution: {integrity: sha512-F6ukyCTwbfsEX5F2YmVYmM5TcTHy1q9P5rWlRbrk56KyMh3v9xRGUO3aa8+SkvMi0SHXtASJv1283enXimC0Og==} + dependencies: + expect: 29.3.1 + pretty-format: 29.3.1 + dev: true + /@types/js-cookie/2.2.7: resolution: {integrity: sha512-aLkWa0C0vO5b4Sr798E26QgOkss68Un0bLjs7u9qxzPT5CG+8DuNTffWES58YzJs3hrVAOs1wonycqEBqNJubA==} dev: false @@ -3184,14 +3700,16 @@ packages: resolution: {integrity: sha512-K0VQKziLUWkVKiRVrx4a40iPaxTUefQmjtkQofBkYRcoaaL/8rhwDWww9qWbrgicNOgnpIsMxyNIUM4+n6dUIA==} dev: true - /@types/minimist/1.2.2: - resolution: {integrity: sha512-jhuKLIRrhvCPLqwPcx6INqmKeiA5EWrsCOPhrlFSrbrmU4ZMPjj5Ul/oLCMDO98XRUIwVm78xICz4EPCektzeQ==} - dev: true - /@types/ms/0.7.31: resolution: {integrity: sha512-iiUgKzV9AuaEkZqkOLDIvlQiL6ltuZd9tGcW3gwpnX8JbuiuhFlEGmmFXEXkN50Cvq7Os88IY2v0dkDqXYWVgA==} dev: true + /@types/node-forge/1.3.1: + resolution: {integrity: sha512-hvQ7Wav8I0j9amPXJtGqI/Yx70zeF62UKlAYq8JPm0nHzjKKzZvo9iR3YI2MiOghZRlOI+tQ2f6D+G6vVf4V2Q==} + dependencies: + '@types/node': 17.0.45 + dev: true + /@types/node/12.20.24: resolution: {integrity: sha512-yxDeaQIAJlMav7fH5AQqPH1u8YIuhYJXYBzxaQ4PifsU0GDO38MSdmEDeRlIxrKbC6NbEaaEHDanWb+y30U8SQ==} dev: true @@ -3199,6 +3717,7 @@ packages: /@types/node/14.18.32: resolution: {integrity: sha512-Y6S38pFr04yb13qqHf8uk1nHE3lXgQ30WZbv1mLliV9pt0NjvqdWttLcrOYLnXbOafknVYRHZGoMSpR9UwfYow==} dev: true + optional: true /@types/node/17.0.45: resolution: {integrity: sha512-w+tIMs3rq2afQdsPJlODhoUEKzFP1ayaoyl1CcnwtIlsVe7K7bA1NGm4s3PraqTLlXnbIN84zuBlxBWo1u9BLw==} @@ -3215,6 +3734,10 @@ packages: resolution: {integrity: sha512-SuT16Q1K51EAVPz1K29DJ/sXjhSQ0zjvsypYJ6tlwVsRV9jwW5Adq2ch8Dq8kDBCkYnELS7N7VNCSB5nC56t/g==} dev: true + /@types/prettier/2.7.2: + resolution: {integrity: sha512-KufADq8uQqo1pYKVIYzfKbJfBAc0sOeXqGbFaSpv8MRmC/zXgowNZmFcbngndGk922QDmOASEXUZCaY48gs4cg==} + dev: true + /@types/prop-types/15.7.5: resolution: {integrity: sha512-JCB8C6SnDoQf0cNycqd/35A7MjcnK+ZTqE7judS6o7utxUCg6imJg3QK2qzHKszlTjcj2cn+NwMB2i96ubpj7w==} @@ -3246,12 +3769,24 @@ packages: /@types/scheduler/0.16.2: resolution: {integrity: sha512-hppQEBDmlwhFAXKJX2KnWLYu5yMfi91yazPb2l+lbJiwW+wdo1gNeRA+3RgNSO39WYX2euey41KEwnqesU2Jew==} + /@types/stack-utils/2.0.1: + resolution: {integrity: sha512-Hl219/BT5fLAaz6NDkSuhzasy49dwQS/DSdu4MdggFB8zcXv7vflBI3xp7FEmkmdDkBUI2bPUNeMttp2knYdxw==} + dev: true + /@types/tern/0.23.4: resolution: {integrity: sha512-JAUw1iXGO1qaWwEOzxTKJZ/5JxVeON9kvGZ/osgZaJImBnyjyn0cjovPsf6FNLmyGY8Vw9DoXZCMlfMkMwHRWg==} dependencies: '@types/estree': 1.0.0 dev: true + /@types/toposort/2.0.3: + resolution: {integrity: sha512-jRtyvEu0Na/sy0oIxBW0f6wPQjidgVqlmCTJVHEGTNEUdL1f0YSvdPzHY7nX7MUWAZS6zcAa0KkqofHjy/xDZQ==} + dev: true + + /@types/unescape-js/1.0.0: + resolution: {integrity: sha512-5Mv0p9cuMl4fzLHRClxAgjOHlo5aIkzk/1moeIteQe75uOGu1IkCsT+igG5asMj4cMSnBS6TNYkBXi8pcAIa0g==} + dev: true + /@types/unist/2.0.6: resolution: {integrity: sha512-PBjIUxZHOuj0R15/xuwJYjFi+KZdNFrehocChv4g5hu6aFroHue8m0lBP0POdK2nKzbw0cgV1mws8+V/JAcEkQ==} dev: true @@ -3272,7 +3807,7 @@ packages: '@types/yargs-parser': 21.0.0 dev: true - /@typescript-eslint/eslint-plugin/5.36.1_2pbiccfevvtrzq7jtratuel47i: + /@typescript-eslint/eslint-plugin/5.36.1_d563juxnks3tstp6fg4vrqzsky: resolution: {integrity: sha512-iC40UK8q1tMepSDwiLbTbMXKDxzNy+4TfPWgIL661Ym0sD42vRcQU93IsZIrmi+x292DBr60UI/gSwfdVYexCA==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} peerDependencies: @@ -3283,12 +3818,11 @@ packages: typescript: optional: true dependencies: - '@typescript-eslint/parser': 5.36.1_eslint@8.25.0 + '@typescript-eslint/parser': 5.36.1 '@typescript-eslint/scope-manager': 5.36.1 - '@typescript-eslint/type-utils': 5.36.1_eslint@8.25.0 - '@typescript-eslint/utils': 5.36.1_eslint@8.25.0 + '@typescript-eslint/type-utils': 5.36.1 + '@typescript-eslint/utils': 5.36.1 debug: 4.3.4 - eslint: 8.25.0 functional-red-black-tree: 1.0.1 ignore: 5.2.1 regexpp: 3.2.0 @@ -3298,7 +3832,7 @@ packages: - supports-color dev: true - /@typescript-eslint/eslint-plugin/5.36.1_d563juxnks3tstp6fg4vrqzsky: + /@typescript-eslint/eslint-plugin/5.36.1_pydmdwhbhioitmd4vyrmo4rkou: resolution: {integrity: sha512-iC40UK8q1tMepSDwiLbTbMXKDxzNy+4TfPWgIL661Ym0sD42vRcQU93IsZIrmi+x292DBr60UI/gSwfdVYexCA==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} peerDependencies: @@ -3309,16 +3843,17 @@ packages: typescript: optional: true dependencies: - '@typescript-eslint/parser': 5.36.1 + '@typescript-eslint/parser': 5.40.0_typescript@4.5.5 '@typescript-eslint/scope-manager': 5.36.1 - '@typescript-eslint/type-utils': 5.36.1 - '@typescript-eslint/utils': 5.36.1 + '@typescript-eslint/type-utils': 5.36.1_typescript@4.5.5 + '@typescript-eslint/utils': 5.36.1_typescript@4.5.5 debug: 4.3.4 functional-red-black-tree: 1.0.1 ignore: 5.2.1 regexpp: 3.2.0 semver: 7.3.8 - tsutils: 3.21.0 + tsutils: 3.21.0_typescript@4.5.5 + typescript: 4.5.5 transitivePeerDependencies: - supports-color dev: true @@ -3341,8 +3876,8 @@ packages: - supports-color dev: true - /@typescript-eslint/parser/5.36.1_eslint@8.25.0: - resolution: {integrity: sha512-/IsgNGOkBi7CuDfUbwt1eOqUXF9WGVBW9dwEe1pi+L32XrTsZIgmDFIi2RxjzsvB/8i+MIf5JIoTEH8LOZ368A==} + /@typescript-eslint/parser/5.40.0_typescript@4.5.5: + resolution: {integrity: sha512-Ah5gqyX2ySkiuYeOIDg7ap51/b63QgWZA7w6AHtFrag7aH0lRQPbLzUjk0c9o5/KZ6JRkTTDKShL4AUrQa6/hw==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} peerDependencies: eslint: ^6.0.0 || ^7.0.0 || ^8.0.0 @@ -3351,11 +3886,11 @@ packages: typescript: optional: true dependencies: - '@typescript-eslint/scope-manager': 5.36.1 - '@typescript-eslint/types': 5.36.1 - '@typescript-eslint/typescript-estree': 5.36.1 + '@typescript-eslint/scope-manager': 5.40.0 + '@typescript-eslint/types': 5.40.0 + '@typescript-eslint/typescript-estree': 5.40.0_typescript@4.5.5 debug: 4.3.4 - eslint: 8.25.0 + typescript: 4.5.5 transitivePeerDependencies: - supports-color dev: true @@ -3414,7 +3949,7 @@ packages: - supports-color dev: true - /@typescript-eslint/type-utils/5.36.1_eslint@8.25.0: + /@typescript-eslint/type-utils/5.36.1_typescript@4.5.5: resolution: {integrity: sha512-xfZhfmoQT6m3lmlqDvDzv9TiCYdw22cdj06xY0obSznBsT///GK5IEZQdGliXpAOaRL34o8phEvXzEo/VJx13Q==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} peerDependencies: @@ -3424,11 +3959,11 @@ packages: typescript: optional: true dependencies: - '@typescript-eslint/typescript-estree': 5.36.1 - '@typescript-eslint/utils': 5.36.1_eslint@8.25.0 + '@typescript-eslint/typescript-estree': 5.36.1_typescript@4.5.5 + '@typescript-eslint/utils': 5.36.1_typescript@4.5.5 debug: 4.3.4 - eslint: 8.25.0 - tsutils: 3.21.0 + tsutils: 3.21.0_typescript@4.5.5 + typescript: 4.5.5 transitivePeerDependencies: - supports-color dev: true @@ -3463,6 +3998,27 @@ packages: - supports-color dev: true + /@typescript-eslint/typescript-estree/5.36.1_typescript@4.5.5: + resolution: {integrity: sha512-ih7V52zvHdiX6WcPjsOdmADhYMDN15SylWRZrT2OMy80wzKbc79n8wFW0xpWpU0x3VpBz/oDgTm2xwDAnFTl+g==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + peerDependencies: + typescript: '*' + peerDependenciesMeta: + typescript: + optional: true + dependencies: + '@typescript-eslint/types': 5.36.1 + '@typescript-eslint/visitor-keys': 5.36.1 + debug: 4.3.4 + globby: 11.1.0 + is-glob: 4.0.3 + semver: 7.3.8 + tsutils: 3.21.0_typescript@4.5.5 + typescript: 4.5.5 + transitivePeerDependencies: + - supports-color + dev: true + /@typescript-eslint/typescript-estree/5.40.0: resolution: {integrity: sha512-b0GYlDj8TLTOqwX7EGbw2gL5EXS2CPEWhF9nGJiGmEcmlpNBjyHsTwbqpyIEPVpl6br4UcBOYlcI2FJVtJkYhg==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} @@ -3483,6 +4039,27 @@ packages: - supports-color dev: true + /@typescript-eslint/typescript-estree/5.40.0_typescript@4.5.5: + resolution: {integrity: sha512-b0GYlDj8TLTOqwX7EGbw2gL5EXS2CPEWhF9nGJiGmEcmlpNBjyHsTwbqpyIEPVpl6br4UcBOYlcI2FJVtJkYhg==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + peerDependencies: + typescript: '*' + peerDependenciesMeta: + typescript: + optional: true + dependencies: + '@typescript-eslint/types': 5.40.0 + '@typescript-eslint/visitor-keys': 5.40.0 + debug: 4.3.4 + globby: 11.1.0 + is-glob: 4.0.3 + semver: 7.3.8 + tsutils: 3.21.0_typescript@4.5.5 + typescript: 4.5.5 + transitivePeerDependencies: + - supports-color + dev: true + /@typescript-eslint/typescript-estree/5.40.0_typescript@4.8.4: resolution: {integrity: sha512-b0GYlDj8TLTOqwX7EGbw2gL5EXS2CPEWhF9nGJiGmEcmlpNBjyHsTwbqpyIEPVpl6br4UcBOYlcI2FJVtJkYhg==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} @@ -3521,7 +4098,7 @@ packages: - typescript dev: true - /@typescript-eslint/utils/5.36.1_eslint@8.25.0: + /@typescript-eslint/utils/5.36.1_typescript@4.5.5: resolution: {integrity: sha512-lNj4FtTiXm5c+u0pUehozaUWhh7UYKnwryku0nxJlYUEWetyG92uw2pr+2Iy4M/u0ONMKzfrx7AsGBTCzORmIg==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} peerDependencies: @@ -3530,10 +4107,9 @@ packages: '@types/json-schema': 7.0.11 '@typescript-eslint/scope-manager': 5.36.1 '@typescript-eslint/types': 5.36.1 - '@typescript-eslint/typescript-estree': 5.36.1 - eslint: 8.25.0 + '@typescript-eslint/typescript-estree': 5.36.1_typescript@4.5.5 eslint-scope: 5.1.1 - eslint-utils: 3.0.0_eslint@8.25.0 + eslint-utils: 3.0.0 transitivePeerDependencies: - supports-color - typescript @@ -3557,25 +4133,6 @@ packages: - typescript dev: true - /@typescript-eslint/utils/5.40.0_eslint@8.25.0: - resolution: {integrity: sha512-MO0y3T5BQ5+tkkuYZJBjePewsY+cQnfkYeRqS6tPh28niiIwPnQ1t59CSRcs1ZwJJNOdWw7rv9pF8aP58IMihA==} - engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} - peerDependencies: - eslint: ^6.0.0 || ^7.0.0 || ^8.0.0 - dependencies: - '@types/json-schema': 7.0.11 - '@typescript-eslint/scope-manager': 5.40.0 - '@typescript-eslint/types': 5.40.0 - '@typescript-eslint/typescript-estree': 5.40.0 - eslint: 8.25.0 - eslint-scope: 5.1.1 - eslint-utils: 3.0.0_eslint@8.25.0 - semver: 7.3.8 - transitivePeerDependencies: - - supports-color - - typescript - dev: true - /@typescript-eslint/visitor-keys/5.36.1: resolution: {integrity: sha512-ojB9aRyRFzVMN3b5joSYni6FAS10BBSCAfKJhjJAV08t/a95aM6tAhz+O1jF+EtgxktuSO3wJysp2R+Def/IWQ==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} @@ -3936,35 +4493,6 @@ packages: - typescript dev: true - /@umijs/lint/4.0.36_nc6yd4bwypbhcidfskuo37yccq: - resolution: {integrity: sha512-jFySi0NfDbAuYl0R+UmUxNgDWiL050kfVWk5paDZZ5+SjT4zuVEAUCY+wH9ggf+feenY52ZbIOXWSF2A6/LCVA==} - dependencies: - '@babel/core': 7.18.9 - '@babel/eslint-parser': 7.18.9_442gibva36idvxayifa5d4cjjm - '@stylelint/postcss-css-in-js': 0.38.0_vlaoyimx2imj5gm3vlc667gbzm - '@typescript-eslint/eslint-plugin': 5.36.1_2pbiccfevvtrzq7jtratuel47i - '@typescript-eslint/parser': 5.36.1_eslint@8.25.0 - '@umijs/babel-preset-umi': 4.0.36 - eslint-plugin-jest: 26.1.5_wxut34qkzjlpmrfbzmy3qlkfma - eslint-plugin-react: 7.29.4_eslint@8.25.0 - eslint-plugin-react-hooks: 4.5.0_eslint@8.25.0 - postcss: 8.4.18 - postcss-syntax: 0.36.2_postcss@8.4.18 - stylelint-config-standard: 25.0.0_stylelint@14.16.0 - transitivePeerDependencies: - - eslint - - jest - - postcss-html - - postcss-jsx - - postcss-less - - postcss-markdown - - postcss-scss - - styled-components - - stylelint - - supports-color - - typescript - dev: true - /@umijs/lint/4.0.40: resolution: {integrity: sha512-f8Yvkv3b64O4kPfKMOZu8T39GDP9zJHEdwjwFtnAQZsqFhPjQcfLiH2bNNyY2BQcBES7UyBfqsTy7aJcWijh/A==} dependencies: @@ -3994,35 +4522,6 @@ packages: - typescript dev: true - /@umijs/lint/4.0.40_nc6yd4bwypbhcidfskuo37yccq: - resolution: {integrity: sha512-f8Yvkv3b64O4kPfKMOZu8T39GDP9zJHEdwjwFtnAQZsqFhPjQcfLiH2bNNyY2BQcBES7UyBfqsTy7aJcWijh/A==} - dependencies: - '@babel/core': 7.18.9 - '@babel/eslint-parser': 7.18.9_442gibva36idvxayifa5d4cjjm - '@stylelint/postcss-css-in-js': 0.38.0_2kvscvrvsi2imdv3wm7he2jef4 - '@typescript-eslint/eslint-plugin': 5.36.1_2pbiccfevvtrzq7jtratuel47i - '@typescript-eslint/parser': 5.36.1_eslint@8.25.0 - '@umijs/babel-preset-umi': 4.0.40 - eslint-plugin-jest: 26.1.5_wxut34qkzjlpmrfbzmy3qlkfma - eslint-plugin-react: 7.29.4_eslint@8.25.0 - eslint-plugin-react-hooks: 4.5.0_eslint@8.25.0 - postcss: 8.4.19 - postcss-syntax: 0.36.2_postcss@8.4.19 - stylelint-config-standard: 25.0.0_stylelint@14.16.0 - transitivePeerDependencies: - - eslint - - jest - - postcss-html - - postcss-jsx - - postcss-less - - postcss-markdown - - postcss-scss - - styled-components - - stylelint - - supports-color - - typescript - dev: true - /@umijs/mfsu/4.0.25: resolution: {integrity: sha512-VvOZLugRqINrbHuJi8kjrI7vtUXCm61e+CroPULr7TOEfve+6gFCSBTn0Bm84bXl+Jpe2tlgz86J+6AH8AqF7g==} dependencies: @@ -4407,14 +4906,6 @@ packages: resolution: {integrity: sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==} dev: true - /JSONStream/1.3.5: - resolution: {integrity: sha512-E+iruNOY8VV9s4JEbe1aNEm6MiszPRr/UfcHMz0TQh1BXSxHK+ASV1R6W4HpjBhSeS+54PIsAMCBmwD06LLsqQ==} - hasBin: true - dependencies: - jsonparse: 1.3.1 - through: 2.3.8 - dev: true - /acorn-import-assertions/1.8.0_acorn@8.8.0: resolution: {integrity: sha512-m7VZ3jwz4eK6A4Vtt8Ew1/mNbP24u0FhdyfA7fSvnJR6LMdfOYnmuIrrJAgrYfYJ10F/otaHTtrtrtmHdMNzEw==} peerDependencies: @@ -4446,7 +4937,6 @@ packages: /acorn-walk/8.2.0: resolution: {integrity: sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA==} engines: {node: '>=0.4.0'} - dev: true /acorn/6.4.2: resolution: {integrity: sha512-XtGIhXwF8YM8bJhGxG5kXgjkEuNGLTkoYqVE+KMR+aspr4KGYmKYg7yUe3KghyQ9yheNwLnjmzh/7+gfDBmHCQ==} @@ -4458,7 +4948,6 @@ packages: resolution: {integrity: sha512-QOxyigPVrpZ2GXT+PFyZTl6TtOFc5egxHIP9IlQ+RbupQuX4RkT/Bee4/kQuC02Xkzg84JcT7oLYtDIQxp+v7w==} engines: {node: '>=0.4.0'} hasBin: true - dev: true /add-dom-event-listener/1.1.0: resolution: {integrity: sha512-WCxx1ixHT0GQU9hb0KI/mhgRQhnU+U3GvwY6ZvVjYq8rsihIGoaIOUbY0yMPBxLH5MDtr0kz3fisWGNcbWW7Jw==} @@ -4550,6 +5039,7 @@ packages: require-from-string: 2.0.2 uri-js: 4.4.1 dev: true + optional: true /animated-scroll-to/2.3.0: resolution: {integrity: sha512-PT/5MSKCWQaK2kuOl2HT2KJMuJEvUS4/TgMhWy82c2EmF74/CIkvPBPKOvd8nMYP6Higo7xCn49/iSW9BccMoQ==} @@ -4612,6 +5102,11 @@ packages: color-convert: 2.0.1 dev: true + /ansi-styles/5.2.0: + resolution: {integrity: sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==} + engines: {node: '>=10'} + dev: true + /ansi-styles/6.2.1: resolution: {integrity: sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==} engines: {node: '>=12'} @@ -4691,6 +5186,7 @@ packages: /arg/4.1.3: resolution: {integrity: sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==} dev: true + optional: true /arg/5.0.2: resolution: {integrity: sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==} @@ -4719,10 +5215,6 @@ packages: tslib: 2.4.0 dev: true - /array-ify/1.0.0: - resolution: {integrity: sha512-c5AMf34bKdvPhQ7tBGhqkgKNUzMr4WUs+WDtC2ZUGOUncbxKMTvqxYctiseW3+L4bA8ec+GcZ6/A/FW4m8ukng==} - dev: true - /array-includes/3.1.5: resolution: {integrity: sha512-iSDYZMMyTPkiFasVqfuAQnWAYcvO/SeBSCGKePoEthjp4LEMTe4uLc7b025o4jAZpHhihh8xPo99TNWUWWkGDQ==} engines: {node: '>= 0.4'} @@ -4763,11 +5255,6 @@ packages: es-shim-unscopables: 1.0.0 dev: true - /arrify/1.0.1: - resolution: {integrity: sha512-3CYzex9M9FGQjCGMGyi6/31c8GJbgb0qGyrx5HWxPd0aCwh4cB2YjMb2Xf9UuoogrMrlO9cTqnB5rI5GHZTcUA==} - engines: {node: '>=0.10.0'} - dev: true - /asap/2.0.6: resolution: {integrity: sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA==} dev: false @@ -4796,7 +5283,6 @@ packages: /astring/1.8.3: resolution: {integrity: sha512-sRpyiNrx2dEYIMmUXprS8nlpRg2Drs8m9ElX9vVEXaCB4XEAJhKfs7IcX0IwShjuOAjLR6wzIrgoptz1n19i1A==} hasBin: true - dev: true /async-validator/4.2.5: resolution: {integrity: sha512-7HhHjtERjqlNbZtqNqy2rckN/SpOOlmDliet+lP7k+eKZEjPk3DgyeU9lIXLdeLz0uBbbVp+9Qdow9wJWgwwfg==} @@ -4869,19 +5355,37 @@ packages: form-data: 4.0.0 proxy-from-env: 1.1.0 transitivePeerDependencies: - - debug - dev: false + - debug + dev: false + + /babel-jest/28.1.3: + resolution: {integrity: sha512-epUaPOEWMk3cWX0M/sPvCHHCe9fMFAa/9hXEgKP8nFfNl/jlGkE9ucq9NqkZGXLDduCJYS0UvSlPUwC0S+rH6Q==} + engines: {node: ^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0} + peerDependencies: + '@babel/core': ^7.8.0 + dependencies: + '@jest/transform': 28.1.3 + '@types/babel__core': 7.1.19 + babel-plugin-istanbul: 6.1.1 + babel-preset-jest: 28.1.3 + chalk: 4.1.2 + graceful-fs: 4.2.10 + slash: 3.0.0 + transitivePeerDependencies: + - supports-color + dev: true - /babel-jest/28.1.3: - resolution: {integrity: sha512-epUaPOEWMk3cWX0M/sPvCHHCe9fMFAa/9hXEgKP8nFfNl/jlGkE9ucq9NqkZGXLDduCJYS0UvSlPUwC0S+rH6Q==} - engines: {node: ^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0} + /babel-jest/29.3.1_@babel+core@7.19.3: + resolution: {integrity: sha512-aard+xnMoxgjwV70t0L6wkW/3HQQtV+O0PEimxKgzNqCJnbYmroPojdP2tqKSOAt8QAKV/uSZU8851M7B5+fcA==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} peerDependencies: '@babel/core': ^7.8.0 dependencies: - '@jest/transform': 28.1.3 + '@babel/core': 7.19.3 + '@jest/transform': 29.3.1 '@types/babel__core': 7.1.19 babel-plugin-istanbul: 6.1.1 - babel-preset-jest: 28.1.3 + babel-preset-jest: 29.2.0_@babel+core@7.19.3 chalk: 4.1.2 graceful-fs: 4.2.10 slash: 3.0.0 @@ -4899,7 +5403,7 @@ packages: resolution: {integrity: sha512-Y1IQok9821cC9onCx5otgFfRm7Lm+I+wwxOx738M/WLPZ9Q42m4IG5W0FNX8WLL2gYMZo3JkuXIH2DOpWM+qwA==} engines: {node: '>=8'} dependencies: - '@babel/helper-plugin-utils': 7.19.0 + '@babel/helper-plugin-utils': 7.20.2 '@istanbuljs/load-nyc-config': 1.1.0 '@istanbuljs/schema': 0.1.3 istanbul-lib-instrument: 5.2.1 @@ -4912,8 +5416,18 @@ packages: resolution: {integrity: sha512-Ys3tUKAmfnkRUpPdpa98eYrAR0nV+sSFUZZEGuQ2EbFd1y4SOLtD5QDNHAq+bb9a+bbXvYQC4b+ID/THIMcU6Q==} engines: {node: ^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0} dependencies: - '@babel/template': 7.18.10 - '@babel/types': 7.19.4 + '@babel/template': 7.20.7 + '@babel/types': 7.20.7 + '@types/babel__core': 7.1.19 + '@types/babel__traverse': 7.18.2 + dev: true + + /babel-plugin-jest-hoist/29.2.0: + resolution: {integrity: sha512-TnspP2WNiR3GLfCsUNHqeXw0RoQ2f9U5hQ5L3XFpwuO8htQmSrhh8qsB6vi5Yi8+kuynN1yjDjQsPfkebmB6ZA==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + dependencies: + '@babel/template': 7.20.7 + '@babel/types': 7.20.7 '@types/babel__core': 7.1.19 '@types/babel__traverse': 7.18.2 dev: true @@ -4984,6 +5498,26 @@ packages: '@babel/plugin-syntax-top-level-await': 7.14.5 dev: true + /babel-preset-current-node-syntax/1.0.1_@babel+core@7.19.3: + resolution: {integrity: sha512-M7LQ0bxarkxQoN+vz5aJPsLBn77n8QgTFmo8WK0/44auK2xlCXrYcUxHFxgU7qW5Yzw/CjmLRK2uJzaCd7LvqQ==} + peerDependencies: + '@babel/core': ^7.0.0 + dependencies: + '@babel/core': 7.19.3 + '@babel/plugin-syntax-async-generators': 7.8.4_@babel+core@7.19.3 + '@babel/plugin-syntax-bigint': 7.8.3_@babel+core@7.19.3 + '@babel/plugin-syntax-class-properties': 7.12.13_@babel+core@7.19.3 + '@babel/plugin-syntax-import-meta': 7.10.4_@babel+core@7.19.3 + '@babel/plugin-syntax-json-strings': 7.8.3_@babel+core@7.19.3 + '@babel/plugin-syntax-logical-assignment-operators': 7.10.4_@babel+core@7.19.3 + '@babel/plugin-syntax-nullish-coalescing-operator': 7.8.3_@babel+core@7.19.3 + '@babel/plugin-syntax-numeric-separator': 7.10.4_@babel+core@7.19.3 + '@babel/plugin-syntax-object-rest-spread': 7.8.3_@babel+core@7.19.3 + '@babel/plugin-syntax-optional-catch-binding': 7.8.3_@babel+core@7.19.3 + '@babel/plugin-syntax-optional-chaining': 7.8.3_@babel+core@7.19.3 + '@babel/plugin-syntax-top-level-await': 7.14.5_@babel+core@7.19.3 + dev: true + /babel-preset-jest/28.1.3: resolution: {integrity: sha512-L+fupJvlWAHbQfn74coNX3zf60LXMJsezNvvx8eIh7iOR1luJ1poxYgQk1F8PYtNq/6QODDHCqsSnTFSWC491A==} engines: {node: ^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0} @@ -4994,6 +5528,17 @@ packages: babel-preset-current-node-syntax: 1.0.1 dev: true + /babel-preset-jest/29.2.0_@babel+core@7.19.3: + resolution: {integrity: sha512-z9JmMJppMxNv8N7fNRHvhMg9cvIkMxQBXgFkane3yKVEvEOP+kB50lk8DFRvF9PGqbyXxlmebKWhuDORO8RgdA==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + peerDependencies: + '@babel/core': ^7.0.0 + dependencies: + '@babel/core': 7.19.3 + babel-plugin-jest-hoist: 29.2.0 + babel-preset-current-node-syntax: 1.0.1_@babel+core@7.19.3 + dev: true + /bail/2.0.2: resolution: {integrity: sha512-0xO6mYd7JB2YesxDKplafRpsiOzPt9V02ddPCLbY1xYGPOX24NTyN50qnUxgCPcSoYMhKpAuBTjQoRZCAkUDRw==} dev: true @@ -5001,10 +5546,6 @@ packages: /balanced-match/1.0.2: resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} - /balanced-match/2.0.0: - resolution: {integrity: sha512-1ugUSr8BHXRnK23KfuYS+gVMC3LB8QGH9W1iGtDPsNWoQbgtXSExkBu2aDR4epiGWZOjZsj6lDl/N/AqqTC3UA==} - dev: true - /base16/1.0.0: resolution: {integrity: sha512-pNdYkNPiJUnEhnfXV56+sQy8+AaPcG3POZAUnwr4EeqCUZFz4u2PePbo3e5Gj4ziYPCWGUZT9RHisvJKnwFuBQ==} dev: false @@ -5154,6 +5695,13 @@ packages: update-browserslist-db: 1.0.10_browserslist@4.21.4 dev: true + /bs-logger/0.2.6: + resolution: {integrity: sha512-pd8DCoxmbgc7hyPKOvxtqNcjYoOsABPQdcCUjGp3d42VR2CX1ORhk2A87oqqu5R1kk+76nsxZupkmyd+MVtCog==} + engines: {node: '>= 6'} + dependencies: + fast-json-stable-stringify: 2.1.0 + dev: true + /bser/2.1.1: resolution: {integrity: sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ==} dependencies: @@ -5275,15 +5823,6 @@ packages: tslib: 2.4.0 dev: true - /camelcase-keys/6.2.2: - resolution: {integrity: sha512-YrwaA0vEKazPBkn0ipTiMpSajYDSe+KjQfrjhcBMxJt/znbvlHd8Pw/Vamaz5EB4Wfhs3SUR3Z9mwRu/P3s3Yg==} - engines: {node: '>=8'} - dependencies: - camelcase: 5.3.1 - map-obj: 4.3.0 - quick-lru: 4.0.1 - dev: true - /camelcase/4.1.0: resolution: {integrity: sha512-FxAv7HpHrXbh3aPo4o2qxHay2lkLY3x5Mw3KeE4KQE8ysVfziWeRZDwcjauvwBSGEC/nXUPzZy8zeh4HokqOnw==} engines: {node: '>=4'} @@ -5328,6 +5867,11 @@ packages: supports-color: 7.2.0 dev: true + /char-regex/1.0.2: + resolution: {integrity: sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw==} + engines: {node: '>=10'} + dev: true + /character-entities-html4/2.1.0: resolution: {integrity: sha512-1v7fgQRj6hnSwFpq1Eu0ynr/CDEw0rXo2B61qXrLNdHZmPKgb7fqS1a2JwF0rISo9q77jDI8VMEHoApn8qDoZA==} dev: true @@ -5388,6 +5932,10 @@ packages: safe-buffer: 5.2.1 dev: true + /cjs-module-lexer/1.2.2: + resolution: {integrity: sha512-cOU9usZw8/dXIXKtwa8pM0OTJQuJkxMN6w30csNRUerHfeQ5R6U3kkU/FtJeIf3M202OHfY2U8ccInBG7/xogA==} + dev: true + /classcat/5.0.4: resolution: {integrity: sha512-sbpkOw6z413p+HDGcBENe498WM9woqWHiJxCq7nvmxe9WmrUmqfAcxpIwAiMtM5Q3AhYkzXcNQHqsWq0mND51g==} dev: false @@ -5489,6 +6037,16 @@ packages: engines: {node: '>=0.8'} dev: true + /clsx/1.2.1: + resolution: {integrity: sha512-EcR6r5a8bj6pu3ycsa/E/cKVGuTgZJZdsyUYHOksG/UHIiKfjxzRxYJpyVBwYaQeOvghal9fcc4PidlgzugAQg==} + engines: {node: '>=6'} + dev: false + + /co/4.6.0: + resolution: {integrity: sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ==} + engines: {iojs: '>= 1.0.0', node: '>= 0.12.0'} + dev: true + /codemirror/5.65.10: resolution: {integrity: sha512-IXAG5wlhbgcTJ6rZZcmi4+sjWIbJqIGfeg3tNa3yX84Jb3T4huS5qzQAo/cUisc1l3bI47WZodpyf7cYcocDKg==} dev: false @@ -5534,6 +6092,10 @@ packages: - supports-color dev: true + /collect-v8-coverage/1.0.1: + resolution: {integrity: sha512-iBPtljfCNcTKNAto0KEtDfZ3qzjJvqE3aTGZsbhjSBlorqpXJlaWWtPO35D+ZImoC3KWejX64o+yPGxhWSTzfg==} + dev: true + /color-convert/1.9.3: resolution: {integrity: sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==} dependencies: @@ -5553,10 +6115,6 @@ packages: resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} dev: true - /colord/2.9.3: - resolution: {integrity: sha512-jeC1axXpnb0/2nn/Y1LPuLdgXBLH7aDcHu4KEKfqw3CUhX7ZpfBSlPKyqXE6btIgEzfWtrX3/tyBCaCvXvMkOw==} - dev: true - /colorette/2.0.19: resolution: {integrity: sha512-3tlv/dIP7FWvj3BsbHrGLJ6l/oKh1O3TcgBqMn+yyCagOxc23fyzDS6HypQbgxWbkpDnf52p1LuR4eWDQ/K9WQ==} dev: true @@ -5624,11 +6182,8 @@ packages: resolution: {integrity: sha512-QE33hToZseCH3jS0qN96O/bSh3kaw/h+Tq7ngyY9eWDUnTlTNUyqfqvCXioLe5Na5jFsL78ra/wuBU4iuEgd4w==} dev: true - /compare-func/2.0.0: - resolution: {integrity: sha512-zHig5N+tPWARooBnb0Zx1MFcdfpyJrfTJ3Y5L+IFvUm8rM74hHz66z0gw0x4tijh5CorKkKUCnW82R2vmpeCRA==} - dependencies: - array-ify: 1.0.0 - dot-prop: 5.3.0 + /commondir/1.0.1: + resolution: {integrity: sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg==} dev: true /compute-scroll-into-view/2.0.2: @@ -5668,43 +6223,17 @@ packages: resolution: {integrity: sha512-xFxOwqIzR/e1k1gLiWEophSCMqXcwVHIH7akf7b/vxcUeGunlj3hvZaaqxwHsTgn+IndtkQJgSztIDWeumWJDQ==} dev: true - /conventional-changelog-angular/5.0.13: - resolution: {integrity: sha512-i/gipMxs7s8L/QeuavPF2hLnJgH6pEZAttySB6aiQLWcX3puWDL3ACVmvBhJGxnAy52Qc15ua26BufY6KpmrVA==} - engines: {node: '>=10'} - dependencies: - compare-func: 2.0.0 - q: 1.5.1 - dev: true - - /conventional-changelog-conventionalcommits/5.0.0: - resolution: {integrity: sha512-lCDbA+ZqVFQGUj7h9QBKoIpLhl8iihkO0nCTyRNzuXtcd7ubODpYB04IFy31JloiJgG0Uovu8ot8oxRzn7Nwtw==} - engines: {node: '>=10'} - dependencies: - compare-func: 2.0.0 - lodash: 4.17.21 - q: 1.5.1 - dev: true - /conventional-commit-types/3.0.0: resolution: {integrity: sha512-SmmCYnOniSsAa9GqWOeLqc179lfr5TRu5b4QFDkbsrJ5TZjPJx85wtOr3zn+1dbeNiXDKGPbZ72IKbPhLXh/Lg==} dev: true - /conventional-commits-parser/3.2.4: - resolution: {integrity: sha512-nK7sAtfi+QXbxHCYfhpZsfRtaitZLIA6889kFIouLvz6repszQDgxBu7wf2WbU+Dco7sAnNCJYERCwt54WPC2Q==} - engines: {node: '>=10'} - hasBin: true - dependencies: - JSONStream: 1.3.5 - is-text-path: 1.0.1 - lodash: 4.17.21 - meow: 8.1.2 - split2: 3.2.2 - through2: 4.0.2 - dev: true - /convert-source-map/1.9.0: resolution: {integrity: sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==} + /convert-source-map/2.0.0: + resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==} + dev: true + /copy-concurrently/1.0.5: resolution: {integrity: sha512-f2domd9fsVDFtaFcbaRZuYXwtdmnzqbADSwhSWYxYB/Q8zsdUUFMXVRwXGDMWmbEzAn1kdRrtI1T/KTFOL4X2A==} dependencies: @@ -5757,6 +6286,7 @@ packages: ts-node: 10.9.1_jcmx33t3olsvcxopqdljsohpme typescript: 4.8.4 dev: true + optional: true /cosmiconfig/7.1.0: resolution: {integrity: sha512-AdmX6xUzdNASswsFtmwSt7Vj8po9IuqXm0UXz7QKPuEUmPB4XyjGfaAr2PSuELMwkRMVH1EpIkX5bTZGRB3eCA==} @@ -5806,6 +6336,7 @@ packages: /create-require/1.1.1: resolution: {integrity: sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==} dev: true + optional: true /cross-fetch/3.1.5: resolution: {integrity: sha512-lvb1SBsI0Z7GDwmuid+mU3kWVBwTVUbe7S0H52yaaAdQOXq2YktTCZdlAcNKFzE6QtRz0snpw9bNiPeOIkkQvw==} @@ -5874,11 +6405,6 @@ packages: postcss-selector-parser: 6.0.11 dev: true - /css-functions-list/3.1.0: - resolution: {integrity: sha512-/9lCvYZaUbBGvYUgYGFJ4dcYiyqdhSjG7IPVluoV8A1ILjkF7ilmhp1OGUz8n+nmBcu0RNrQAzgD8B6FJbrt2w==} - engines: {node: '>=12.22'} - dev: true - /css-has-pseudo/3.0.4: resolution: {integrity: sha512-Vse0xpR1K9MNlp2j5w1pgWIJtm1a8qS0JwS9goFYcImjlHEmywP9VUF05aGBXzGpDJF86QXk4L0ypBmwPhGArw==} engines: {node: ^12 || ^14 || >=16} @@ -6091,11 +6617,6 @@ packages: d3-transition: 3.0.1_d3-selection@3.0.0 dev: false - /dargs/7.0.0: - resolution: {integrity: sha512-2iy1EkLdlBzQGvbweYRFxmFath8+K7+AKB0TlhHWkNuH+TmovaMH/Wp7V7R4u7f4SnX3OgLsU9t1NI9ioDnUpg==} - engines: {node: '>=8'} - dev: true - /datauri/3.0.0: resolution: {integrity: sha512-NeDFuUPV1YCpCn8MUIcDk1QnuyenUHs7f4Q5P0n9FFA0neKFrfEH9esR+YMW95BplbYfdmjbs0Pl/ZGAaM2QHQ==} engines: {node: '>= 8'} @@ -6153,19 +6674,6 @@ packages: ms: 2.1.2 dev: true - /decamelize-keys/1.1.1: - resolution: {integrity: sha512-WiPxgEirIV0/eIOMcnFBA3/IJZAZqKnwAwWyvvdi4lsr1WCN22nhdf/3db3DoZcUjTV2SqfzIwNyp6y2xs3nmg==} - engines: {node: '>=0.10.0'} - dependencies: - decamelize: 1.2.0 - map-obj: 1.0.1 - dev: true - - /decamelize/1.2.0: - resolution: {integrity: sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==} - engines: {node: '>=0.10.0'} - dev: true - /decode-named-character-reference/1.0.2: resolution: {integrity: sha512-O8x12RzrUF8xyVcY0KJowWsmaJxQbmy0/EtnNtHRpsOcT7dFk5W598coHqBVpmWo1oQQfsCqfCmkZN5DJrZVdg==} dependencies: @@ -6180,6 +6688,10 @@ packages: resolution: {integrity: sha512-Q6fKUPqnAHAyhiUgFU7BUzLiv0kd8saH9al7tnu5Q/okj6dnupxyTgFIBjVzJATdfIAm9NAsvXNzjaKa+bxVyA==} dev: true + /deep-diff/1.0.2: + resolution: {integrity: sha512-aWS3UIVH+NPGCD1kki+DCU9Dua032iSsO43LqQpcs4R3+dVv7tX0qBGjiVHJHjplsoUM2XRO/KB92glqc68awg==} + dev: false + /deep-extend/0.6.0: resolution: {integrity: sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==} engines: {node: '>=4.0.0'} @@ -6235,6 +6747,11 @@ packages: engines: {node: '>=0.10.0'} dev: true + /detect-indent/5.0.0: + resolution: {integrity: sha512-rlpvsxUtM0PQvy9iZe640/IWwWYyBsTApREbA1pHOpmOUIl9MkP/U4z7vTtg4Oaojvqhxt7sdufnT0EzGaR31g==} + engines: {node: '>=4'} + dev: true + /detect-indent/6.1.0: resolution: {integrity: sha512-reYkTUJAZb9gUuZ2RvVCNhVHdg62RHnJ7WJl8ftMi4diZ6NWlciOzQN88pUhSELEwflJht4oQDv0F0BMlwaYtA==} engines: {node: '>=8'} @@ -6255,10 +6772,16 @@ packages: resolution: {integrity: sha512-T0NIuQpnTvFDATNuHN5roPwSBG83rFsuO+MXXH9/3N1eFbn4wcPjttvjMLEPWJ0RGUYgQE7cGgS3tNxbqCGM7g==} dev: true + /diff-sequences/29.3.1: + resolution: {integrity: sha512-hlM3QR272NXCi4pq+N4Kok4kOp6EsgOM3ZSpJI7Da3UAs+Ttsi8MRmB6trM/lhyzUxGfOgnpkHtgqm5Q/CTcfQ==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + dev: true + /diff/4.0.2: resolution: {integrity: sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==} engines: {node: '>=0.3.1'} dev: true + optional: true /diff/5.1.0: resolution: {integrity: sha512-D+mk+qE8VC/PAUrlAU34N+VfXev0ghe5ywmpqrawphmVZc1bEfn56uo9qpyGp1p4xpzOHkSW4ztBd6L7Xx4ACw==} @@ -6361,13 +6884,6 @@ packages: is-obj: 1.0.1 dev: true - /dot-prop/5.3.0: - resolution: {integrity: sha512-QM8q3zDe58hqUqjraQOmzZ1LIH9SWQJTlEKCH4kJ2oQvLZk7RbQXvtDM2XEq3fwkV9CCvvH4LA0AV+ogFsBM2Q==} - engines: {node: '>=8'} - dependencies: - is-obj: 2.0.0 - dev: true - /dumi-afx-deps/1.0.0-alpha.6: resolution: {integrity: sha512-PAstGjZTWoSEYOAtcmTUQ7mhN8l48t6rY0vaEmk5Tbjiuq2XBhp+bjXwhxlw7jUilXVamEQo5adHb6M7u13eEg==} dev: true @@ -6467,98 +6983,6 @@ packages: - webpack-plugin-serve dev: true - /dumi/2.0.16_hahnn56zypgjpv523dkkei53ve: - resolution: {integrity: sha512-GpHtv8bVmiOcrEQHuN5rb4TUuOOZESkC4Jzkb2nP+3T3+lbh1248+K+YxwAzeoFiA5gx0DiYWnKQ0+NOmapIsA==} - hasBin: true - peerDependencies: - react: '>=16.8' - react-dom: '>=16.8' - dependencies: - '@ant-design/icons-svg': 4.2.1 - '@makotot/ghostui': 2.0.0_react@18.2.0 - '@stackblitz/sdk': 1.8.1 - '@swc/core': 1.3.22 - '@types/hast': 2.3.4 - '@types/mdast': 3.0.10 - '@umijs/bundler-utils': 4.0.36 - '@umijs/core': 4.0.36 - animated-scroll-to: 2.3.0 - classnames: 2.3.2 - codesandbox: 2.2.3 - deepmerge: 4.2.2 - dumi-afx-deps: 1.0.0-alpha.6 - dumi-assets-types: 2.0.0-alpha.0 - enhanced-resolve: 5.12.0 - estree-util-to-js: 1.1.0 - estree-util-visit: 1.2.0 - file-system-cache: 2.0.0 - github-slugger: 1.4.0 - hast-util-is-element: 2.1.2 - hast-util-raw: 7.2.3 - hast-util-to-estree: 2.1.0 - hast-util-to-string: 2.0.0 - heti: 0.9.2 - html-to-text: 8.2.1 - js-yaml: 4.1.0 - lodash.throttle: 4.1.1 - mdast-util-to-string: 3.1.0 - pluralize: 8.0.0 - prism-react-renderer: 1.3.5_react@18.2.0 - prism-themes: 1.9.0 - prismjs: 1.29.0 - raw-loader: 4.0.2 - rc-tabs: 12.1.0-alpha.1_biqbaboplfbrettd7655fr4n2y - react: 18.2.0 - react-copy-to-clipboard: 5.1.0_react@18.2.0 - react-dom: 18.2.0_react@18.2.0 - react-error-boundary: 3.1.4_react@18.2.0 - react-intl: 6.2.5_react@18.2.0 - rehype-autolink-headings: 6.1.1 - rehype-remove-comments: 5.0.0 - rehype-stringify: 9.0.3 - remark-breaks: 3.0.2 - remark-directive: 2.0.1 - remark-frontmatter: 4.0.1 - remark-gfm: 3.0.1 - remark-parse: 10.0.1 - remark-rehype: 10.1.0 - sass: 1.55.0 - sitemap: 7.1.1 - umi: 4.0.36_hahnn56zypgjpv523dkkei53ve - unified: 10.1.2 - unist-util-visit: 4.1.1 - unist-util-visit-parents: 5.1.1 - url: 0.11.0 - v8-compile-cache: 2.3.0 - vfile: 5.3.6 - transitivePeerDependencies: - - '@babel/core' - - '@types/react' - - '@types/webpack' - - encoding - - eslint - - jest - - postcss - - postcss-html - - postcss-jsx - - postcss-less - - postcss-markdown - - postcss-scss - - prettier - - rollup - - sockjs-client - - styled-components - - stylelint - - supports-color - - type-fest - - typescript - - vue-template-compiler - - webpack - - webpack-dev-server - - webpack-hot-middleware - - webpack-plugin-serve - dev: true - /dumi/2.0.16_sjkfibpvkg33iagsiij4zgoske: resolution: {integrity: sha512-GpHtv8bVmiOcrEQHuN5rb4TUuOOZESkC4Jzkb2nP+3T3+lbh1248+K+YxwAzeoFiA5gx0DiYWnKQ0+NOmapIsA==} hasBin: true @@ -6701,6 +7125,11 @@ packages: minimalistic-crypto-utils: 1.0.1 dev: true + /emittery/0.13.1: + resolution: {integrity: sha512-DeWwawk6r5yR9jFgnDKYt4sLS0LmHJJi3ZOnb5/JdbYwj3nW+FxQnHIjhBKz8YLC7oRNPVM9NQ47I3CVx34eqQ==} + engines: {node: '>=12'} + dev: true + /emoji-regex/8.0.0: resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==} dev: true @@ -7501,6 +7930,11 @@ packages: resolution: {integrity: sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==} engines: {node: '>=0.8.0'} + /escape-string-regexp/2.0.0: + resolution: {integrity: sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==} + engines: {node: '>=8'} + dev: true + /escape-string-regexp/4.0.0: resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==} engines: {node: '>=10'} @@ -7652,27 +8086,6 @@ packages: - typescript dev: true - /eslint-plugin-jest/26.1.5_wxut34qkzjlpmrfbzmy3qlkfma: - resolution: {integrity: sha512-su89aDuljL9bTjEufTXmKUMSFe2kZUL9bi7+woq+C2ukHZordhtfPm4Vg+tdioHBaKf8v3/FXW9uV0ksqhYGFw==} - engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} - peerDependencies: - '@typescript-eslint/eslint-plugin': ^5.0.0 - eslint: ^6.0.0 || ^7.0.0 || ^8.0.0 - jest: '*' - peerDependenciesMeta: - '@typescript-eslint/eslint-plugin': - optional: true - jest: - optional: true - dependencies: - '@typescript-eslint/eslint-plugin': 5.36.1_2pbiccfevvtrzq7jtratuel47i - '@typescript-eslint/utils': 5.40.0_eslint@8.25.0 - eslint: 8.25.0 - transitivePeerDependencies: - - supports-color - - typescript - dev: true - /eslint-plugin-n/15.3.0_eslint@8.25.0: resolution: {integrity: sha512-IyzPnEWHypCWasDpxeJnim60jhlumbmq0pubL6IOcnk8u2y53s5QfT8JnXy7skjHJ44yWHRb11PLtDHuu1kg/Q==} engines: {node: '>=12.22.0'} @@ -7723,15 +8136,6 @@ packages: eslint: ^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 dev: true - /eslint-plugin-react-hooks/4.5.0_eslint@8.25.0: - resolution: {integrity: sha512-8k1gRt7D7h03kd+SAAlzXkQwWK22BnK6GKZG+FJA6BAGy22CFvl8kCIXKpVux0cCxMWDQUPqSok0LKaZ0aOcCw==} - engines: {node: '>=10'} - peerDependencies: - eslint: ^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 - dependencies: - eslint: 8.25.0 - dev: true - /eslint-plugin-react-hooks/4.6.0_eslint@8.25.0: resolution: {integrity: sha512-oFc7Itz9Qxh2x4gNHStv3BqJq54ExXmfC+a1NjAta66IAN87Wu0R/QArgIS9qKzX3dXKPI9H5crl9QchNMY9+g==} engines: {node: '>=10'} @@ -7763,29 +8167,6 @@ packages: string.prototype.matchall: 4.0.7 dev: true - /eslint-plugin-react/7.29.4_eslint@8.25.0: - resolution: {integrity: sha512-CVCXajliVh509PcZYRFyu/BoUEz452+jtQJq2b3Bae4v3xBUWPLCmtmBM+ZinG4MzwmxJgJ2M5rMqhqLVn7MtQ==} - engines: {node: '>=4'} - peerDependencies: - eslint: ^3 || ^4 || ^5 || ^6 || ^7 || ^8 - dependencies: - array-includes: 3.1.5 - array.prototype.flatmap: 1.3.0 - doctrine: 2.1.0 - eslint: 8.25.0 - estraverse: 5.3.0 - jsx-ast-utils: 3.3.3 - minimatch: 3.1.2 - object.entries: 1.1.5 - object.fromentries: 2.0.5 - object.hasown: 1.1.1 - object.values: 1.1.5 - prop-types: 15.8.1 - resolve: 2.0.0-next.4 - semver: 6.3.0 - string.prototype.matchall: 4.0.7 - dev: true - /eslint-scope/5.1.1: resolution: {integrity: sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==} engines: {node: '>=8.0.0'} @@ -7954,6 +8335,10 @@ packages: '@types/unist': 2.0.6 dev: true + /estree-walker/1.0.1: + resolution: {integrity: sha512-1fMXF3YP4pZZVozF8j/ZLfvnR8NSIljt56UhbZ5PeeDmmGHpgpdwQt7ITlGvYaQukCvuBRMLEiKiYC+oeIg4cg==} + dev: true + /estree-walker/2.0.2: resolution: {integrity: sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==} dev: true @@ -8018,6 +8403,11 @@ packages: strip-final-newline: 3.0.0 dev: true + /exit/0.1.2: + resolution: {integrity: sha512-Zk/eNKV2zbjpKzrsQ+n1G6poVbErQxJ0LBOJXaKZ1EViLzH+hrLu9cdXI4zw9dBQJslwBEpbQ2P1oS7nDxs6jQ==} + engines: {node: '>= 0.8.0'} + dev: true + /expand-tilde/1.2.2: resolution: {integrity: sha512-rtmc+cjLZqnu9dSYosX9EWmSJhTwpACgJQTfj4hgg2JjOD/6SIQalZrt4a3aQeh++oNxkazcaxrhPUj6+g5G/Q==} engines: {node: '>=0.10.0'} @@ -8032,6 +8422,17 @@ packages: homedir-polyfill: 1.0.3 dev: true + /expect/29.3.1: + resolution: {integrity: sha512-gGb1yTgU30Q0O/tQq+z30KBWv24ApkMgFUpvKBkyLUBL68Wv8dHdJxTBZFl/iT8K/bqDHvUYRH6IIN3rToopPA==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + dependencies: + '@jest/expect-utils': 29.3.1 + jest-get-type: 29.2.0 + jest-matcher-utils: 29.3.1 + jest-message-util: 29.3.1 + jest-util: 29.3.1 + dev: true + /extend-shallow/2.0.1: resolution: {integrity: sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug==} engines: {node: '>=0.10.0'} @@ -8083,10 +8484,12 @@ packages: engines: {node: '>=6'} dev: true - /fastest-levenshtein/1.0.16: - resolution: {integrity: sha512-eRnCtTTtGZFpQCwhJiUOuxPQWRXVKYDn0b2PeHfXL6/Zi53SLAzAHfVhVWK2AryC/WH05kGfxhFIPvTF0SXQzg==} - engines: {node: '>= 4.9.1'} - dev: true + /fast-xml-parser/4.0.12: + resolution: {integrity: sha512-/Nmo3823Rfx7UTJosQNz6hBVbszfv1Unb7A4iNJZhvCGCgtIHv/uODmrYIH8vc05+XKZ4hNIOv6SlBejvJgATw==} + hasBin: true + dependencies: + strnum: 1.0.5 + dev: false /fastq/1.13.0: resolution: {integrity: sha512-YpkpUnK8od0o1hmeSc7UUs/eB/vIPWJYjKck2QKIzAf71Vm1AAQ3EbuZB3g2JIy+pg+ERD0vqI79KyZiB2e2Nw==} @@ -8259,6 +8662,15 @@ packages: path-exists: 3.0.0 dev: true + /find-cache-dir/3.3.2: + resolution: {integrity: sha512-wXZV5emFEjrridIgED11OoUKLxiYjAcqot/NJdAkOhlJ+vGzwhOAfcG5OX1jP+S0PcjEn8bdMJv+g2jwQ3Onig==} + engines: {node: '>=8'} + dependencies: + commondir: 1.0.1 + make-dir: 3.1.0 + pkg-dir: 4.2.0 + dev: true + /find-file-up/0.1.3: resolution: {integrity: sha512-mBxmNbVyjg1LQIIpgO8hN+ybWBgDQK8qjht+EbrTCGmmPV/sc7RF1i9stPTD6bpvXZywBdrwRYxhSdJv867L6A==} engines: {node: '>=0.10.0'} @@ -8564,7 +8976,6 @@ packages: engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} os: [darwin] requiresBuild: true - dev: true optional: true /function-bind/1.1.1: @@ -8655,18 +9066,6 @@ packages: resolution: {integrity: sha512-Y7wLWcrLUXwk2noSka166byGCvhMtDRpgHdzCno1UQv/n/Hegp++a2xBWJL1lJarnKD3SWaljD+0z1ztqxuKyQ==} dev: true - /git-raw-commits/2.0.11: - resolution: {integrity: sha512-VnctFhw+xfj8Va1xtfEqCUD2XDrbAPSJx+hSrE5K7fGdjZruW7XV+QOrN7LF/RJyvspRiD2I0asWsxFp0ya26A==} - engines: {node: '>=10'} - hasBin: true - dependencies: - dargs: 7.0.0 - lodash: 4.17.21 - meow: 8.1.2 - split2: 3.2.2 - through2: 4.0.2 - dev: true - /git-repo-name/0.6.0: resolution: {integrity: sha512-DF4XxB6H+Te79JA08/QF/IjIv+j+0gF990WlgAX3SXXU2irfqvBc/xxlAIh6eJWYaKz45MrrGVBFS0Qc4bBz5g==} engines: {node: '>=0.8'} @@ -8740,13 +9139,6 @@ packages: resolve-dir: 1.0.1 dev: true - /global-modules/2.0.0: - resolution: {integrity: sha512-NGbfmJBp9x8IxyJSd1P+otYK8vonoJactOogrVfFRIAEY1ukil8RSKDz2Yo7wh1oihl51l/r6W4epkeKJHqL8A==} - engines: {node: '>=6'} - dependencies: - global-prefix: 3.0.0 - dev: true - /global-prefix/0.1.5: resolution: {integrity: sha512-gOPiyxcD9dJGCEArAhF4Hd0BAqvAe/JzERP7tYumE4yIkmIedPUVXcJFWbV3/p/ovIIvKjkrTk+f1UVkq7vvbw==} engines: {node: '>=0.10.0'} @@ -8768,15 +9160,6 @@ packages: which: 1.3.1 dev: true - /global-prefix/3.0.0: - resolution: {integrity: sha512-awConJSVCHVGND6x3tmMaKcQvwXLhjdkmomy2W+Goaui8YPgYgXJZewhg3fWC+DlfqqQuWg8AwqjGTD2nAPVWg==} - engines: {node: '>=6'} - dependencies: - ini: 1.3.8 - kind-of: 6.0.3 - which: 1.3.1 - dev: true - /globals/11.12.0: resolution: {integrity: sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==} engines: {node: '>=4'} @@ -8815,10 +9198,6 @@ packages: slash: 3.0.0 dev: true - /globjoin/0.1.4: - resolution: {integrity: sha512-xYfnw62CKG8nLkZBfWbhWwDw02CHty86jfPcc2cr3ZfeuK9ysoVPPEUxf21bAD/rWAgk52SuBrLJlefNy8mvFg==} - dev: true - /got/6.7.1: resolution: {integrity: sha512-Y/K3EDuiQN9rTZhBvPRWMLXIKdeD1Rj0nzunfoi0Yyn5WBEbzxXKU9Ub2X41oZBagVWOBU3MuDonFMgPWQFnwg==} engines: {node: '>=4'} @@ -8853,11 +9232,6 @@ packages: resolution: {integrity: sha512-9Qn4yBxelxoh2Ow62nP+Ka/kMnOXRi8BXnRaUwezLNhqelnN49xKz4F/dPP8OYLxLxq6JDtZb2i9XznUQbNPTg==} dev: true - /hard-rejection/2.1.0: - resolution: {integrity: sha512-VIZB+ibDhx7ObhAe7OVtoEbuP4h/MuOTHJ+J8h/eBXotJYl0fBgR72xDFCKgIh22OJZIOVNxBMWuhAr10r8HdA==} - engines: {node: '>=6'} - dev: true - /harmony-reflect/1.6.2: resolution: {integrity: sha512-HIp/n38R9kQjDEziXyDTuW3vvoxxyxjxFzXLrBr18uB47GnSt+G9D29fqrpM5ZkspMcPICud3XsBJQ4Y2URg8g==} dev: true @@ -9098,13 +9472,6 @@ packages: resolution: {integrity: sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==} dev: true - /hosted-git-info/4.1.0: - resolution: {integrity: sha512-kyCuEOWjJqZuDbRHzL8V93NzQhwIB71oFWSyzVo+KPZI+pnQPPxucdkrOZvkLRnrf5URsQM+IJ09Dw29cRALIA==} - engines: {node: '>=10'} - dependencies: - lru-cache: 6.0.0 - dev: true - /hpack.js/2.1.6: resolution: {integrity: sha512-zJxVehUdMGIKsRaNt7apO2Gqp0BdqW5yaiGHXXmbpvxgBYVZnAql+BJb4RO5ad2MgpbZKn5G6nMnegrH1FcNYQ==} dependencies: @@ -9122,6 +9489,10 @@ packages: resolution: {integrity: sha512-DV5Ln36z34NNTDgnz0EWGBLZENelNAtkiFA4kyNOG2tDI6Mz1uSWiq1wAKdyjnJwyDiDO7Fa2SO1CTxPXL8VxA==} dev: true + /html-escaper/2.0.2: + resolution: {integrity: sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==} + dev: true + /html-minifier-terser/6.1.0: resolution: {integrity: sha512-YXxSlJBZTP7RS3tWnQw74ooKa6L9b9i9QYXY21eUEvhZ3u9XLfv6OnFsQq6RxkhHygsaUMvYsZRV5rU/OVNZxw==} engines: {node: '>=12'} @@ -9136,11 +9507,6 @@ packages: terser: 5.15.1 dev: true - /html-tags/3.2.0: - resolution: {integrity: sha512-vy7ClnArOZwCnqZgvv+ddgHgJiAFXe3Ge9ML5/mBctVJoUoYPCdxVucOywjDARn6CVoh3dRSFdPHy2sX80L0Wg==} - engines: {node: '>=8'} - dev: true - /html-to-text/8.2.1: resolution: {integrity: sha512-aN/3JvAk8qFsWVeE9InWAWueLXrbkoVZy0TkzaGhoRBC2gCFEeRLDDJN3/ijIGHohy6H+SZzUQWN/hcYtaPK8w==} engines: {node: '>=10.23.2'} @@ -9337,6 +9703,15 @@ packages: engines: {node: '>=8'} dev: true + /import-local/3.1.0: + resolution: {integrity: sha512-ASB07uLtnDs1o6EHjKpX34BKYDSqnFerfTOJL2HvMqF70LnxpjkzDB8J44oT9pu4AMPkQwf8jl6szgvNd2tRIg==} + engines: {node: '>=8'} + hasBin: true + dependencies: + pkg-dir: 4.2.0 + resolve-cwd: 3.0.0 + dev: true + /imurmurhash/0.1.4: resolution: {integrity: sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==} engines: {node: '>=0.8.19'} @@ -9597,6 +9972,11 @@ packages: engines: {node: '>=12'} dev: true + /is-generator-fn/2.1.0: + resolution: {integrity: sha512-cTIB4yPYL/Grw0EaSzASzg6bBy9gqCofvWN8okThAYIxKJZC+udlRAmGbM0XLeniEJSs8uEgHPGuHSe1XsOLSQ==} + engines: {node: '>=6'} + dev: true + /is-generator-function/1.0.10: resolution: {integrity: sha512-jsEjy9l3yiXEQ+PsXdmBwEPcOxaXWLspKdplFUVI9vq1iZgIekeC0L167qeu86czQaxed3q/Uzuw0swL0irL8A==} engines: {node: '>= 0.4'} @@ -9659,11 +10039,6 @@ packages: engines: {node: '>=0.10.0'} dev: true - /is-obj/2.0.0: - resolution: {integrity: sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w==} - engines: {node: '>=8'} - dev: true - /is-path-inside/1.0.1: resolution: {integrity: sha512-qhsCR/Esx4U4hg/9I19OVUAJkGWtjRYHMRgUMZE2TDdj+Ag+kttZanLupfddNyglzz50cUlmWzUaI37GDfNx/g==} engines: {node: '>=0.10.0'} @@ -9686,16 +10061,17 @@ packages: engines: {node: '>=12'} dev: true - /is-plain-object/5.0.0: - resolution: {integrity: sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q==} - engines: {node: '>=0.10.0'} - dev: true - /is-redirect/1.0.0: resolution: {integrity: sha512-cr/SlUEe5zOGmzvj9bUyC4LVvkNVAXu4GytXLNMr1pny+a65MpQ9IJzFHD5vi7FyJgb4qt27+eS3TuQnqB+RQw==} engines: {node: '>=0.10.0'} dev: true + /is-reference/1.2.1: + resolution: {integrity: sha512-U82MsXXiFIrjCK4otLT+o2NA2Cd2g5MLoOVXUZjIOhLurrRxpEXzI8O0KZHr3IjLvlAH1kTPYSuqer5T9ZVBKQ==} + dependencies: + '@types/estree': 1.0.0 + dev: true + /is-regex/1.1.4: resolution: {integrity: sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg==} engines: {node: '>= 0.4'} @@ -9748,13 +10124,6 @@ packages: has-symbols: 1.0.3 dev: true - /is-text-path/1.0.1: - resolution: {integrity: sha512-xFuJpne9oFz5qDaodwmmG08e3CawH/2ZV8Qqza1Ko7Sk8POWbkRdwIoAWVhqvq0XeUzANEhKo2n0IXUGBm7A/w==} - engines: {node: '>=0.10.0'} - dependencies: - text-extensions: 1.9.0 - dev: true - /is-typed-array/1.1.9: resolution: {integrity: sha512-kfrlnTTn8pZkfpJMUgYD7YZ3qzeJgWUn8XfVYBARc4wnmNOmLbmuuaAs3q5fvB0UJOn6yHAKaGTPM7d6ezoD/A==} engines: {node: '>= 0.4'} @@ -9838,6 +10207,10 @@ packages: - encoding dev: true + /isomorphic.js/0.2.5: + resolution: {integrity: sha512-PIeMbHqMt4DnUP3MA/Flc0HElYjMXArsw1qwJZcm9sqR8mq3l8NYizFMty0pWwE/tzIGH3EKK5+jes5mAr85yw==} + dev: false + /istanbul-lib-coverage/3.2.0: resolution: {integrity: sha512-eOeJ5BHCmHYvQK7xt9GkdHuzuCGS1Y6g9Gvnx3Ym33fz/HpLRYxiS0wHNr+m/MBC8B647Xt608vCDEvhl9c6Mw==} engines: {node: '>=8'} @@ -9847,50 +10220,457 @@ packages: resolution: {integrity: sha512-pzqtp31nLv/XFOzXGuvhCb8qhjmTVo5vjVk19XE4CRlSWz0KoeJ3bw9XsA7nOp9YBf4qHjwBxkDzKcME/J29Yg==} engines: {node: '>=8'} dependencies: - '@babel/core': 7.19.3 - '@babel/parser': 7.19.4 - '@istanbuljs/schema': 0.1.3 - istanbul-lib-coverage: 3.2.0 - semver: 6.3.0 + '@babel/core': 7.19.3 + '@babel/parser': 7.19.4 + '@istanbuljs/schema': 0.1.3 + istanbul-lib-coverage: 3.2.0 + semver: 6.3.0 + transitivePeerDependencies: + - supports-color + dev: true + + /istanbul-lib-report/3.0.0: + resolution: {integrity: sha512-wcdi+uAKzfiGT2abPpKZ0hSU1rGQjUQnLvtY5MpQ7QCTahD3VODhcu4wcfY1YtkGaDD5yuydOLINXsfbus9ROw==} + engines: {node: '>=8'} + dependencies: + istanbul-lib-coverage: 3.2.0 + make-dir: 3.1.0 + supports-color: 7.2.0 + dev: true + + /istanbul-lib-source-maps/4.0.1: + resolution: {integrity: sha512-n3s8EwkdFIJCG3BPKBYvskgXGoy88ARzvegkitk60NxRdwltLOTaH7CUiMRXvwYorl0Q712iEjcWB+fK/MrWVw==} + engines: {node: '>=10'} + dependencies: + debug: 4.3.4 + istanbul-lib-coverage: 3.2.0 + source-map: 0.6.1 + transitivePeerDependencies: + - supports-color + dev: true + + /istanbul-reports/3.1.5: + resolution: {integrity: sha512-nUsEMa9pBt/NOHqbcbeJEgqIlY/K7rVWUX6Lql2orY5e9roQOthbR3vtY4zzf2orPELg80fnxxk9zUyPlgwD1w==} + engines: {node: '>=8'} + dependencies: + html-escaper: 2.0.2 + istanbul-lib-report: 3.0.0 + dev: true + + /istextorbinary/2.6.0: + resolution: {integrity: sha512-+XRlFseT8B3L9KyjxxLjfXSLMuErKDsd8DBNrsaxoViABMEZlOSCstwmw0qpoFX3+U6yWU1yhLudAe6/lETGGA==} + engines: {node: '>=0.12'} + dependencies: + binaryextensions: 2.3.0 + editions: 2.3.1 + textextensions: 2.6.0 + dev: true + + /javascript-natural-sort/0.7.1: + resolution: {integrity: sha512-nO6jcEfZWQXDhOiBtG2KvKyEptz7RVbpGP4vTD2hLBdmNQSsCiicO2Ioinv6UI4y9ukqnBpy+XZ9H6uLNgJTlw==} + dev: true + + /jest-changed-files/29.2.0: + resolution: {integrity: sha512-qPVmLLyBmvF5HJrY7krDisx6Voi8DmlV3GZYX0aFNbaQsZeoz1hfxcCMbqDGuQCxU1dJy9eYc2xscE8QrCCYaA==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + dependencies: + execa: 5.1.1 + p-limit: 3.1.0 + dev: true + + /jest-circus/29.3.1: + resolution: {integrity: sha512-wpr26sEvwb3qQQbdlmei+gzp6yoSSoSL6GsLPxnuayZSMrSd5Ka7IjAvatpIernBvT2+Ic6RLTg+jSebScmasg==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + dependencies: + '@jest/environment': 29.3.1 + '@jest/expect': 29.3.1 + '@jest/test-result': 29.3.1 + '@jest/types': 29.3.1 + '@types/node': 17.0.45 + chalk: 4.1.2 + co: 4.6.0 + dedent: 0.7.0 + is-generator-fn: 2.1.0 + jest-each: 29.3.1 + jest-matcher-utils: 29.3.1 + jest-message-util: 29.3.1 + jest-runtime: 29.3.1 + jest-snapshot: 29.3.1 + jest-util: 29.3.1 + p-limit: 3.1.0 + pretty-format: 29.3.1 + slash: 3.0.0 + stack-utils: 2.0.6 + transitivePeerDependencies: + - supports-color + dev: true + + /jest-cli/29.3.1: + resolution: {integrity: sha512-TO/ewvwyvPOiBBuWZ0gm04z3WWP8TIK8acgPzE4IxgsLKQgb377NYGrQLc3Wl/7ndWzIH2CDNNsUjGxwLL43VQ==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + hasBin: true + peerDependencies: + node-notifier: ^8.0.1 || ^9.0.0 || ^10.0.0 + peerDependenciesMeta: + node-notifier: + optional: true + dependencies: + '@jest/core': 29.3.1 + '@jest/test-result': 29.3.1 + '@jest/types': 29.3.1 + chalk: 4.1.2 + exit: 0.1.2 + graceful-fs: 4.2.10 + import-local: 3.1.0 + jest-config: 29.3.1 + jest-util: 29.3.1 + jest-validate: 29.3.1 + prompts: 2.4.2 + yargs: 17.6.0 + transitivePeerDependencies: + - '@types/node' + - supports-color + - ts-node + dev: true + + /jest-config/29.3.1: + resolution: {integrity: sha512-y0tFHdj2WnTEhxmGUK1T7fgLen7YK4RtfvpLFBXfQkh2eMJAQq24Vx9472lvn5wg0MAO6B+iPfJfzdR9hJYalg==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + peerDependencies: + '@types/node': '*' + ts-node: '>=9.0.0' + peerDependenciesMeta: + '@types/node': + optional: true + ts-node: + optional: true + dependencies: + '@babel/core': 7.19.3 + '@jest/test-sequencer': 29.3.1 + '@jest/types': 29.3.1 + babel-jest: 29.3.1_@babel+core@7.19.3 + chalk: 4.1.2 + ci-info: 3.7.0 + deepmerge: 4.2.2 + glob: 7.2.3 + graceful-fs: 4.2.10 + jest-circus: 29.3.1 + jest-environment-node: 29.3.1 + jest-get-type: 29.2.0 + jest-regex-util: 29.2.0 + jest-resolve: 29.3.1 + jest-runner: 29.3.1 + jest-util: 29.3.1 + jest-validate: 29.3.1 + micromatch: 4.0.5 + parse-json: 5.2.0 + pretty-format: 29.3.1 + slash: 3.0.0 + strip-json-comments: 3.1.1 + transitivePeerDependencies: + - supports-color + dev: true + + /jest-config/29.3.1_@types+node@17.0.45: + resolution: {integrity: sha512-y0tFHdj2WnTEhxmGUK1T7fgLen7YK4RtfvpLFBXfQkh2eMJAQq24Vx9472lvn5wg0MAO6B+iPfJfzdR9hJYalg==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + peerDependencies: + '@types/node': '*' + ts-node: '>=9.0.0' + peerDependenciesMeta: + '@types/node': + optional: true + ts-node: + optional: true + dependencies: + '@babel/core': 7.19.3 + '@jest/test-sequencer': 29.3.1 + '@jest/types': 29.3.1 + '@types/node': 17.0.45 + babel-jest: 29.3.1_@babel+core@7.19.3 + chalk: 4.1.2 + ci-info: 3.7.0 + deepmerge: 4.2.2 + glob: 7.2.3 + graceful-fs: 4.2.10 + jest-circus: 29.3.1 + jest-environment-node: 29.3.1 + jest-get-type: 29.2.0 + jest-regex-util: 29.2.0 + jest-resolve: 29.3.1 + jest-runner: 29.3.1 + jest-util: 29.3.1 + jest-validate: 29.3.1 + micromatch: 4.0.5 + parse-json: 5.2.0 + pretty-format: 29.3.1 + slash: 3.0.0 + strip-json-comments: 3.1.1 + transitivePeerDependencies: + - supports-color + dev: true + + /jest-diff/29.3.1: + resolution: {integrity: sha512-vU8vyiO7568tmin2lA3r2DP8oRvzhvRcD4DjpXc6uGveQodyk7CKLhQlCSiwgx3g0pFaE88/KLZ0yaTWMc4Uiw==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + dependencies: + chalk: 4.1.2 + diff-sequences: 29.3.1 + jest-get-type: 29.2.0 + pretty-format: 29.3.1 + dev: true + + /jest-docblock/29.2.0: + resolution: {integrity: sha512-bkxUsxTgWQGbXV5IENmfiIuqZhJcyvF7tU4zJ/7ioTutdz4ToB5Yx6JOFBpgI+TphRY4lhOyCWGNH/QFQh5T6A==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + dependencies: + detect-newline: 3.1.0 + dev: true + + /jest-each/29.3.1: + resolution: {integrity: sha512-qrZH7PmFB9rEzCSl00BWjZYuS1BSOH8lLuC0azQE9lQrAx3PWGKHTDudQiOSwIy5dGAJh7KA0ScYlCP7JxvFYA==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + dependencies: + '@jest/types': 29.3.1 + chalk: 4.1.2 + jest-get-type: 29.2.0 + jest-util: 29.3.1 + pretty-format: 29.3.1 + dev: true + + /jest-environment-node/29.3.1: + resolution: {integrity: sha512-xm2THL18Xf5sIHoU7OThBPtuH6Lerd+Y1NLYiZJlkE3hbE+7N7r8uvHIl/FkZ5ymKXJe/11SQuf3fv4v6rUMag==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + dependencies: + '@jest/environment': 29.3.1 + '@jest/fake-timers': 29.3.1 + '@jest/types': 29.3.1 + '@types/node': 17.0.45 + jest-mock: 29.3.1 + jest-util: 29.3.1 + dev: true + + /jest-get-type/29.2.0: + resolution: {integrity: sha512-uXNJlg8hKFEnDgFsrCjznB+sTxdkuqiCL6zMgA75qEbAJjJYTs9XPrvDctrEig2GDow22T/LvHgO57iJhXB/UA==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + dev: true + + /jest-haste-map/28.1.3: + resolution: {integrity: sha512-3S+RQWDXccXDKSWnkHa/dPwt+2qwA8CJzR61w3FoYCvoo3Pn8tvGcysmMF0Bj0EX5RYvAI2EIvC57OmotfdtKA==} + engines: {node: ^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0} + dependencies: + '@jest/types': 28.1.3 + '@types/graceful-fs': 4.1.5 + '@types/node': 17.0.45 + anymatch: 3.1.2 + fb-watchman: 2.0.2 + graceful-fs: 4.2.10 + jest-regex-util: 28.0.2 + jest-util: 28.1.3 + jest-worker: 28.1.3 + micromatch: 4.0.5 + walker: 1.0.8 + optionalDependencies: + fsevents: 2.3.2 + dev: true + + /jest-haste-map/29.3.1: + resolution: {integrity: sha512-/FFtvoG1xjbbPXQLFef+WSU4yrc0fc0Dds6aRPBojUid7qlPqZvxdUBA03HW0fnVHXVCnCdkuoghYItKNzc/0A==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + dependencies: + '@jest/types': 29.3.1 + '@types/graceful-fs': 4.1.5 + '@types/node': 17.0.45 + anymatch: 3.1.2 + fb-watchman: 2.0.2 + graceful-fs: 4.2.10 + jest-regex-util: 29.2.0 + jest-util: 29.3.1 + jest-worker: 29.3.1 + micromatch: 4.0.5 + walker: 1.0.8 + optionalDependencies: + fsevents: 2.3.2 + dev: true + + /jest-leak-detector/29.3.1: + resolution: {integrity: sha512-3DA/VVXj4zFOPagGkuqHnSQf1GZBmmlagpguxEERO6Pla2g84Q1MaVIB3YMxgUaFIaYag8ZnTyQgiZ35YEqAQA==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + dependencies: + jest-get-type: 29.2.0 + pretty-format: 29.3.1 + dev: true + + /jest-matcher-utils/29.3.1: + resolution: {integrity: sha512-fkRMZUAScup3txIKfMe3AIZZmPEjWEdsPJFK3AIy5qRohWqQFg1qrmKfYXR9qEkNc7OdAu2N4KPHibEmy4HPeQ==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + dependencies: + chalk: 4.1.2 + jest-diff: 29.3.1 + jest-get-type: 29.2.0 + pretty-format: 29.3.1 + dev: true + + /jest-message-util/29.3.1: + resolution: {integrity: sha512-lMJTbgNcDm5z+6KDxWtqOFWlGQxD6XaYwBqHR8kmpkP+WWWG90I35kdtQHY67Ay5CSuydkTBbJG+tH9JShFCyA==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + dependencies: + '@babel/code-frame': 7.18.6 + '@jest/types': 29.3.1 + '@types/stack-utils': 2.0.1 + chalk: 4.1.2 + graceful-fs: 4.2.10 + micromatch: 4.0.5 + pretty-format: 29.3.1 + slash: 3.0.0 + stack-utils: 2.0.6 + dev: true + + /jest-mock/29.3.1: + resolution: {integrity: sha512-H8/qFDtDVMFvFP4X8NuOT3XRDzOUTz+FeACjufHzsOIBAxivLqkB1PoLCaJx9iPPQ8dZThHPp/G3WRWyMgA3JA==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + dependencies: + '@jest/types': 29.3.1 + '@types/node': 17.0.45 + jest-util: 29.3.1 + dev: true + + /jest-pnp-resolver/1.2.3_jest-resolve@29.3.1: + resolution: {integrity: sha512-+3NpwQEnRoIBtx4fyhblQDPgJI0H1IEIkX7ShLUjPGA7TtUTvI1oiKi3SR4oBR0hQhQR80l4WAe5RrXBwWMA8w==} + engines: {node: '>=6'} + peerDependencies: + jest-resolve: '*' + peerDependenciesMeta: + jest-resolve: + optional: true + dependencies: + jest-resolve: 29.3.1 + dev: true + + /jest-regex-util/28.0.2: + resolution: {integrity: sha512-4s0IgyNIy0y9FK+cjoVYoxamT7Zeo7MhzqRGx7YDYmaQn1wucY9rotiGkBzzcMXTtjrCAP/f7f+E0F7+fxPNdw==} + engines: {node: ^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0} + dev: true + + /jest-regex-util/29.2.0: + resolution: {integrity: sha512-6yXn0kg2JXzH30cr2NlThF+70iuO/3irbaB4mh5WyqNIvLLP+B6sFdluO1/1RJmslyh/f9osnefECflHvTbwVA==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + dev: true + + /jest-resolve-dependencies/29.3.1: + resolution: {integrity: sha512-Vk0cYq0byRw2WluNmNWGqPeRnZ3p3hHmjJMp2dyyZeYIfiBskwq4rpiuGFR6QGAdbj58WC7HN4hQHjf2mpvrLA==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + dependencies: + jest-regex-util: 29.2.0 + jest-snapshot: 29.3.1 transitivePeerDependencies: - supports-color dev: true - /istextorbinary/2.6.0: - resolution: {integrity: sha512-+XRlFseT8B3L9KyjxxLjfXSLMuErKDsd8DBNrsaxoViABMEZlOSCstwmw0qpoFX3+U6yWU1yhLudAe6/lETGGA==} - engines: {node: '>=0.12'} + /jest-resolve/29.3.1: + resolution: {integrity: sha512-amXJgH/Ng712w3Uz5gqzFBBjxV8WFLSmNjoreBGMqxgCz5cH7swmBZzgBaCIOsvb0NbpJ0vgaSFdJqMdT+rADw==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} dependencies: - binaryextensions: 2.3.0 - editions: 2.3.1 - textextensions: 2.6.0 + chalk: 4.1.2 + graceful-fs: 4.2.10 + jest-haste-map: 29.3.1 + jest-pnp-resolver: 1.2.3_jest-resolve@29.3.1 + jest-util: 29.3.1 + jest-validate: 29.3.1 + resolve: 1.22.1 + resolve.exports: 1.1.0 + slash: 3.0.0 dev: true - /javascript-natural-sort/0.7.1: - resolution: {integrity: sha512-nO6jcEfZWQXDhOiBtG2KvKyEptz7RVbpGP4vTD2hLBdmNQSsCiicO2Ioinv6UI4y9ukqnBpy+XZ9H6uLNgJTlw==} + /jest-runner/29.3.1: + resolution: {integrity: sha512-oFvcwRNrKMtE6u9+AQPMATxFcTySyKfLhvso7Sdk/rNpbhg4g2GAGCopiInk1OP4q6gz3n6MajW4+fnHWlU3bA==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + dependencies: + '@jest/console': 29.3.1 + '@jest/environment': 29.3.1 + '@jest/test-result': 29.3.1 + '@jest/transform': 29.3.1 + '@jest/types': 29.3.1 + '@types/node': 17.0.45 + chalk: 4.1.2 + emittery: 0.13.1 + graceful-fs: 4.2.10 + jest-docblock: 29.2.0 + jest-environment-node: 29.3.1 + jest-haste-map: 29.3.1 + jest-leak-detector: 29.3.1 + jest-message-util: 29.3.1 + jest-resolve: 29.3.1 + jest-runtime: 29.3.1 + jest-util: 29.3.1 + jest-watcher: 29.3.1 + jest-worker: 29.3.1 + p-limit: 3.1.0 + source-map-support: 0.5.13 + transitivePeerDependencies: + - supports-color dev: true - /jest-haste-map/28.1.3: - resolution: {integrity: sha512-3S+RQWDXccXDKSWnkHa/dPwt+2qwA8CJzR61w3FoYCvoo3Pn8tvGcysmMF0Bj0EX5RYvAI2EIvC57OmotfdtKA==} - engines: {node: ^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0} + /jest-runtime/29.3.1: + resolution: {integrity: sha512-jLzkIxIqXwBEOZx7wx9OO9sxoZmgT2NhmQKzHQm1xwR1kNW/dn0OjxR424VwHHf1SPN6Qwlb5pp1oGCeFTQ62A==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} dependencies: - '@jest/types': 28.1.3 - '@types/graceful-fs': 4.1.5 + '@jest/environment': 29.3.1 + '@jest/fake-timers': 29.3.1 + '@jest/globals': 29.3.1 + '@jest/source-map': 29.2.0 + '@jest/test-result': 29.3.1 + '@jest/transform': 29.3.1 + '@jest/types': 29.3.1 '@types/node': 17.0.45 - anymatch: 3.1.2 - fb-watchman: 2.0.2 + chalk: 4.1.2 + cjs-module-lexer: 1.2.2 + collect-v8-coverage: 1.0.1 + glob: 7.2.3 graceful-fs: 4.2.10 - jest-regex-util: 28.0.2 - jest-util: 28.1.3 - jest-worker: 28.1.3 - micromatch: 4.0.5 - walker: 1.0.8 - optionalDependencies: - fsevents: 2.3.2 + jest-haste-map: 29.3.1 + jest-message-util: 29.3.1 + jest-mock: 29.3.1 + jest-regex-util: 29.2.0 + jest-resolve: 29.3.1 + jest-snapshot: 29.3.1 + jest-util: 29.3.1 + slash: 3.0.0 + strip-bom: 4.0.0 + transitivePeerDependencies: + - supports-color dev: true - /jest-regex-util/28.0.2: - resolution: {integrity: sha512-4s0IgyNIy0y9FK+cjoVYoxamT7Zeo7MhzqRGx7YDYmaQn1wucY9rotiGkBzzcMXTtjrCAP/f7f+E0F7+fxPNdw==} - engines: {node: ^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0} + /jest-snapshot/29.3.1: + resolution: {integrity: sha512-+3JOc+s28upYLI2OJM4PWRGK9AgpsMs/ekNryUV0yMBClT9B1DF2u2qay8YxcQd338PPYSFNb0lsar1B49sLDA==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + dependencies: + '@babel/core': 7.19.3 + '@babel/generator': 7.19.5 + '@babel/plugin-syntax-jsx': 7.18.6_@babel+core@7.19.3 + '@babel/plugin-syntax-typescript': 7.20.0_@babel+core@7.19.3 + '@babel/traverse': 7.19.4 + '@babel/types': 7.19.4 + '@jest/expect-utils': 29.3.1 + '@jest/transform': 29.3.1 + '@jest/types': 29.3.1 + '@types/babel__traverse': 7.18.2 + '@types/prettier': 2.7.2 + babel-preset-current-node-syntax: 1.0.1_@babel+core@7.19.3 + chalk: 4.1.2 + expect: 29.3.1 + graceful-fs: 4.2.10 + jest-diff: 29.3.1 + jest-get-type: 29.2.0 + jest-haste-map: 29.3.1 + jest-matcher-utils: 29.3.1 + jest-message-util: 29.3.1 + jest-util: 29.3.1 + natural-compare: 1.4.0 + pretty-format: 29.3.1 + semver: 7.3.8 + transitivePeerDependencies: + - supports-color dev: true /jest-util/28.1.3: @@ -9905,6 +10685,44 @@ packages: picomatch: 2.3.1 dev: true + /jest-util/29.3.1: + resolution: {integrity: sha512-7YOVZaiX7RJLv76ZfHt4nbNEzzTRiMW/IiOG7ZOKmTXmoGBxUDefgMAxQubu6WPVqP5zSzAdZG0FfLcC7HOIFQ==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + dependencies: + '@jest/types': 29.3.1 + '@types/node': 17.0.45 + chalk: 4.1.2 + ci-info: 3.7.0 + graceful-fs: 4.2.10 + picomatch: 2.3.1 + dev: true + + /jest-validate/29.3.1: + resolution: {integrity: sha512-N9Lr3oYR2Mpzuelp1F8negJR3YE+L1ebk1rYA5qYo9TTY3f9OWdptLoNSPP9itOCBIRBqjt/S5XHlzYglLN67g==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + dependencies: + '@jest/types': 29.3.1 + camelcase: 6.3.0 + chalk: 4.1.2 + jest-get-type: 29.2.0 + leven: 3.1.0 + pretty-format: 29.3.1 + dev: true + + /jest-watcher/29.3.1: + resolution: {integrity: sha512-RspXG2BQFDsZSRKGCT/NiNa8RkQ1iKAjrO0//soTMWx/QUt+OcxMqMSBxz23PYGqUuWm2+m2mNNsmj0eIoOaFg==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + dependencies: + '@jest/test-result': 29.3.1 + '@jest/types': 29.3.1 + '@types/node': 17.0.45 + ansi-escapes: 4.3.2 + chalk: 4.1.2 + emittery: 0.13.1 + jest-util: 29.3.1 + string-length: 4.0.2 + dev: true + /jest-worker/27.5.1: resolution: {integrity: sha512-7vuh85V5cdDofPyxn58nrPjBktZo0u9x1g8WtjQol+jZDaE+fhN+cIvTj11GndBnMnyfrUOG1sZQxCdjKh+DKg==} engines: {node: '>= 10.13.0'} @@ -9923,6 +10741,36 @@ packages: supports-color: 8.1.1 dev: true + /jest-worker/29.3.1: + resolution: {integrity: sha512-lY4AnnmsEWeiXirAIA0c9SDPbuCBq8IYuDVL8PMm0MZ2PEs2yPvRA/J64QBXuZp7CYKrDM/rmNrc9/i3KJQncw==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + dependencies: + '@types/node': 17.0.45 + jest-util: 29.3.1 + merge-stream: 2.0.0 + supports-color: 8.1.1 + dev: true + + /jest/29.0.3: + resolution: {integrity: sha512-ElgUtJBLgXM1E8L6K1RW1T96R897YY/3lRYqq9uVcPWtP2AAl/nQ16IYDh/FzQOOQ12VEuLdcPU83mbhG2C3PQ==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + hasBin: true + peerDependencies: + node-notifier: ^8.0.1 || ^9.0.0 || ^10.0.0 + peerDependenciesMeta: + node-notifier: + optional: true + dependencies: + '@jest/core': 29.3.1 + '@jest/types': 29.3.1 + import-local: 3.1.0 + jest-cli: 29.3.1 + transitivePeerDependencies: + - '@types/node' + - supports-color + - ts-node + dev: true + /jju/1.4.0: resolution: {integrity: sha512-8wb9Yw966OSxApiCt0K3yNJL8pnNeIv+OEq2YMidz4FKP6nonSRoOXc80iXY4JaN2FC11B9qsNmDsm+ZOfMROA==} dev: true @@ -9979,6 +10827,7 @@ packages: /json-schema-traverse/1.0.0: resolution: {integrity: sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==} dev: true + optional: true /json-stable-stringify-without-jsonify/1.0.1: resolution: {integrity: sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==} @@ -10028,11 +10877,6 @@ packages: graceful-fs: 4.2.10 dev: true - /jsonparse/1.3.1: - resolution: {integrity: sha512-POQXvpdL69+CluYsillJ7SUhKvytYjW9vG/GKpnf+xP8UWgYEM/RaMzHHofbALDiKbbP1W8UEYmgGl39WkPZsg==} - engines: {'0': node >= 0.2.0} - dev: true - /jsx-ast-utils/3.3.3: resolution: {integrity: sha512-fYQHZTZ8jSfmWZ0iyzfwiU4WDX4HpHbMCZ3gPlWYiCl3BoeOTsqKBqnTVfH2rYT7eP5c3sVbeSPHnnJOaTrWiw==} engines: {node: '>=4.0'} @@ -10041,9 +10885,9 @@ packages: object.assign: 4.1.4 dev: true - /kind-of/6.0.3: - resolution: {integrity: sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==} - engines: {node: '>=0.10.0'} + /kleur/3.0.3: + resolution: {integrity: sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==} + engines: {node: '>=6'} dev: true /kleur/4.1.5: @@ -10051,9 +10895,10 @@ packages: engines: {node: '>=6'} dev: true - /known-css-properties/0.26.0: - resolution: {integrity: sha512-5FZRzrZzNTBruuurWpvZnvP9pum+fe0HcK8z/ooo+U+Hmp4vtbyp1/QDsqmufirXy4egGzbaH/y2uCZf+6W5Kg==} - dev: true + /klona/2.0.5: + resolution: {integrity: sha512-pJiBpiXMbt7dkzXe8Ghj/u4FfXOOa98fPW+bihOJ4SjnoijweJrNThJfd3ifXpXhREjpoF2mZVH1GfS9LV3kHQ==} + engines: {node: '>= 8'} + dev: false /latest-version/3.1.0: resolution: {integrity: sha512-Be1YRHWWlZaSsrz2U+VInk+tO0EwLIyV+23RhWLINJYwg/UIikxjlj3MhH37/6/EDCAusjajvMkMMUXRaMWl/w==} @@ -10067,6 +10912,11 @@ packages: engines: {node: '>=0.10.0'} dev: true + /leven/3.1.0: + resolution: {integrity: sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==} + engines: {node: '>=6'} + dev: true + /levn/0.4.1: resolution: {integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==} engines: {node: '>= 0.8.0'} @@ -10075,6 +10925,13 @@ packages: type-check: 0.4.0 dev: true + /lib0/0.2.58: + resolution: {integrity: sha512-6ovqPaYfOKU7GkkVxz/wjMR0zsqmNsISLvH+h9Lx5YNtWDZey69aYsTGXaSVpUPpJ+ZFtIvcZHsTGL3MbwOM8A==} + engines: {node: '>=14'} + dependencies: + isomorphic.js: 0.2.5 + dev: false + /lilconfig/2.0.6: resolution: {integrity: sha512-9JROoBW7pobfsx+Sq2JsASvCo6Pfo6WWoUW79HuB1BCoBXD4PLWJPqDF6fNj67pqBYTbAHkE57M1kS/+L1neOg==} engines: {node: '>=10'} @@ -10161,10 +11018,6 @@ packages: p-locate: 5.0.0 dev: true - /lodash.camelcase/4.3.0: - resolution: {integrity: sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA==} - dev: true - /lodash.curry/4.1.1: resolution: {integrity: sha512-/u14pXGviLaweY5JI0IUzgzF2J6Ne8INyzAZjImcryjgkZ+ebruBxy2/JaOOkTqScddcYtakjhSaeemV8lR0tA==} dev: false @@ -10180,36 +11033,26 @@ packages: /lodash.isequal/4.5.0: resolution: {integrity: sha512-pDo3lu8Jhfjqls6GkMgpahsF9kCyayhgykjyLMNFTKWrpVdAQtYyB4muAMWozBB4ig/dtWAmsMxLEI8wuz+DYQ==} - /lodash.isfunction/3.0.9: - resolution: {integrity: sha512-AirXNj15uRIMMPihnkInB4i3NHeb4iBtNg9WRWuK2o31S+ePwwNmDPaTL3o7dTJ+VXNZim7rFs4rxN4YU1oUJw==} - dev: true - /lodash.isplainobject/4.0.6: resolution: {integrity: sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==} dev: true - - /lodash.kebabcase/4.1.1: - resolution: {integrity: sha512-N8XRTIMMqqDgSy4VLKPnJ/+hpGZN+PHQiJnSenYqPaVV/NCqEogTnAdZLQiGKhxX+JCs8waWq2t1XHWKOmlY8g==} - dev: true + optional: true /lodash.map/4.6.0: resolution: {integrity: sha512-worNHGKLDetmcEYDvh2stPCrrQRkP20E4l0iIS7F8EvzMqBBi7ltvFN5m1HvTf1P7Jk1txKhvFcmYsCr8O2F1Q==} dev: true + /lodash.memoize/4.1.2: + resolution: {integrity: sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag==} + dev: true + /lodash.merge/4.6.2: resolution: {integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==} /lodash.mergewith/4.6.2: resolution: {integrity: sha512-GK3g5RPZWTRSeLSpgP8Xhra+pnjBC56q9FZYe1d5RN3TJ35dbkGy3YqBSMbyCrlbi+CM9Z3Jk5yTL7RCsqboyQ==} dev: true - - /lodash.snakecase/4.1.1: - resolution: {integrity: sha512-QZ1d4xoBHYUeuouhEq3lk3Uq7ldgyFXGBhg04+oRLnIz8o9T65Eh+8YdroUwn846zchkA9yDsDl5CVVaV2nqYw==} - dev: true - - /lodash.startcase/4.4.0: - resolution: {integrity: sha512-+WKqsK294HMSc2jEbNgpHpd0JfIBhp7rEV4aqXWqFr6AlXov+SlcgB1Fv01y2kGe3Gc8nMW7VA0SrGuSkRfIEg==} - dev: true + optional: true /lodash.throttle/4.1.1: resolution: {integrity: sha512-wIkUCfVKpVsWo3JSZlc+8MB5it+2AN5W8J7YVMST30UrvcQNZ1Okbj+rbVniijTWE6FGYy4XJq/rHkas8qJMLQ==} @@ -10219,17 +11062,10 @@ packages: resolution: {integrity: sha512-SY0SwuPOHRwKcCNTdsntPYb+Zddz5mDUIVFABzRMqmAiL41pMeyoQFGxYAw5zdc9NnH4pbJqiqqp5ckfxa+zSA==} dev: false - /lodash.truncate/4.4.2: - resolution: {integrity: sha512-jttmRe7bRse52OsWIMDLaXxWqRAmtIUccAQ3garviCqJjafXOfNMO0yMfNpdD6zbGaTU0P5Nz7e7gAT6cKmJRw==} - dev: true - /lodash.uniq/4.5.0: resolution: {integrity: sha512-xfBaXQd9ryd9dlSDvnvI0lvxfLJlYAZzXomUYzLKtUeOQvOP5piqAWuGtrhWeqaXK9hhoM/iyJc5AV+XfsX3HQ==} dev: true - - /lodash.upperfirst/4.3.1: - resolution: {integrity: sha512-sReKOYJIJf74dhJONhU4e0/shzi1trVbSWDOhKYE5XV2O+H7Sb2Dihwuc7xWxVl+DgFPyTqIN3zMfT9cq5iWDg==} - dev: true + optional: true /lodash/4.17.21: resolution: {integrity: sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==} @@ -10309,6 +11145,12 @@ packages: hasBin: true dev: true + /magic-string/0.25.9: + resolution: {integrity: sha512-RmF0AsMzgt25qzqqLc1+MbHmhdx0ojF2Fvs4XnOqz2ZOBXzzkEwc/dJQZCYHAn7v1jbVOjAZfK8msRn4BxO4VQ==} + dependencies: + sourcemap-codec: 1.4.8 + dev: true + /magic-string/0.26.2: resolution: {integrity: sha512-NzzlXpclt5zAbmo6h6jNc8zl2gNRGHvmsZW4IvZhTC4W7k4OlLP+S5YLussa/r3ixNT66KOQfNORlXHSOy/X4A==} engines: {node: '>=12'} @@ -10330,6 +11172,21 @@ packages: pify: 3.0.0 dev: true + /make-dir/2.1.0: + resolution: {integrity: sha512-LS9X+dc8KLxXCb8dni79fLIIUA5VyZoyjSMCwTluaXA0o27cCK0bhXkpgw+sTXVpPy/lSO57ilRixqk0vDmtRA==} + engines: {node: '>=6'} + dependencies: + pify: 4.0.1 + semver: 5.7.1 + dev: true + + /make-dir/3.1.0: + resolution: {integrity: sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==} + engines: {node: '>=8'} + dependencies: + semver: 6.3.0 + dev: true + /make-error/1.3.6: resolution: {integrity: sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==} dev: true @@ -10358,24 +11215,10 @@ packages: tmpl: 1.0.5 dev: true - /map-obj/1.0.1: - resolution: {integrity: sha512-7N/q3lyZ+LVCp7PzuxrJr4KMbBE2hW7BT7YNia330OFxIf4d3r5zVpicP2650l7CPN6RM9zOJRl3NGpqSiw3Eg==} - engines: {node: '>=0.10.0'} - dev: true - - /map-obj/4.3.0: - resolution: {integrity: sha512-hdN1wVrZbb29eBGiGjJbeP8JbKjq1urkHJ/LIP/NY48MZ1QVXUsQBV1G1zvYFHn1XE06cwjBsOI2K3Ulnj1YXQ==} - engines: {node: '>=8'} - dev: true - /markdown-table/3.0.3: resolution: {integrity: sha512-Z1NL3Tb1M9wH4XESsCDEksWoKTdlUafKc4pt0GRwjUyXaCFZ+dc3g2erqB6zm3szA2IUSi7VnPI+o/9jnxh9hw==} dev: true - /mathml-tag-names/2.1.3: - resolution: {integrity: sha512-APMBEanjybaPzUrfqU0IMU5I0AswKMH7k8OTLs0vvV4KZpExkTkY87nR/zpbuTPj+gARop7aGUbl11pnDfW6xg==} - dev: true - /md5.js/1.3.5: resolution: {integrity: sha512-xitP+WxNPcTTOgnTJcrhM0xvdPepipPSf3I8EIpGKeFLjt3PlJLIDG3u8EX53ZIubkb+5U2+3rELYpEhHhzdkg==} dependencies: @@ -10568,41 +11411,6 @@ packages: readable-stream: 2.3.7 dev: false - /meow/8.1.2: - resolution: {integrity: sha512-r85E3NdZ+mpYk1C6RjPFEMSE+s1iZMuHtsHAqY0DT3jZczl0diWUZ8g6oU7h0M9cD2EL+PzaYghhCLzR0ZNn5Q==} - engines: {node: '>=10'} - dependencies: - '@types/minimist': 1.2.2 - camelcase-keys: 6.2.2 - decamelize-keys: 1.1.1 - hard-rejection: 2.1.0 - minimist-options: 4.1.0 - normalize-package-data: 3.0.3 - read-pkg-up: 7.0.1 - redent: 3.0.0 - trim-newlines: 3.0.1 - type-fest: 0.18.1 - yargs-parser: 20.2.9 - dev: true - - /meow/9.0.0: - resolution: {integrity: sha512-+obSblOQmRhcyBt62furQqRAQpNyWXo8BuQ5bN7dG8wmwQ+vwHKp/rCFD4CrTP8CsDQD1sjoZ94K417XEUk8IQ==} - engines: {node: '>=10'} - dependencies: - '@types/minimist': 1.2.2 - camelcase-keys: 6.2.2 - decamelize: 1.2.0 - decamelize-keys: 1.1.1 - hard-rejection: 2.1.0 - minimist-options: 4.1.0 - normalize-package-data: 3.0.3 - read-pkg-up: 7.0.1 - redent: 3.0.0 - trim-newlines: 3.0.1 - type-fest: 0.18.1 - yargs-parser: 20.2.9 - dev: true - /merge-stream/2.0.0: resolution: {integrity: sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==} dev: true @@ -10932,11 +11740,6 @@ packages: engines: {node: '>=12'} dev: true - /min-indent/1.0.1: - resolution: {integrity: sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==} - engines: {node: '>=4'} - dev: true - /minimalistic-assert/1.0.1: resolution: {integrity: sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==} dev: true @@ -10950,15 +11753,6 @@ packages: dependencies: brace-expansion: 1.1.11 - /minimist-options/4.1.0: - resolution: {integrity: sha512-Q4r8ghd80yhO/0j1O3B2BjweX3fiHg9cdOwjJd2J76Q135c+NDxGCqdYKQ1SKBuFfgWbAUzBfvYjPUEeNgqN1A==} - engines: {node: '>= 6'} - dependencies: - arrify: 1.0.1 - is-plain-obj: 1.1.0 - kind-of: 6.0.3 - dev: true - /minimist/1.2.6: resolution: {integrity: sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q==} dev: true @@ -11005,6 +11799,16 @@ packages: minimist: 1.2.6 dev: true + /moment-timezone/0.5.40: + resolution: {integrity: sha512-tWfmNkRYmBkPJz5mr9GVDn9vRlVZOTe6yqY92rFxiOdWXbjaR0+9LwQnZGGuNR63X456NqmEkbskte8tWL5ePg==} + dependencies: + moment: 2.29.4 + dev: false + + /moment/2.29.4: + resolution: {integrity: sha512-5LC9SOxjSc2HF6vO2CyuTDNivEdoz2IvyJJGj6X8DJ0eFyfszE0QiEd+iXmBvUP3WHxSjFH/vIsA0EN00cgr8w==} + dev: false + /moo/0.5.2: resolution: {integrity: sha512-iSAJLHYKnX41mKcJKjqvnAN9sf0LMDTXDEvFv+ffuRR9a1MIuXLjMNL6EsnDHSkKLTWNqQQ5uo61P4EbU4NU+Q==} dev: true @@ -11103,6 +11907,11 @@ packages: dependencies: whatwg-url: 5.0.0 + /node-forge/1.3.1: + resolution: {integrity: sha512-dPEtOeMvF9VMcYV/1Wb8CPoVAXtp6MKMlcbAt4ddqmGqUJ6fQZFXkNZNkNlfevtNkGtaSoXf/vNNNSvgrdXwtA==} + engines: {node: '>= 6.13.0'} + dev: false + /node-int64/0.4.0: resolution: {integrity: sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw==} dev: true @@ -11148,16 +11957,6 @@ packages: validate-npm-package-license: 3.0.4 dev: true - /normalize-package-data/3.0.3: - resolution: {integrity: sha512-p2W1sgqij3zMMyRC067Dg16bfzVH+w7hyegmpIvZ4JNjqtGOVAIvLmjBx3yP7YTe9vKJgkoNOPjwQGogDoMXFA==} - engines: {node: '>=10'} - dependencies: - hosted-git-info: 4.1.0 - is-core-module: 2.10.0 - semver: 7.3.8 - validate-npm-package-license: 3.0.4 - dev: true - /normalize-path/3.0.0: resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==} engines: {node: '>=0.10.0'} @@ -11662,6 +12461,11 @@ packages: engines: {node: '>=4'} dev: true + /pify/4.0.1: + resolution: {integrity: sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==} + engines: {node: '>=6'} + dev: true + /pino-abstract-transport/0.5.0: resolution: {integrity: sha512-+KAgmVeqXYbTtU2FScx1XS3kNyfZ5TrXY07V96QnUSFqo2gAqlvmaxH67Lj7SWazqsMabf+58ctdTcBgnOLUOQ==} dependencies: @@ -11695,6 +12499,13 @@ packages: engines: {node: '>= 6'} dev: true + /pkg-dir/4.2.0: + resolution: {integrity: sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==} + engines: {node: '>=8'} + dependencies: + find-up: 4.1.0 + dev: true + /pkg-up/3.1.0: resolution: {integrity: sha512-nDywThFk1i4BQK4twPQ6TA4RT8bDY96yeuCVBWL3ePARCiEKDRSrNGbFIgUJpLp+XeIR65v8ra7WuJOFUBtkMA==} engines: {node: '>=8'} @@ -12084,10 +12895,6 @@ packages: postcss: 8.4.19 dev: true - /postcss-media-query-parser/0.2.3: - resolution: {integrity: sha512-3sOlxmbKcSHMjlUXQZKQ06jOswE7oVkXPxmZdoB1r5l0q6gTFTQSHxNxOrCccElbW7dxNytifNEo8qidX2Vsig==} - dev: true - /postcss-modules-extract-imports/3.0.0_postcss@8.4.19: resolution: {integrity: sha512-bdHleFnP3kZ4NYDhuGlVK+CMrQ/pqUm8bx/oGL93K6gVwiclvX5x0n76fYMKuIGKzlABOy13zsvqjb0f92TEXw==} engines: {node: ^10 || ^12 || >= 14} @@ -12353,19 +13160,6 @@ packages: postcss: 8.4.19 dev: true - /postcss-resolve-nested-selector/0.1.1: - resolution: {integrity: sha512-HvExULSwLqHLgUy1rl3ANIqCsvMS0WHss2UOsXhXnQaZ9VCc2oBvIpXrl00IUFT5ZDITME0o6oiXeiHr2SAIfw==} - dev: true - - /postcss-safe-parser/6.0.0_postcss@8.4.19: - resolution: {integrity: sha512-FARHN8pwH+WiS2OPCxJI8FuRJpTVnn6ZNFiqAM2aeW2LwTHWWmWgIyKC6cUo0L8aeKiF/14MNvnpls6R2PBeMQ==} - engines: {node: '>=12.0'} - peerDependencies: - postcss: ^8.3.3 - dependencies: - postcss: 8.4.19 - dev: true - /postcss-selector-not/5.0.0: resolution: {integrity: sha512-/2K3A4TCP9orP4TNS7u3tGdRFVKqz/E6pX3aGnriPG0jU78of8wsUcqE4QAhWEU0d+WnMSF93Ah3F//vUtK+iQ==} peerDependencies: @@ -12485,31 +13279,6 @@ packages: typescript: '>=2.9' dev: true - /prettier-plugin-organize-imports/2.3.4_prettier@2.8.1: - resolution: {integrity: sha512-R8o23sf5iVL/U71h9SFUdhdOEPsi3nm42FD/oDYIZ2PQa4TNWWuWecxln6jlIQzpZTDMUeO1NicJP6lLn2TtRw==} - peerDependencies: - prettier: '>=2.0' - typescript: '>=2.9' - dependencies: - prettier: 2.8.1 - dev: true - - /prettier-plugin-organize-imports/3.2.1_prettier@2.8.1: - resolution: {integrity: sha512-bty7C2Ecard5EOXirtzeCAqj4FU4epeuWrQt/Z+sh8UVEpBlBZ3m3KNPz2kFu7KgRTQx/C9o4/TdquPD1jOqjQ==} - peerDependencies: - '@volar/vue-language-plugin-pug': ^1.0.4 - '@volar/vue-typescript': ^1.0.4 - prettier: '>=2.0' - typescript: '>=2.9' - peerDependenciesMeta: - '@volar/vue-language-plugin-pug': - optional: true - '@volar/vue-typescript': - optional: true - dependencies: - prettier: 2.8.1 - dev: true - /prettier-plugin-packagejson/2.3.0: resolution: {integrity: sha512-2SAPMMk1UDkqsB7DifWKcwCm6VC52JXMrzLHfbcQHJRWhRCj9zziOy+s+2XOyPBeyqFqS+A/1IKzOrxKFTo6pw==} peerDependencies: @@ -12521,18 +13290,6 @@ packages: sort-package-json: 1.57.0 dev: true - /prettier-plugin-packagejson/2.3.0_prettier@2.8.1: - resolution: {integrity: sha512-2SAPMMk1UDkqsB7DifWKcwCm6VC52JXMrzLHfbcQHJRWhRCj9zziOy+s+2XOyPBeyqFqS+A/1IKzOrxKFTo6pw==} - peerDependencies: - prettier: '>= 1.16.0' - peerDependenciesMeta: - prettier: - optional: true - dependencies: - prettier: 2.8.1 - sort-package-json: 1.57.0 - dev: true - /prettier/2.8.1: resolution: {integrity: sha512-lqGoSJBQNJidqCHE80vqZJHWHRFoNYsSpP9AjFhlhi9ODCJA541svILes/+/1GM3VaL/abZi7cpFzOpdR9UPKg==} engines: {node: '>=10.13.0'} @@ -12545,6 +13302,15 @@ packages: renderkid: 3.0.0 dev: true + /pretty-format/29.3.1: + resolution: {integrity: sha512-FyLnmb1cYJV8biEIiRyzRFvs2lry7PPIvOqKVe1GCUEYg4YGmlx1qG9EJNMxArYm7piII4qb8UV1Pncq5dxmcg==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + dependencies: + '@jest/schemas': 29.0.0 + ansi-styles: 5.2.0 + react-is: 18.2.0 + dev: true + /prism-react-renderer/1.3.5_react@18.2.0: resolution: {integrity: sha512-IJ+MSwBWKG+SM3b2SUfdrhC+gu01QkV2KmRQgREThBfSQRoufqRfxfHUxpG1WcaFjP+kojcFyO9Qqtpgt3qLCg==} peerDependencies: @@ -12599,6 +13365,14 @@ packages: asap: 2.0.6 dev: false + /prompts/2.4.2: + resolution: {integrity: sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==} + engines: {node: '>= 6'} + dependencies: + kleur: 3.0.3 + sisteransi: 1.0.5 + dev: true + /prop-types/15.8.1: resolution: {integrity: sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==} dependencies: @@ -12678,11 +13452,6 @@ packages: resolution: {integrity: sha512-QFADYnsVoBMw1srW7OVKEYjG+MbIa49s54w1MA1EDY6r2r/sTcKKYqRX1f4GYvnXP7eN/Pe9HFcX+hwzmrXRHA==} dev: false - /q/1.5.1: - resolution: {integrity: sha512-kV/CThkXo6xyFEZUugw/+pIOywXcDbFYgSct5cT3gqlbkBE1SJdwy6UQoZvodiWF/ckQLZyDE/Bu1M6gVu5lVw==} - engines: {node: '>=0.6.0', teleport: '>=0.2.0'} - dev: true - /query-string/6.14.1: resolution: {integrity: sha512-XDxAeVmpfu1/6IjyT/gXHOl+S0vQ9owggJ30hhWKdHAsNPOcasn5o9BW0eejZqL2e4vMjhAxoW3jVHcD6mbcYw==} engines: {node: '>=6'} @@ -12717,11 +13486,6 @@ packages: resolution: {integrity: sha512-tYC1Q1hgyRuHgloV/YXs2w15unPVh8qfu/qCTfhTYamaw7fyhumKa2yGpdSo87vY32rIclj+4fWYQXUMs9EHvg==} dev: true - /quick-lru/4.0.1: - resolution: {integrity: sha512-ARhCpm70fzdcvNQfPoy49IaanKkTlRWF2JMzqhcJbhSFRZv7nPTvZJdcY7301IPmvW+/p0RgIWnQDLJxifsQ7g==} - engines: {node: '>=8'} - dev: true - /railroad-diagrams/1.0.0: resolution: {integrity: sha512-cz93DjNeLY0idrCNOH6PviZGRN9GJhsdm9hpn1YCS879fj4W+x5IFJhhkRZcwVgMmFF7R82UA/7Oh+R8lLZg6A==} dev: true @@ -13510,6 +14274,10 @@ packages: /react-is/16.13.1: resolution: {integrity: sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==} + /react-is/18.2.0: + resolution: {integrity: sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==} + dev: true + /react-json-view/1.21.3_rj7ozvcq3uehdlnj3cbwzbi5ce: resolution: {integrity: sha512-13p8IREj9/x/Ye4WI/JpjhoIwuzEgUAtgJZNBJckfzJt1qyh24BdTm6UQNGnyTq9dapQdrqvquZTo3dz1X6Cjw==} peerDependencies: @@ -13637,6 +14405,17 @@ packages: - '@types/react' dev: false + /react-toastify/9.1.1_biqbaboplfbrettd7655fr4n2y: + resolution: {integrity: sha512-pkFCla1z3ve045qvjEmn2xOJOy4ZciwRXm1oMPULVkELi5aJdHCN/FHnuqXq8IwGDLB7PPk2/J6uP9D8ejuiRw==} + peerDependencies: + react: '>=16' + react-dom: '>=16' + dependencies: + clsx: 1.2.1 + react: 18.2.0 + react-dom: 18.2.0_react@18.2.0 + dev: false + /react/18.1.0: resolution: {integrity: sha512-4oL8ivCz5ZEPyclFQXaNksK3adutVS8l2xzZU0cqEFrE9Sb7fC0EFK5uEk74wIreL1DERyjvsU915j1pcT2uEQ==} engines: {node: '>=0.10.0'} @@ -13684,20 +14463,11 @@ packages: '@reactflow/core': 11.3.0_rekktpi253ghbdqoi5qfkp32ay '@reactflow/minimap': 11.2.0_rekktpi253ghbdqoi5qfkp32ay '@reactflow/node-toolbar': 1.0.0_rekktpi253ghbdqoi5qfkp32ay - react: 18.2.0 - react-dom: 18.2.0_react@18.2.0 - transitivePeerDependencies: - - immer - dev: false - - /read-pkg-up/7.0.1: - resolution: {integrity: sha512-zK0TB7Xd6JpCLmlLmufqykGE+/TlOePD6qKClNW7hHDKFh/J7/7gCWGR7joEQEW1bKq3a3yUZSObOoWLFQ4ohg==} - engines: {node: '>=8'} - dependencies: - find-up: 4.1.0 - read-pkg: 5.2.0 - type-fest: 0.8.1 - dev: true + react: 18.2.0 + react-dom: 18.2.0_react@18.2.0 + transitivePeerDependencies: + - immer + dev: false /read-pkg/5.2.0: resolution: {integrity: sha512-Ug69mNOpfvKDAc2Q8DRpMjjzdtrnv9HcSMX+4VsZxD1aZ6ZzrIE7rlzXBtWTyhULSMKg076AW6WR5iZpD0JiOg==} @@ -13768,14 +14538,6 @@ packages: react-dom: 18.2.0_react@18.2.0 dev: false - /redent/3.0.0: - resolution: {integrity: sha512-6tDA8g98We0zd0GvVeMT9arEOnTw9qM03L9cJXaCjrip1OO764RDBLBfrB4cwzNGDj5OA5ioymC9GkizgWJDUg==} - engines: {node: '>=8'} - dependencies: - indent-string: 4.0.0 - strip-indent: 3.0.0 - dev: true - /redux/4.2.0: resolution: {integrity: sha512-oSBmcKKIuIR4ME29/AeNUnl5L+hvBq7OaJWzaptTQJAntaPvxIJqfnjbaEiCzzaIz+XmVILfqAM3Ob0aXLPfjA==} dependencies: @@ -13962,6 +14724,7 @@ packages: resolution: {integrity: sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==} engines: {node: '>=0.10.0'} dev: true + optional: true /reselect/4.1.6: resolution: {integrity: sha512-ZovIuXqto7elwnxyXbBtCPo9YFEr3uJqj2rRbcOOog1bmu2Ag85M4hixSwFWyaBMKXNgvPaJ9OSu9SkBPIeJHQ==} @@ -13970,6 +14733,13 @@ packages: /resize-observer-polyfill/1.5.1: resolution: {integrity: sha512-LwZrotdHOo12nQuZlHEmtuXdqGoOD0OhaxopaNFxWzInpEgaLWoVuAMbTzixuosCx2nEG58ngzW3vxdWoxIgdg==} + /resolve-cwd/3.0.0: + resolution: {integrity: sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg==} + engines: {node: '>=8'} + dependencies: + resolve-from: 5.0.0 + dev: true + /resolve-dir/0.1.1: resolution: {integrity: sha512-QxMPqI6le2u0dCLyiGzgy92kjkkL6zO0XyvHzjdTNH3zM6e5Hz3BwG6+aEyNgiQ5Xz6PwTwgQEj3U50dByPKIA==} engines: {node: '>=0.10.0'} @@ -14006,6 +14776,12 @@ packages: dependencies: global-dirs: 0.1.1 dev: true + optional: true + + /resolve.exports/1.1.0: + resolution: {integrity: sha512-J1l+Zxxp4XK3LUDZ9m60LRJF/mAe4z6a4xyabPHk7pvK5t35dACV32iIjJDFeWZFfZlO29w6SZ67knR0tHzJtQ==} + engines: {node: '>=10'} + dev: true /resolve/1.17.0: resolution: {integrity: sha512-ic+7JYiV8Vi2yzQGFWOkiZD5Z9z7O2Zhm9XMaTxdJExKasieFCr+yXZ/WmXsckHiKl12ar0y6XiXDx3m4RHn1w==} @@ -14092,6 +14868,40 @@ packages: inherits: 2.0.4 dev: true + /rollup-plugin-generate-package-json/3.2.0_rollup@2.78.1: + resolution: {integrity: sha512-+Kq1kFVr+maxW/mZB+E+XuaieCXVZqjl2tNU9k3TtAMs3NOaeREa5sRHy67qKDmcnFtZZukIQ3dFCcnV+r0xyw==} + engines: {node: '>=8.3'} + peerDependencies: + rollup: '>=1.0.0' + dependencies: + read-pkg: 5.2.0 + rollup: 2.78.1 + write-pkg: 4.0.0 + dev: true + + /rollup-plugin-peer-deps-external/2.2.4_rollup@2.78.1: + resolution: {integrity: sha512-AWdukIM1+k5JDdAqV/Cxd+nejvno2FVLVeZ74NKggm3Q5s9cbbcOgUPGdbxPi4BXu7xGaZ8HG12F+thImYu/0g==} + peerDependencies: + rollup: '*' + dependencies: + rollup: 2.78.1 + dev: true + + /rollup-plugin-typescript2/0.32.1_2hrmc4tvghoalmdywjmqyits4a: + resolution: {integrity: sha512-RanO8bp1WbeMv0bVlgcbsFNCn+Y3rX7wF97SQLDxf0fMLsg0B/QFF005t4AsGUcDgF3aKJHoqt4JF2xVaABeKw==} + peerDependencies: + rollup: '>=1.26.3' + typescript: '>=2.4.0' + dependencies: + '@rollup/pluginutils': 4.2.1 + find-cache-dir: 3.3.2 + fs-extra: 10.1.0 + resolve: 1.22.1 + rollup: 2.78.1 + tslib: 2.4.0 + typescript: 4.5.5 + dev: true + /rollup-plugin-visualizer/5.6.0: resolution: {integrity: sha512-CKcc8GTUZjC+LsMytU8ocRr/cGZIfMR7+mdy4YnlyetlmIl/dM8BMnOEpD4JPIGt+ZVW7Db9ZtSsbgyeBH3uTA==} engines: {node: '>=12'} @@ -14126,7 +14936,6 @@ packages: hasBin: true optionalDependencies: fsevents: 2.3.2 - dev: true /run-async/2.4.1: resolution: {integrity: sha512-tvVnVv01b8c1RrA6Ep7JkStj85Guv/YrMcwqYQnwjsAS2cTmmPGBBjAjpCW7RrSodNSoE2/qg9O4bceNvUuDgQ==} @@ -14261,14 +15070,6 @@ packages: hasBin: true dev: true - /semver/7.3.7: - resolution: {integrity: sha512-QlYTucUYOews+WeEujDoEGziz4K6c47V/Bd+LjSSYcA94p+DmINdf7ncaUinThfvZyu13lN9OY1XDxt8C0Tw0g==} - engines: {node: '>=10'} - hasBin: true - dependencies: - lru-cache: 6.0.0 - dev: true - /semver/7.3.8: resolution: {integrity: sha512-NB1ctGL5rlHrPJtFDVIVzTyQylMLu9N9VICA6HSFJo8MCGVTMW6gfpicwKmmK/dAjTOrqu5l63JJOpDSrAis3A==} engines: {node: '>=10'} @@ -14339,6 +15140,10 @@ packages: resolution: {integrity: sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==} dev: true + /sisteransi/1.0.5: + resolution: {integrity: sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==} + dev: true + /sitemap/7.1.1: resolution: {integrity: sha512-mK3aFtjz4VdJN0igpIJrinf3EO8U8mxOPsTBzSsy06UtjZQJ3YY3o3Xa7zSc5nMqcMrRwlChHZ18Kxg0caiPBg==} engines: {node: '>=12.0.0', npm: '>=5.6.0'} @@ -14408,6 +15213,13 @@ packages: atomic-sleep: 1.0.0 dev: true + /sort-keys/2.0.0: + resolution: {integrity: sha512-/dPCrG1s3ePpWm6yBbxZq5Be1dXGLyLn9Z791chDC3NFrpkVbWGzkBwPN1knaciexFXgRJ7hzdnwZ4stHSDmjg==} + engines: {node: '>=4'} + dependencies: + is-plain-obj: 1.1.0 + dev: true + /sort-object-keys/1.1.3: resolution: {integrity: sha512-855pvK+VkU7PaKYPc+Jjnmt4EzejQHyhhF33q31qG8x7maDzkeFhAAThdCYay11CISO+qAMwjOBP+fPZe0IPyg==} dev: true @@ -14429,6 +15241,13 @@ packages: engines: {node: '>=0.10.0'} dev: true + /source-map-support/0.5.13: + resolution: {integrity: sha512-SHSKFHadjVA5oR4PPqhtAVdcBWwRYVd6g6cAXnIbRiIwc2EhPrTuKUBdSLvlEKyIP3GCf89fltvcZiP9MMFA1w==} + dependencies: + buffer-from: 1.1.2 + source-map: 0.6.1 + dev: true + /source-map-support/0.5.21: resolution: {integrity: sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==} dependencies: @@ -14511,12 +15330,6 @@ packages: resolution: {integrity: sha512-43ZssAJaMusuKWL8sKUBQXHWOpq8d6CfN/u1p4gUzfJkM05C8rxTmYrkIPTXapZpORA6LkkzcUulJ8FqA7Uudw==} engines: {node: '>=6'} - /split2/3.2.2: - resolution: {integrity: sha512-9NThjpgZnifTkJpzTZ7Eue85S49QwpNhZTq6GRJwObb6jnLFNGB7Qm73V5HewTROPyxD0C29xqmaI68bQtV+hg==} - dependencies: - readable-stream: 3.6.0 - dev: true - /split2/4.1.0: resolution: {integrity: sha512-VBiJxFkxiXRlUIeyMQi8s4hgvKCSjtknJv/LVYbrgALPwf5zSKmEwV9Lst25AkvMDnvxODugjdl6KZgwKM1WYQ==} engines: {node: '>= 10.x'} @@ -14543,6 +15356,13 @@ packages: deprecated: 'Modern JS already guarantees Array#sort() is a stable sort, so this library is deprecated. See the compatibility table on MDN: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/sort#browser_compatibility' dev: true + /stack-utils/2.0.6: + resolution: {integrity: sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ==} + engines: {node: '>=10'} + dependencies: + escape-string-regexp: 2.0.0 + dev: true + /stackframe/1.3.4: resolution: {integrity: sha512-oeVtt7eWQS+Na6F//S4kJ2K2VbRlS9D43mAlMyVpVWovy9o+jfgH8O9agzANzaiLjclA0oYzUXEM4PurhSUChw==} dev: true @@ -14588,6 +15408,14 @@ packages: resolution: {integrity: sha512-u/1tdPl4yQnPBjnVrmdLo9gtuLvELKsAoRapekWggdiQNvvvum+jYF329d84NAa660KQw7pB2n36KrIKVoXa3A==} dev: false + /string-length/4.0.2: + resolution: {integrity: sha512-+l6rNN5fYHNhZZy41RXsYptCjA2Igmq4EG7kZAYFQI1E1VTXarr6ZPXBg6eq7Y6eK4FEhY6AJlyuFIb/v/S0VQ==} + engines: {node: '>=10'} + dependencies: + char-regex: 1.0.2 + strip-ansi: 6.0.1 + dev: true + /string-width/2.1.1: resolution: {integrity: sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==} engines: {node: '>=4'} @@ -14614,6 +15442,10 @@ packages: strip-ansi: 7.0.1 dev: true + /string.fromcodepoint/0.2.1: + resolution: {integrity: sha512-n69H31OnxSGSZyZbgBlvYIXlrMhJQ0dQAX1js1QDhpaUH6zmU3QYlj07bCwCNlPOu3oRXIubGPl2gDGnHsiCqg==} + dev: false + /string.prototype.matchall/4.0.7: resolution: {integrity: sha512-f48okCX7JiwVi1NXCVWcFnZgADDC/n2vePlQ/KUCNqCikLLilQvwjMO8+BHVKvgzH0JB0J9LEPgxOGT02RoETg==} dependencies: @@ -14714,13 +15546,6 @@ packages: engines: {node: '>=12'} dev: true - /strip-indent/3.0.0: - resolution: {integrity: sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ==} - engines: {node: '>=8'} - dependencies: - min-indent: 1.0.1 - dev: true - /strip-json-comments/2.0.1: resolution: {integrity: sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==} engines: {node: '>=0.10.0'} @@ -14731,9 +15556,9 @@ packages: engines: {node: '>=8'} dev: true - /style-search/0.1.0: - resolution: {integrity: sha512-Dj1Okke1C3uKKwQcetra4jSuk0DqbzbYtXipzFlFMZtowbF1x7BKJwB9AayVMyFARvU8EDrZdcax4At/452cAg==} - dev: true + /strnum/1.0.5: + resolution: {integrity: sha512-J8bbNyKKXl5qYcR36TIO8W3mVGVHrmmxsd5PAItGkmyzwJvybiw2IVq5nqd0i4LSNSkB/sx9VHllbfFdr9k1JA==} + dev: false /style-to-object/0.3.0: resolution: {integrity: sha512-CzFnRRXhzWIdItT3OmF8SQfWyahHhjq3HwcMNCNLn+N7klOOqPjMeG/4JSu77D7ypZdGvSzvkrbyeTMizz2VrA==} @@ -14747,14 +15572,6 @@ packages: stylelint: ^14.4.0 dev: true - /stylelint-config-recommended/7.0.0_stylelint@14.16.0: - resolution: {integrity: sha512-yGn84Bf/q41J4luis1AZ95gj0EQwRX8lWmGmBwkwBNSkpGSpl66XcPTulxGa/Z91aPoNGuIGBmFkcM1MejMo9Q==} - peerDependencies: - stylelint: ^14.4.0 - dependencies: - stylelint: 14.16.0 - dev: true - /stylelint-config-standard/25.0.0: resolution: {integrity: sha512-21HnP3VSpaT1wFjFvv9VjvOGDtAviv47uTp3uFmzcN+3Lt+RYRv6oAplLaV51Kf792JSxJ6svCJh/G18E9VnCA==} peerDependencies: @@ -14763,62 +15580,6 @@ packages: stylelint-config-recommended: 7.0.0 dev: true - /stylelint-config-standard/25.0.0_stylelint@14.16.0: - resolution: {integrity: sha512-21HnP3VSpaT1wFjFvv9VjvOGDtAviv47uTp3uFmzcN+3Lt+RYRv6oAplLaV51Kf792JSxJ6svCJh/G18E9VnCA==} - peerDependencies: - stylelint: ^14.4.0 - dependencies: - stylelint: 14.16.0 - stylelint-config-recommended: 7.0.0_stylelint@14.16.0 - dev: true - - /stylelint/14.16.0: - resolution: {integrity: sha512-X6uTi9DcxjzLV8ZUAjit1vsRtSwcls0nl07c9rqOPzvpA8IvTX/xWEkBRowS0ffevRrqkHa/ThDEu86u73FQDg==} - engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} - hasBin: true - dependencies: - '@csstools/selector-specificity': 2.0.2_tbwh2mpcdwdeb2slx6bobindua - balanced-match: 2.0.0 - colord: 2.9.3 - cosmiconfig: 7.1.0 - css-functions-list: 3.1.0 - debug: 4.3.4 - fast-glob: 3.2.12 - fastest-levenshtein: 1.0.16 - file-entry-cache: 6.0.1 - global-modules: 2.0.0 - globby: 11.1.0 - globjoin: 0.1.4 - html-tags: 3.2.0 - ignore: 5.2.1 - import-lazy: 4.0.0 - imurmurhash: 0.1.4 - is-plain-object: 5.0.0 - known-css-properties: 0.26.0 - mathml-tag-names: 2.1.3 - meow: 9.0.0 - micromatch: 4.0.5 - normalize-path: 3.0.0 - picocolors: 1.0.0 - postcss: 8.4.19 - postcss-media-query-parser: 0.2.3 - postcss-resolve-nested-selector: 0.1.1 - postcss-safe-parser: 6.0.0_postcss@8.4.19 - postcss-selector-parser: 6.0.11 - postcss-value-parser: 4.2.0 - resolve-from: 5.0.0 - string-width: 4.2.3 - strip-ansi: 6.0.1 - style-search: 0.1.0 - supports-hyperlinks: 2.3.0 - svg-tags: 1.0.0 - table: 6.8.1 - v8-compile-cache: 2.3.0 - write-file-atomic: 4.0.2 - transitivePeerDependencies: - - supports-color - dev: true - /stylis/4.0.13: resolution: {integrity: sha512-xGPXiFVl4YED9Jh7Euv2V220mriG9u4B2TA6Ybjc1catrstKD2PpIdU3U0RKpkVBC2EhmL/F0sPCr9vrFTNRag==} @@ -14846,14 +15607,6 @@ packages: has-flag: 4.0.0 dev: true - /supports-hyperlinks/2.3.0: - resolution: {integrity: sha512-RpsAZlpWcDwOPQA22aCH4J0t7L8JmAvsCxfOSEwm7cQs3LshN36QaTkwd70DnBOXDWGssw2eUoc8CaRWT0XunA==} - engines: {node: '>=8'} - dependencies: - has-flag: 4.0.0 - supports-color: 7.2.0 - dev: true - /supports-preserve-symlinks-flag/1.0.0: resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==} engines: {node: '>= 0.4'} @@ -14862,10 +15615,6 @@ packages: resolution: {integrity: sha512-e4hG1hRwoOdRb37cIMSgzNsxyzKfayW6VOflrwvR+/bzrkyxY/31WkbgnQpgtrNp1SdpJvpUAGTa/ZoiPNDuRQ==} dev: true - /svg-tags/1.0.0: - resolution: {integrity: sha512-ovssysQTa+luh7A5Weu3Rta6FJlFBBbInjOh722LIt6klpU2/HtdUbszju/G4devcvk8PGt7FCLv5wftu3THUA==} - dev: true - /svgo/2.8.0: resolution: {integrity: sha512-+N/Q9kV1+F+UeWYoSiULYo4xYSDQlTgb+ayMobAXPwMnLvop7oxKMo9OzIrX5x3eS4L4f2UHhc9axXwY8DpChg==} engines: {node: '>=10.13.0'} @@ -14890,17 +15639,6 @@ packages: use-sync-external-store: 1.2.0_react@18.2.0 dev: false - /table/6.8.1: - resolution: {integrity: sha512-Y4X9zqrCftUhMeH2EptSSERdVKt/nEdijTOacGD/97EKjhQ/Qs8RTlEGABSJNNN8lac9kheH+af7yAkEWlgneA==} - engines: {node: '>=10.0.0'} - dependencies: - ajv: 8.11.0 - lodash.truncate: 4.4.2 - slice-ansi: 4.0.0 - string-width: 4.2.3 - strip-ansi: 6.0.1 - dev: true - /tapable/0.2.9: resolution: {integrity: sha512-2wsvQ+4GwBvLPLWsNfLCDYGsW6xb7aeC6utq2Qh0PFwgEy7K7dsma9Jsmb2zSQj7GvYAyUGSntLtsv++GmgL1A==} engines: {node: '>=0.6'} @@ -14997,11 +15735,6 @@ packages: minimatch: 3.1.2 dev: true - /text-extensions/1.9.0: - resolution: {integrity: sha512-wiBrwC1EhBelW12Zy26JeOUkQ5mRu+5o8rpsJk5+2t+Y5vE7e842qtZDQ2g1NpX/29HdyFeJ4nSIhI47ENSxlQ==} - engines: {node: '>=0.10'} - dev: true - /text-table/0.2.0: resolution: {integrity: sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==} dev: true @@ -15028,12 +15761,6 @@ packages: xtend: 4.0.2 dev: true - /through2/4.0.2: - resolution: {integrity: sha512-iOqSav00cVxEEICeD7TjLB1sueEL+81Wpzp2bY17uZjZN0pWZPuo4suZ/61VujxmqSGFfgOcNuTZ85QJwNZQpw==} - dependencies: - readable-stream: 3.6.0 - dev: true - /timed-out/4.0.1: resolution: {integrity: sha512-G7r3AhovYtr5YKOWQkta8RKAPb+J9IsO4uVmzjl8AZwfhs8UcUwTiD6gcJYSgOtzyjvQKrKYn41syHbUWMkafA==} engines: {node: '>=0.10.0'} @@ -15083,6 +15810,10 @@ packages: /toggle-selection/1.0.6: resolution: {integrity: sha512-BiZS+C1OS8g/q2RRbJmy59xpyghNBqrr6k5L/uKBGRsTfxmu3ffiRnd8mlGPUVayg8pvfi5urfnu8TU7DVOkLQ==} + /toposort/2.0.2: + resolution: {integrity: sha512-0a5EOkAUp8D4moMi2W8ZF8jcga7BgZd91O/yabJCFY8az+XSzeGyTKs0Aoo897iV1Nj6guFq8orWDS96z91oGg==} + dev: false + /tr46/0.0.3: resolution: {integrity: sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==} @@ -15099,15 +15830,43 @@ packages: resolution: {integrity: sha512-kRj8B+YHZCc9kQYdWfJB2/oUl9rA99qbowYYBtr4ui4mZyAQ2JpvVBd/6U2YloATfqBhBTSMhTpgBHtU0Mf3Rg==} dev: true - /trim-newlines/3.0.1: - resolution: {integrity: sha512-c1PTsA3tYrIsLGkJkzHF+w9F2EyxfXGo4UyJc4pFL++FMjnq0HJS69T3M7d//gKrFKwy429bouPescbjecU+Zw==} - engines: {node: '>=8'} - dev: true - /trough/2.1.0: resolution: {integrity: sha512-AqTiAOLcj85xS7vQ8QkAV41hPDIJ71XJB4RCUrzo/1GM2CQwhkJGaf9Hgr7BOugMRpgGUrqRg/DrBDl4H40+8g==} dev: true + /ts-jest/29.0.1_3bi5c5acf6iiidhyy65zjxahau: + resolution: {integrity: sha512-htQOHshgvhn93QLxrmxpiQPk69+M1g7govO1g6kf6GsjCv4uvRV0znVmDrrvjUrVCnTYeY4FBxTYYYD4airyJA==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + hasBin: true + peerDependencies: + '@babel/core': '>=7.0.0-beta.0 <8' + '@jest/types': ^29.0.0 + babel-jest: ^29.0.0 + esbuild: '*' + jest: ^29.0.0 + typescript: '>=4.3' + peerDependenciesMeta: + '@babel/core': + optional: true + '@jest/types': + optional: true + babel-jest: + optional: true + esbuild: + optional: true + dependencies: + bs-logger: 0.2.6 + fast-json-stable-stringify: 2.1.0 + jest: 29.0.3 + jest-util: 29.3.1 + json5: 2.2.1 + lodash.memoize: 4.1.2 + make-error: 1.3.6 + semver: 7.3.8 + typescript: 4.5.5 + yargs-parser: 21.1.1 + dev: true + /ts-node/10.9.1_jcmx33t3olsvcxopqdljsohpme: resolution: {integrity: sha512-NtVysVPkxxrwFGUUxGYhfux8k78pQB3JqYBXlLRZgdGUqTO5wU/UyHop5p70iEbGhB7q5KmiZiU0Y3KlJrScEw==} hasBin: true @@ -15138,6 +15897,7 @@ packages: v8-compile-cache-lib: 3.0.1 yn: 3.1.1 dev: true + optional: true /ts-pattern/4.0.5: resolution: {integrity: sha512-Bq44KCEt7JVaNLa148mBCJkcQf4l7jtLEBDuDdeuLynWDA+1a60P4D0rMkqSM9mOKLQbIWUddE9h3XKyKwBeqA==} @@ -15162,7 +15922,6 @@ packages: /tslib/1.14.1: resolution: {integrity: sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==} - dev: true /tslib/2.4.0: resolution: {integrity: sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ==} @@ -15177,6 +15936,16 @@ packages: tslib: 1.14.1 dev: true + /tsutils/3.21.0_typescript@4.5.5: + resolution: {integrity: sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==} + engines: {node: '>= 6'} + peerDependencies: + typescript: '>=2.8.0 || >= 3.2.0-dev || >= 3.3.0-dev || >= 3.4.0-dev || >= 3.5.0-dev || >= 3.6.0-dev || >= 3.6.0-beta || >= 3.7.0-dev || >= 3.7.0-beta' + dependencies: + tslib: 1.14.1 + typescript: 4.5.5 + dev: true + /tsutils/3.21.0_typescript@4.8.4: resolution: {integrity: sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==} engines: {node: '>= 6'} @@ -15209,9 +15978,9 @@ packages: prelude-ls: 1.2.1 dev: true - /type-fest/0.18.1: - resolution: {integrity: sha512-OIAYXk8+ISY+qTOwkHtKqzAuxchoMiD9Udx+FSGQDuiRR+PJKJHc2NJAXlbhkGwTt/4/nKZxELY1w3ReWOL8mw==} - engines: {node: '>=10'} + /type-detect/4.0.8: + resolution: {integrity: sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==} + engines: {node: '>=4'} dev: true /type-fest/0.20.2: @@ -15224,13 +15993,13 @@ packages: engines: {node: '>=10'} dev: true - /type-fest/0.6.0: - resolution: {integrity: sha512-q+MB8nYR1KDLrgr4G5yemftpMC7/QLqVndBmEEdqzmNj5dcFOO4Oo8qlwZE3ULT3+Zim1F8Kq4cBnikNhlCMlg==} - engines: {node: '>=8'} + /type-fest/0.4.1: + resolution: {integrity: sha512-IwzA/LSfD2vC1/YDYMv/zHP4rDF1usCwllsDpbolT3D4fUepIO7f9K70jjmUewU/LmGUKJcwcVtDCpnKk4BPMw==} + engines: {node: '>=6'} dev: true - /type-fest/0.8.1: - resolution: {integrity: sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==} + /type-fest/0.6.0: + resolution: {integrity: sha512-q+MB8nYR1KDLrgr4G5yemftpMC7/QLqVndBmEEdqzmNj5dcFOO4Oo8qlwZE3ULT3+Zim1F8Kq4cBnikNhlCMlg==} engines: {node: '>=8'} dev: true @@ -15238,6 +16007,11 @@ packages: resolution: {integrity: sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA==} dev: true + /typescript/4.5.5: + resolution: {integrity: sha512-TCTIul70LyWe6IJWT8QSYeA54WQe8EjQFU4wY52Fasj5UKx88LNYKCgBEHcOMOrFF1rKGbD8v/xcNWVUq9SymA==} + engines: {node: '>=4.2.0'} + hasBin: true + /typescript/4.7.4: resolution: {integrity: sha512-C0WQT0gezHuw6AdY1M2jxUO83Rjf0HP7Sk1DtXj6j1EwkQNZrHAg2XPWlq62oqEhYvONq5pkC2Y9oPljWToLmQ==} engines: {node: '>=4.2.0'} @@ -15301,53 +16075,6 @@ packages: - webpack-plugin-serve dev: true - /umi/4.0.36_hahnn56zypgjpv523dkkei53ve: - resolution: {integrity: sha512-GT2g0Nai6FcSQxAjwfONewagHUv2Lqg8IWBtACXCDj3Nm2AjhJIGyG+UrOsgpI/T+/Upz7H3XjBjciex/Lrw0Q==} - engines: {node: '>=14'} - hasBin: true - dependencies: - '@babel/runtime': 7.18.9 - '@umijs/bundler-utils': 4.0.36 - '@umijs/bundler-webpack': 4.0.36 - '@umijs/core': 4.0.36 - '@umijs/lint': 4.0.36_nc6yd4bwypbhcidfskuo37yccq - '@umijs/preset-umi': 4.0.36 - '@umijs/renderer-react': 4.0.36_biqbaboplfbrettd7655fr4n2y - '@umijs/server': 4.0.36 - '@umijs/test': 4.0.36 - '@umijs/utils': 4.0.36 - prettier-plugin-organize-imports: 2.3.4_prettier@2.8.1 - prettier-plugin-packagejson: 2.3.0_prettier@2.8.1 - transitivePeerDependencies: - - '@babel/core' - - '@types/react' - - '@types/webpack' - - encoding - - eslint - - jest - - postcss - - postcss-html - - postcss-jsx - - postcss-less - - postcss-markdown - - postcss-scss - - prettier - - react - - react-dom - - rollup - - sockjs-client - - styled-components - - stylelint - - supports-color - - type-fest - - typescript - - vue-template-compiler - - webpack - - webpack-dev-server - - webpack-hot-middleware - - webpack-plugin-serve - dev: true - /umi/4.0.36_sjkfibpvkg33iagsiij4zgoske: resolution: {integrity: sha512-GT2g0Nai6FcSQxAjwfONewagHUv2Lqg8IWBtACXCDj3Nm2AjhJIGyG+UrOsgpI/T+/Upz7H3XjBjciex/Lrw0Q==} engines: {node: '>=14'} @@ -15404,6 +16131,12 @@ packages: which-boxed-primitive: 1.0.2 dev: true + /unescape-js/1.1.4: + resolution: {integrity: sha512-42SD8NOQEhdYntEiUQdYq/1V/YHwr1HLwlHuTJB5InVVdOSbgI6xu8jK5q65yIzuFCfczzyDF/7hbGzVbyCw0g==} + dependencies: + string.fromcodepoint: 0.2.1 + dev: false + /unfetch/4.2.0: resolution: {integrity: sha512-F9p7yYCn6cIW9El1zi0HI6vqpeIvBsr3dSuRO6Xuppb1u5rXpCPmMvLSyECLhybr9isec8Ohl0hPekMVrEinDA==} dev: true @@ -15664,11 +16397,21 @@ packages: /v8-compile-cache-lib/3.0.1: resolution: {integrity: sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==} dev: true + optional: true /v8-compile-cache/2.3.0: resolution: {integrity: sha512-l8lCEmLcLYZh4nbunNZvQCJc5pv7+RCwa8q/LdUx8u7lsWvPDKmpodJAJNwkAhJC//dFY48KuIEmjtd4RViDrA==} dev: true + /v8-to-istanbul/9.0.1: + resolution: {integrity: sha512-74Y4LqY74kLE6IFyIjPtkSTWzUZmj8tdHT9Ii/26dvQ6K9Dl2NbEfj0XgU2sHCtKgt5VupqhlO/5aWuqS+IY1w==} + engines: {node: '>=10.12.0'} + dependencies: + '@jridgewell/trace-mapping': 0.3.17 + '@types/istanbul-lib-coverage': 2.0.4 + convert-source-map: 1.9.0 + dev: true + /validate-npm-package-license/3.0.4: resolution: {integrity: sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==} dependencies: @@ -15949,6 +16692,27 @@ packages: signal-exit: 3.0.7 dev: true + /write-json-file/3.2.0: + resolution: {integrity: sha512-3xZqT7Byc2uORAatYiP3DHUUAVEkNOswEWNs9H5KXiicRTvzYzYqKjYc4G7p+8pltvAw641lVByKVtMpf+4sYQ==} + engines: {node: '>=6'} + dependencies: + detect-indent: 5.0.0 + graceful-fs: 4.2.10 + make-dir: 2.1.0 + pify: 4.0.1 + sort-keys: 2.0.0 + write-file-atomic: 2.4.3 + dev: true + + /write-pkg/4.0.0: + resolution: {integrity: sha512-v2UQ+50TNf2rNHJ8NyWttfm/EJUBWMJcx6ZTYZr6Qp52uuegWw/lBkCtCbnYZEmPRNL61m+u67dAmGxo+HTULA==} + engines: {node: '>=8'} + dependencies: + sort-keys: 2.0.0 + type-fest: 0.4.1 + write-json-file: 3.2.0 + dev: true + /xdg-basedir/3.0.0: resolution: {integrity: sha512-1Dly4xqlulvPD3fZUQJLY+FUIeqN3N2MM3uqe4rCJftAvOjFa3jFGfctOgluGx4ahPbUCsZkmJILiP0Vi4T6lQ==} engines: {node: '>=4'} @@ -15989,11 +16753,6 @@ packages: engines: {node: '>= 14'} dev: true - /yargs-parser/20.2.9: - resolution: {integrity: sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==} - engines: {node: '>=10'} - dev: true - /yargs-parser/21.1.1: resolution: {integrity: sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==} engines: {node: '>=12'} @@ -16012,10 +16771,17 @@ packages: yargs-parser: 21.1.1 dev: true + /yjs/13.5.43: + resolution: {integrity: sha512-NJqWuiDOseYjkhnSVo55z+FZD6TsOJBZfMbH2I4OCm5vsgY7TESUjUGb7Pt1lljvvdSfBVj8CxQqZAnVxe5Iyg==} + dependencies: + lib0: 0.2.58 + dev: false + /yn/3.1.1: resolution: {integrity: sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==} engines: {node: '>=6'} dev: true + optional: true /yocto-queue/0.1.0: resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==}