@prismatic-io/spectral 9.0.7 → 9.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -7,10 +7,10 @@ const getImports = ({ inputs }) => {
7
7
  return acc;
8
8
  }
9
9
  return Object.assign(Object.assign({}, acc), { [input.valueType.module]: acc[input.valueType.module]
10
- ? !acc[input.valueType.module].includes(input.valueType.type)
11
- ? [...acc[input.valueType.module], input.valueType.type]
10
+ ? !acc[input.valueType.module].includes(input.valueType.import)
11
+ ? [...acc[input.valueType.module], input.valueType.import]
12
12
  : acc[input.valueType.module]
13
- : [input.valueType.type] });
13
+ : [input.valueType.import] });
14
14
  }, {});
15
15
  };
16
16
  exports.getImports = getImports;
@@ -4,6 +4,11 @@ export type ServerTypeInput = InputBase & {
4
4
  onPremControlled?: boolean;
5
5
  shown?: boolean;
6
6
  };
7
+ type ValueType = string | {
8
+ type: string;
9
+ import: string;
10
+ module: string;
11
+ };
7
12
  export interface Input {
8
13
  key: string;
9
14
  label: string;
@@ -11,21 +16,16 @@ export interface Input {
11
16
  valueType: ValueType;
12
17
  docBlock: string;
13
18
  required: boolean | undefined;
19
+ default: ServerTypeInput["default"];
14
20
  }
15
- export type ValueType = string | {
16
- type: string;
17
- module: string;
18
- };
19
- export type DocBlock = {
20
- inputKey?: string;
21
- propertyKey: keyof ServerTypeInput;
22
- propertyValue?: unknown;
23
- output?: string;
24
- }[];
25
21
  interface GetInputsProps {
26
22
  inputs: ServerTypeInput[];
27
23
  docBlock?: (input: ServerTypeInput) => string;
28
24
  }
29
25
  export declare const getInputs: ({ inputs, docBlock }: GetInputsProps) => Input[];
30
- export declare const INPUT_TYPE_MAP: Record<InputFieldDefinition["type"], ValueType>;
26
+ type InputType = string | {
27
+ type: string;
28
+ module: string;
29
+ };
30
+ export declare const INPUT_TYPE_MAP: Record<InputFieldDefinition["type"], InputType>;
31
31
  export {};
@@ -2,6 +2,12 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.INPUT_TYPE_MAP = exports.getInputs = void 0;
4
4
  const docBlock_1 = require("./docBlock");
5
+ const getDefaultValue = (value) => {
6
+ if (value === undefined || value === "" || typeof value === "string") {
7
+ return value;
8
+ }
9
+ return JSON.stringify(value);
10
+ };
5
11
  const getInputs = ({ inputs, docBlock = docBlock_1.DOC_BLOCK_DEFAULT }) => {
6
12
  return inputs.reduce((acc, input) => {
7
13
  if ((typeof input.shown === "boolean" && input.shown === false) ||
@@ -20,6 +26,7 @@ const getInputs = ({ inputs, docBlock = docBlock_1.DOC_BLOCK_DEFAULT }) => {
20
26
  collection: input.collection,
21
27
  onPremControlled: input.onPremiseControlled || input.onPremControlled,
22
28
  docBlock: docBlock(input),
29
+ default: getDefaultValue(input.default),
23
30
  },
24
31
  ];
25
32
  }, []);
@@ -33,41 +40,50 @@ exports.INPUT_TYPE_MAP = {
33
40
  boolean: "boolean",
34
41
  code: "string",
35
42
  conditional: {
36
- type: "ConditionalExpression",
37
43
  module: "@prismatic-io/spectral",
44
+ type: "ConditionalExpression",
38
45
  },
39
46
  connection: {
40
- type: "Connection",
41
47
  module: "@prismatic-io/spectral",
48
+ type: "Connection",
42
49
  },
43
50
  objectSelection: {
44
- type: "ObjectSelection",
45
51
  module: "@prismatic-io/spectral",
52
+ type: "ObjectSelection",
46
53
  },
47
54
  objectFieldMap: {
48
- type: "ObjectFieldMap",
49
55
  module: "@prismatic-io/spectral",
56
+ type: "ObjectFieldMap",
50
57
  },
51
58
  jsonForm: {
52
- type: "JSONForm",
53
59
  module: "@prismatic-io/spectral",
60
+ type: "JSONForm",
54
61
  },
55
62
  dynamicObjectSelection: "string",
56
63
  dynamicFieldSelection: "string",
57
64
  };
58
65
  const getInputValueType = (input) => {
66
+ const inputType = exports.INPUT_TYPE_MAP[input.type];
59
67
  const valueType = input.model
60
68
  ? input.model
61
69
  .map((choice) => {
62
70
  return `\`${choice.value.replaceAll("\r", "\\r").replaceAll("\n", "\\n")}\``;
63
71
  })
64
72
  .join(" | ")
65
- : exports.INPUT_TYPE_MAP[input.type] || "never";
73
+ : inputType
74
+ ? typeof inputType === "string"
75
+ ? inputType
76
+ : Object.assign(Object.assign({}, inputType), { import: inputType.type })
77
+ : "never";
66
78
  if (input.collection === "keyvaluelist") {
67
- return `Record<string, ${valueType}> | Array<{key: string, value: ${valueType}}>`;
79
+ return typeof valueType === "string"
80
+ ? `Record<string, ${valueType}> | Array<{key: string, value: ${valueType}}>`
81
+ : Object.assign(Object.assign({}, valueType), { type: `Record<string, ${valueType.type}> | Array<{key: string, value: ${valueType.type}}>` });
68
82
  }
69
83
  if (input.collection === "valuelist") {
70
- return `${valueType}[]`;
84
+ return typeof valueType === "string"
85
+ ? `${valueType}[]`
86
+ : Object.assign(Object.assign({}, valueType), { type: `${valueType.type}[]` });
71
87
  }
72
88
  return valueType;
73
89
  };
@@ -6,8 +6,10 @@
6
6
  <%_ } else { -%>
7
7
  collection: undefined,
8
8
  <%_ } -%>
9
- <%_ if (input.default) { -%>
10
- default: <%= input.default %>,
9
+ <%_ if (typeof input.default !== 'undefined') { -%>
10
+ default: `<%- input.default %>`,
11
+ <%_ } else { -%>
12
+ default: undefined,
11
13
  <%_ } -%>
12
14
  <%_ if (input.required) { -%>
13
15
  required: <%= input.required %>,
@@ -18,7 +18,7 @@ const lodash_1 = require("lodash");
18
18
  const convertInput = (key, _a) => {
19
19
  var { default: defaultValue, type, label, collection } = _a, rest = __rest(_a, ["default", "type", "label", "collection"]);
20
20
  const keyLabel = collection === "keyvaluelist" && typeof label === "object" ? label.key : undefined;
21
- return Object.assign(Object.assign({}, (0, lodash_1.omit)(rest, ["onPremControlled"])), { key,
21
+ return Object.assign(Object.assign({}, (0, lodash_1.omit)(rest, ["onPremControlled", "permissionAndVisibilityType", "visibleToOrgDeployer"])), { key,
22
22
  type, default: defaultValue !== null && defaultValue !== void 0 ? defaultValue : types_1.InputFieldDefaultMap[type], collection, label: typeof label === "string" ? label : label.value, keyLabel, onPremiseControlled: ("onPremControlled" in rest && rest.onPremControlled) || undefined });
23
23
  };
24
24
  exports.convertInput = convertInput;
@@ -156,7 +156,7 @@ const convertConfigVarPermissionAndVisibility = ({ permissionAndVisibilityType,
156
156
  };
157
157
  };
158
158
  const convertComponentReference = (componentReference, componentRegistry, referenceType) => {
159
- var _a, _b, _c;
159
+ var _a, _b;
160
160
  const manifest = componentRegistry[componentReference.component];
161
161
  if (!manifest) {
162
162
  throw new Error(`Component with key "${componentReference.component}" not found in component registry.`);
@@ -174,8 +174,12 @@ const convertComponentReference = (componentReference, componentRegistry, refere
174
174
  // older versions of the manifest did not contain a key so we fall back to the componentReference key
175
175
  key: (_b = manifestEntry.key) !== null && _b !== void 0 ? _b : componentReference.key,
176
176
  };
177
- const inputs = Object.entries((_c = componentReference.values) !== null && _c !== void 0 ? _c : {}).reduce((result, [key, value]) => {
178
- const manifestEntryInput = manifestEntry.inputs[key];
177
+ const inputs = Object.entries(manifestEntry.inputs).reduce((result, [key, manifestEntryInput]) => {
178
+ var _a, _b, _c;
179
+ // Retrieve the input value or default to the manifest's default value
180
+ const value = (_b = (_a = componentReference.values) === null || _a === void 0 ? void 0 : _a[key]) !== null && _b !== void 0 ? _b : {
181
+ value: (_c = manifestEntryInput.default) !== null && _c !== void 0 ? _c : "",
182
+ };
179
183
  const type = manifestEntryInput.collection
180
184
  ? "complex"
181
185
  : "value" in value
@@ -186,10 +190,12 @@ const convertComponentReference = (componentReference, componentRegistry, refere
186
190
  ? Object.entries(value.value).map(([k, v]) => ({
187
191
  name: { type: "value", value: k },
188
192
  type: "value",
189
- value: v,
193
+ value: JSON.stringify(v),
190
194
  }))
191
195
  : value.value;
192
- const formattedValue = type === "complex" || typeof valueExpr === "string" ? valueExpr : JSON.stringify(valueExpr);
196
+ const formattedValue = type === "complex" || typeof valueExpr === "string"
197
+ ? valueExpr
198
+ : JSON.stringify(valueExpr);
193
199
  const meta = convertInputPermissionAndVisibility((0, lodash_1.pick)(value, ["permissionAndVisibilityType", "visibleToOrgDeployer"]));
194
200
  return Object.assign(Object.assign({}, result), { [key]: { type: type, value: formattedValue, meta } });
195
201
  }
@@ -320,10 +326,12 @@ const convertConfigVar = (key, configVar, referenceKey, componentRegistry) => {
320
326
  if (input.shown === false) {
321
327
  return result;
322
328
  }
329
+ const meta = convertInputPermissionAndVisibility((0, lodash_1.pick)(input, ["permissionAndVisibilityType", "visibleToOrgDeployer"]));
323
330
  const defaultValue = input.collection ? [] : "";
324
331
  return Object.assign(Object.assign({}, result), { [key]: {
325
332
  type: input.collection ? "complex" : "value",
326
333
  value: input.default || defaultValue,
334
+ meta,
327
335
  } });
328
336
  }, {}),
329
337
  orgOnly,
@@ -402,12 +410,15 @@ const convertOnExecution = (onExecution, componentRegistry) => (context, params)
402
410
  // Construct the component methods from the component registry
403
411
  const componentMethods = Object.entries(componentRegistry).reduce((accumulator, [registryComponentKey, { key: componentKey, actions, public: isPublic, signature }]) => {
404
412
  const componentActions = Object.entries(actions).reduce((actionsAccumulator, [registryActionKey, action]) => {
413
+ const manifestActions = componentRegistry[registryComponentKey].actions[registryActionKey];
405
414
  // Define the method to be called for the action
406
415
  const invokeAction = (values) => __awaiter(void 0, void 0, void 0, function* () {
407
416
  var _a;
408
- // Transform the input values based on the action's inputs
409
- const transformedValues = Object.entries(values).reduce((transformedAccumulator, [inputKey, inputValue]) => {
410
- const { collection } = action.inputs[inputKey];
417
+ // Apply defaults directly within the transformation process
418
+ const transformedValues = Object.entries(manifestActions.inputs).reduce((transformedAccumulator, [inputKey, inputValueBase]) => {
419
+ var _a;
420
+ const inputValue = (_a = values[inputKey]) !== null && _a !== void 0 ? _a : inputValueBase.default;
421
+ const { collection } = inputValueBase;
411
422
  return Object.assign(Object.assign({}, transformedAccumulator), { [inputKey]: convertInputValue(inputValue, collection) });
412
423
  }, {});
413
424
  // Invoke the action with the transformed values
@@ -1,5 +1,5 @@
1
- import { ComponentManifest, PermissionAndVisibilityType } from ".";
2
- import { Prettify, UnionToIntersection } from "./utils";
1
+ import type { ComponentManifest, ConfigVarVisibility } from ".";
2
+ import type { Prettify, UnionToIntersection } from "./utils";
3
3
  /**
4
4
  * Root ComponentRegistry type exposed for augmentation.
5
5
  *
@@ -19,18 +19,6 @@ export type ComponentRegistry = keyof IntegrationDefinitionComponentRegistry ext
19
19
  } : UnionToIntersection<keyof IntegrationDefinitionComponentRegistry extends infer TComponentKey ? TComponentKey extends keyof IntegrationDefinitionComponentRegistry ? {
20
20
  [Key in TComponentKey]: IntegrationDefinitionComponentRegistry[TComponentKey];
21
21
  } : never : never>;
22
- export interface ConnectionInputPermissionAndVisibility {
23
- /**
24
- * Optional value that sets the permission and visibility of the Config Var. @default "customer"
25
- *
26
- * "customer" - Customers can view and edit the Config Var.
27
- * "embedded" - Customers cannot view or update the Config Var as the value will be set programmatically.
28
- * "organization" - Customers cannot view or update the Config Var as it will always have a default value or be set by the organization.
29
- */
30
- permissionAndVisibilityType?: PermissionAndVisibilityType;
31
- /** Optional value that specifies whether this Config Var is visible to an Organization deployer. @default true */
32
- visibleToOrgDeployer?: boolean;
33
- }
34
22
  export type ConfigVarExpression = {
35
23
  configVar: string;
36
24
  };
@@ -42,7 +30,7 @@ type ComponentReferenceTypeValueMap<TValue, TMap extends Record<ComponentReferen
42
30
  actions: ValueExpression<TValue>;
43
31
  triggers: ValueExpression<TValue> | ConfigVarExpression;
44
32
  dataSources: ValueExpression<TValue> | ConfigVarExpression;
45
- connections: (ValueExpression<TValue> | ConfigVarExpression) & ConnectionInputPermissionAndVisibility;
33
+ connections: (ValueExpression<TValue> | ConfigVarExpression) & ConfigVarVisibility;
46
34
  }> = TMap;
47
35
  export type ComponentReference<TComponentReference extends {
48
36
  component: string;
@@ -1,5 +1,5 @@
1
- import { DataSourceDefinition, ConnectionDefinition, Inputs, DataSourceType, Connection, JSONForm, ObjectFieldMap, ObjectSelection, ConfigVarResultCollection, Schedule, CollectionDataSourceType, DataSourceReference, ConfigPage, ConfigPages, ConfigPageElement, ComponentRegistryDataSource, ComponentRegistryConnection, UserLevelConfigPages } from ".";
2
- import { Prettify, UnionToIntersection } from "./utils";
1
+ import { type DataSourceDefinition, type ConnectionDefinition, type Inputs, type DataSourceType, type Connection, type JSONForm, type ObjectFieldMap, type ObjectSelection, type ConfigVarResultCollection, type Schedule, type CollectionDataSourceType, type DataSourceReference, type ConfigPage, type ConfigPages, type ConfigPageElement, type ComponentRegistryDataSource, type ComponentRegistryConnection, type UserLevelConfigPages } from ".";
2
+ import type { Prettify, UnionToIntersection } from "./utils";
3
3
  /** Supported data types for Config Vars. */
4
4
  export type ConfigVarDataType = "string" | "date" | "timestamp" | "picklist" | "code" | "boolean" | "number" | "schedule" | "objectSelection" | "objectFieldMap" | "jsonForm";
5
5
  type ConfigVarDataTypeDefaultValueMap<TMap extends Record<ConfigVarDataType, unknown> = {
@@ -30,14 +30,9 @@ type ConfigVarDataTypeRuntimeValueMap<TMap extends Record<ConfigVarDataType, unk
30
30
  }> = TMap;
31
31
  /** Choices of collection types for multi-value Config Vars. */
32
32
  export type CollectionType = "valuelist" | "keyvaluelist";
33
- export type PermissionAndVisibilityType = "customer" | "embedded" | "organization";
34
33
  type ConfigVarSingleDataType = Extract<ConfigVarDataType, "schedule" | "objectSelection" | "objectFieldMap" | "jsonForm">;
35
- /** Common attribute shared by all types of Config Vars. */
36
- type BaseConfigVar = {
37
- /** A unique, unchanging value that is used to maintain identity for the Config Var even if the key changes. */
38
- stableKey: string;
39
- /** Optional description for this Config Var. */
40
- description?: string;
34
+ export type PermissionAndVisibilityType = "customer" | "embedded" | "organization";
35
+ export interface ConfigVarVisibility {
41
36
  /**
42
37
  * Optional value that sets the permission and visibility of the Config Var. @default "customer"
43
38
  *
@@ -48,7 +43,26 @@ type BaseConfigVar = {
48
43
  permissionAndVisibilityType?: PermissionAndVisibilityType;
49
44
  /** Optional value that specifies whether this Config Var is visible to an Organization deployer. @default true */
50
45
  visibleToOrgDeployer?: boolean;
51
- };
46
+ }
47
+ interface ConfigVarInputVisibility {
48
+ /**
49
+ * Optional value that sets the permission and visibility of the Config Var Input. @default "customer"
50
+ *
51
+ * "customer" - Customers can view and edit the Config Var Input.
52
+ * "embedded" - Customers cannot view or update the Config Var Input as the value will be set programmatically.
53
+ * "organization" - Customers cannot view or update the Config Var Input as it will always have a default value or be set by the organization.
54
+ */
55
+ permissionAndVisibilityType?: PermissionAndVisibilityType;
56
+ /** Optional value that specifies whether this Config Var Input is visible to an Organization deployer. @default true */
57
+ visibleToOrgDeployer?: boolean;
58
+ }
59
+ /** Common attribute shared by all types of Config Vars. */
60
+ type BaseConfigVar = {
61
+ /** A unique, unchanging value that is used to maintain identity for the Config Var even if the key changes. */
62
+ stableKey: string;
63
+ /** Optional description for this Config Var. */
64
+ description?: string;
65
+ } & ConfigVarVisibility;
52
66
  type GetDynamicProperties<TValue, TCollectionType extends CollectionType | undefined> = TCollectionType extends undefined ? {
53
67
  defaultValue?: TValue;
54
68
  /** Optional value to specify the type of collection if the Config Var is multi-value. */
@@ -115,7 +129,11 @@ export type DataSourceConfigVar = DataSourceDefinitionConfigVar | DataSourceRefe
115
129
  type BaseConnectionConfigVar = BaseConfigVar & {
116
130
  dataType: "connection";
117
131
  };
118
- type ConnectionDefinitionConfigVar = BaseConnectionConfigVar & Omit<ConnectionDefinition, "label" | "comments" | "key">;
132
+ type ConnectionDefinitionConfigVar = ConnectionDefinition extends infer TConnectionDefinitionType extends ConnectionDefinition ? TConnectionDefinitionType extends infer TConnectionDefinition extends ConnectionDefinition ? BaseConnectionConfigVar & Omit<TConnectionDefinition, "inputs" | "label" | "comments" | "key"> & {
133
+ inputs: {
134
+ [Key in keyof TConnectionDefinition["inputs"]]: TConnectionDefinition["inputs"][Key] & ConfigVarInputVisibility;
135
+ };
136
+ } : never : never;
119
137
  type OnPremiseConnectionConfigTypeEnum = "allowed" | "disallowed" | "required";
120
138
  type ConnectionReferenceConfigVar = ComponentRegistryConnection extends infer TConnectionReference ? TConnectionReference extends ComponentRegistryConnection ? BaseConnectionConfigVar & {
121
139
  connection: TConnectionReference["reference"] & ("onPremAvailable" extends keyof TConnectionReference ? TConnectionReference["onPremAvailable"] extends true ? {
@@ -1,4 +1,4 @@
1
- import { ConnectionInput, OnPremConnectionInput } from ".";
1
+ import type { ConnectionInput, OnPremConnectionInput } from ".";
2
2
  export declare enum OAuth2Type {
3
3
  ClientCredentials = "client_credentials",
4
4
  AuthorizationCode = "authorization_code"
@@ -26,8 +26,13 @@ export interface OnPremConnectionDefinition extends BaseConnectionDefinition {
26
26
  [key: string]: ConnectionInput;
27
27
  };
28
28
  }
29
+ interface OAuth2Config {
30
+ overrideGrantType?: string;
31
+ allowedTokenParams?: string[];
32
+ }
29
33
  interface OAuth2AuthorizationCodeConnectionDefinition extends BaseConnectionDefinition {
30
34
  oauth2Type: OAuth2Type.AuthorizationCode;
35
+ oauth2Config?: OAuth2Config;
31
36
  /** The PKCE method (S256 or plain) that this OAuth 2.0 connection uses (if any) */
32
37
  oauth2PkceMethod?: OAuth2PkceMethod;
33
38
  inputs: {
@@ -1,4 +1,4 @@
1
- import { ActionPerformFunction, ActionPerformReturn, TriggerEventFunction, TriggerPerformFunction, Inputs, TriggerResult, TriggerPayload, ComponentRegistry, TriggerReference, ConfigVars, ConfigPages, UserLevelConfigPages, ValueExpression, ConfigVarExpression } from ".";
1
+ import { ActionPerformFunction, ActionPerformReturn, TriggerEventFunction, TriggerPerformFunction, Inputs, TriggerResult, TriggerPayload, ComponentRegistry, TriggerReference, ConfigVars, ConfigPages, UserLevelConfigPages, ValueExpression, ConfigVarExpression, ActionContext } from ".";
2
2
  /** Defines attributes of a Code-Native Integration. */
3
3
  export type IntegrationDefinition = {
4
4
  /** The unique name for this Integration. */
@@ -30,6 +30,21 @@ export type IntegrationDefinition = {
30
30
  userLevelConfigPages?: UserLevelConfigPages;
31
31
  componentRegistry?: ComponentRegistry;
32
32
  };
33
+ export type FlowOnExecution<TTriggerPayload extends TriggerPayload> = ActionPerformFunction<{
34
+ onTrigger: {
35
+ type: "data";
36
+ label: string;
37
+ clean: (value: unknown) => {
38
+ results: TTriggerPayload;
39
+ };
40
+ };
41
+ }, ConfigVars, {
42
+ [Key in keyof ComponentRegistry]: ComponentRegistry[Key]["actions"];
43
+ }, false, ActionPerformReturn<false, unknown>>;
44
+ export type FlowExecutionContext = ActionContext<ConfigVars, {
45
+ [Key in keyof ComponentRegistry]: ComponentRegistry[Key]["actions"];
46
+ }>;
47
+ export type FlowExecutionContextActions = FlowExecutionContext["components"];
33
48
  /** Defines attributes of a Flow of a Code-Native Integration. */
34
49
  export interface Flow<TTriggerPayload extends TriggerPayload = TriggerPayload> {
35
50
  /** The unique name for this Flow. */
@@ -61,17 +76,7 @@ export interface Flow<TTriggerPayload extends TriggerPayload = TriggerPayload> {
61
76
  /** Specifies the function to execute when an Instance of an Integration is deleted. */
62
77
  onInstanceDelete?: TriggerEventFunction<Inputs, ConfigVars>;
63
78
  /** Specifies the main function for this Flow */
64
- onExecution: ActionPerformFunction<{
65
- onTrigger: {
66
- type: "data";
67
- label: string;
68
- clean: (value: unknown) => {
69
- results: TTriggerPayload;
70
- };
71
- };
72
- }, ConfigVars, {
73
- [Key in keyof ComponentRegistry]: ComponentRegistry[Key]["actions"];
74
- }, false, ActionPerformReturn<false, unknown>>;
79
+ onExecution: FlowOnExecution<TTriggerPayload>;
75
80
  }
76
81
  /** Defines attributes of a Preprocess Flow Configuration used by a Flow of an Integration. */
77
82
  export type PreprocessFlowConfig = {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@prismatic-io/spectral",
3
- "version": "9.0.7",
3
+ "version": "9.1.1",
4
4
  "description": "Utility library for building Prismatic components",
5
5
  "keywords": ["prismatic"],
6
6
  "main": "dist/index.js",
@@ -39,7 +39,7 @@
39
39
  "files": ["dist/"],
40
40
  "dependencies": {
41
41
  "@jsonforms/core": "3.0.0",
42
- "axios": "1.6.2",
42
+ "axios": "1.7.4",
43
43
  "axios-retry": "3.9.1",
44
44
  "date-fns": "2.30.0",
45
45
  "ejs": "^3.1.10",