@cyvest/cyvest-vis 3.1.0 → 4.0.0

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.
package/dist/index.js CHANGED
@@ -41,7 +41,7 @@ var import_react14 = require("react");
41
41
  var import_react7 = __toESM(require("react"));
42
42
  var import_react8 = require("@xyflow/react");
43
43
  var import_style = require("@xyflow/react/dist/style.css");
44
- var import_cyvest_js = require("@cyvest/cyvest-js");
44
+ var import_cyvest_js2 = require("@cyvest/cyvest-js");
45
45
 
46
46
  // src/types.ts
47
47
  var DEFAULT_FORCE_CONFIG = {
@@ -57,6 +57,7 @@ var import_react = require("react");
57
57
  var import_react2 = require("@xyflow/react");
58
58
 
59
59
  // src/utils/observables.ts
60
+ var import_cyvest_js = require("@cyvest/cyvest-js");
60
61
  var OBSERVABLE_EMOJI_MAP = {
61
62
  // Network
62
63
  "ipv4-addr": "\u{1F310}",
@@ -129,42 +130,22 @@ function truncateLabel(value, maxLength = 20, truncateMiddle = true) {
129
130
  return `${value.slice(0, maxLength - 1)}\u2026`;
130
131
  }
131
132
  function getLevelColor(level) {
132
- const colors = {
133
- NONE: "#6b7280",
134
- // gray-500
135
- TRUSTED: "#22c55e",
136
- // green-500
137
- INFO: "#3b82f6",
138
- // blue-500
139
- SAFE: "#22c55e",
140
- // green-500
141
- NOTABLE: "#eab308",
142
- // yellow-500
143
- SUSPICIOUS: "#f97316",
144
- // orange-500
145
- MALICIOUS: "#ef4444"
146
- // red-500
147
- };
148
- return colors[level] ?? colors.NONE;
133
+ return (0, import_cyvest_js.getColorForLevel)(level);
134
+ }
135
+ function lightenHexColor(hex, amount) {
136
+ const normalized = hex.startsWith("#") ? hex.slice(1) : hex;
137
+ if (normalized.length !== 6) {
138
+ return hex;
139
+ }
140
+ const r = parseInt(normalized.slice(0, 2), 16);
141
+ const g = parseInt(normalized.slice(2, 4), 16);
142
+ const b = parseInt(normalized.slice(4, 6), 16);
143
+ const mix = (channel) => Math.max(0, Math.min(255, Math.round(channel + (255 - channel) * amount)));
144
+ const toHex = (channel) => channel.toString(16).padStart(2, "0");
145
+ return `#${toHex(mix(r))}${toHex(mix(g))}${toHex(mix(b))}`;
149
146
  }
150
147
  function getLevelBackgroundColor(level) {
151
- const colors = {
152
- NONE: "#f3f4f6",
153
- // gray-100
154
- TRUSTED: "#dcfce7",
155
- // green-100
156
- INFO: "#dbeafe",
157
- // blue-100
158
- SAFE: "#dcfce7",
159
- // green-100
160
- NOTABLE: "#fef9c3",
161
- // yellow-100
162
- SUSPICIOUS: "#ffedd5",
163
- // orange-100
164
- MALICIOUS: "#fee2e2"
165
- // red-100
166
- };
167
- return colors[level] ?? colors.NONE;
148
+ return lightenHexColor(getLevelColor(level), 0.85);
168
149
  }
169
150
  var INVESTIGATION_NODE_EMOJI = {
170
151
  root: "\u{1F3AF}",
@@ -554,7 +535,7 @@ var edgeTypes = {
554
535
  floating: FloatingEdge
555
536
  };
556
537
  function createObservableNodes(investigation, rootObservableIds) {
557
- const graph = (0, import_cyvest_js.getObservableGraph)(investigation);
538
+ const graph = (0, import_cyvest_js2.getObservableGraph)(investigation);
558
539
  return graph.nodes.map((graphNode, index) => {
559
540
  const isRoot = rootObservableIds.has(graphNode.id);
560
541
  const shape = getObservableShape(graphNode.type, isRoot);
@@ -584,7 +565,7 @@ function createObservableNodes(investigation, rootObservableIds) {
584
565
  });
585
566
  }
586
567
  function createObservableEdges(investigation) {
587
- const graph = (0, import_cyvest_js.getObservableGraph)(investigation);
568
+ const graph = (0, import_cyvest_js2.getObservableGraph)(investigation);
588
569
  return graph.edges.map((graphEdge, index) => {
589
570
  const edgeData = {
590
571
  relationshipType: graphEdge.type,
@@ -794,19 +775,25 @@ var ObservablesGraphInner = ({
794
775
  };
795
776
  var ObservablesGraph = (props) => {
796
777
  const { investigation } = props;
797
- const rootObservables = (0, import_react7.useMemo)(() => {
798
- const roots = (0, import_cyvest_js.findRootObservables)(investigation);
799
- return new Set(roots.map((r) => r.key));
800
- }, [investigation]);
801
- const primaryRootId = (0, import_react7.useMemo)(() => {
802
- const roots = (0, import_cyvest_js.findRootObservables)(investigation);
803
- return roots.length > 0 ? roots[0].key : void 0;
778
+ const { rootKeys, primaryRootId } = (0, import_react7.useMemo)(() => {
779
+ const rootType = investigation.data_extraction.root_type;
780
+ if (!rootType) {
781
+ return { rootKeys: /* @__PURE__ */ new Set(), primaryRootId: void 0 };
782
+ }
783
+ const normalizedRootType = rootType.toLowerCase().trim();
784
+ const rootsByType = Object.values(investigation.observables).filter(
785
+ (obs) => obs.type.toLowerCase() === normalizedRootType
786
+ );
787
+ return {
788
+ rootKeys: new Set(rootsByType.map((obs) => obs.key)),
789
+ primaryRootId: rootsByType[0]?.key
790
+ };
804
791
  }, [investigation]);
805
792
  const { initialNodes, initialEdges } = (0, import_react7.useMemo)(() => {
806
- const nodes = createObservableNodes(investigation, rootObservables);
793
+ const nodes = createObservableNodes(investigation, rootKeys);
807
794
  const edges = createObservableEdges(investigation);
808
795
  return { initialNodes: nodes, initialEdges: edges };
809
- }, [investigation, rootObservables]);
796
+ }, [investigation, rootKeys]);
810
797
  return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react8.ReactFlowProvider, { children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
811
798
  ObservablesGraphInner,
812
799
  {
@@ -822,7 +809,6 @@ var ObservablesGraph = (props) => {
822
809
  var import_react12 = __toESM(require("react"));
823
810
  var import_react13 = require("@xyflow/react");
824
811
  var import_style2 = require("@xyflow/react/dist/style.css");
825
- var import_cyvest_js2 = require("@cyvest/cyvest-js");
826
812
 
827
813
  // src/components/InvestigationNode.tsx
828
814
  var import_react9 = require("react");
@@ -1028,10 +1014,14 @@ function flattenContainers(containers) {
1028
1014
  function createInvestigationGraph(investigation) {
1029
1015
  const nodes = [];
1030
1016
  const edges = [];
1031
- const roots = (0, import_cyvest_js2.findRootObservables)(investigation);
1032
- const primaryRoot = roots.length > 0 ? roots[0] : null;
1033
- const rootKey = primaryRoot?.key ?? "investigation-root";
1034
- const rootValue = primaryRoot?.value ?? "Investigation";
1017
+ const rootType = investigation.data_extraction.root_type;
1018
+ const normalizedRootType = rootType?.toLowerCase().trim();
1019
+ const rootsByType = normalizedRootType ? Object.values(investigation.observables).filter(
1020
+ (obs) => obs.type.toLowerCase() === normalizedRootType
1021
+ ) : [];
1022
+ const primaryRoot = rootsByType[0] ?? null;
1023
+ const rootKey = primaryRoot?.key ?? investigation.investigation_id;
1024
+ const rootValue = primaryRoot?.value ?? investigation.investigation_name ?? investigation.investigation_id;
1035
1025
  const rootLevel = primaryRoot?.level ?? investigation.level;
1036
1026
  const rootNodeData = {
1037
1027
  label: truncateLabel(rootValue, 24),
package/dist/index.mjs CHANGED
@@ -14,7 +14,7 @@ import {
14
14
  ConnectionMode
15
15
  } from "@xyflow/react";
16
16
  import "@xyflow/react/dist/style.css";
17
- import { getObservableGraph, findRootObservables } from "@cyvest/cyvest-js";
17
+ import { getObservableGraph } from "@cyvest/cyvest-js";
18
18
 
19
19
  // src/types.ts
20
20
  var DEFAULT_FORCE_CONFIG = {
@@ -30,6 +30,7 @@ import { memo } from "react";
30
30
  import { Handle, Position } from "@xyflow/react";
31
31
 
32
32
  // src/utils/observables.ts
33
+ import { getColorForLevel } from "@cyvest/cyvest-js";
33
34
  var OBSERVABLE_EMOJI_MAP = {
34
35
  // Network
35
36
  "ipv4-addr": "\u{1F310}",
@@ -102,42 +103,22 @@ function truncateLabel(value, maxLength = 20, truncateMiddle = true) {
102
103
  return `${value.slice(0, maxLength - 1)}\u2026`;
103
104
  }
104
105
  function getLevelColor(level) {
105
- const colors = {
106
- NONE: "#6b7280",
107
- // gray-500
108
- TRUSTED: "#22c55e",
109
- // green-500
110
- INFO: "#3b82f6",
111
- // blue-500
112
- SAFE: "#22c55e",
113
- // green-500
114
- NOTABLE: "#eab308",
115
- // yellow-500
116
- SUSPICIOUS: "#f97316",
117
- // orange-500
118
- MALICIOUS: "#ef4444"
119
- // red-500
120
- };
121
- return colors[level] ?? colors.NONE;
106
+ return getColorForLevel(level);
107
+ }
108
+ function lightenHexColor(hex, amount) {
109
+ const normalized = hex.startsWith("#") ? hex.slice(1) : hex;
110
+ if (normalized.length !== 6) {
111
+ return hex;
112
+ }
113
+ const r = parseInt(normalized.slice(0, 2), 16);
114
+ const g = parseInt(normalized.slice(2, 4), 16);
115
+ const b = parseInt(normalized.slice(4, 6), 16);
116
+ const mix = (channel) => Math.max(0, Math.min(255, Math.round(channel + (255 - channel) * amount)));
117
+ const toHex = (channel) => channel.toString(16).padStart(2, "0");
118
+ return `#${toHex(mix(r))}${toHex(mix(g))}${toHex(mix(b))}`;
122
119
  }
123
120
  function getLevelBackgroundColor(level) {
124
- const colors = {
125
- NONE: "#f3f4f6",
126
- // gray-100
127
- TRUSTED: "#dcfce7",
128
- // green-100
129
- INFO: "#dbeafe",
130
- // blue-100
131
- SAFE: "#dcfce7",
132
- // green-100
133
- NOTABLE: "#fef9c3",
134
- // yellow-100
135
- SUSPICIOUS: "#ffedd5",
136
- // orange-100
137
- MALICIOUS: "#fee2e2"
138
- // red-100
139
- };
140
- return colors[level] ?? colors.NONE;
121
+ return lightenHexColor(getLevelColor(level), 0.85);
141
122
  }
142
123
  var INVESTIGATION_NODE_EMOJI = {
143
124
  root: "\u{1F3AF}",
@@ -779,19 +760,25 @@ var ObservablesGraphInner = ({
779
760
  };
780
761
  var ObservablesGraph = (props) => {
781
762
  const { investigation } = props;
782
- const rootObservables = useMemo2(() => {
783
- const roots = findRootObservables(investigation);
784
- return new Set(roots.map((r) => r.key));
785
- }, [investigation]);
786
- const primaryRootId = useMemo2(() => {
787
- const roots = findRootObservables(investigation);
788
- return roots.length > 0 ? roots[0].key : void 0;
763
+ const { rootKeys, primaryRootId } = useMemo2(() => {
764
+ const rootType = investigation.data_extraction.root_type;
765
+ if (!rootType) {
766
+ return { rootKeys: /* @__PURE__ */ new Set(), primaryRootId: void 0 };
767
+ }
768
+ const normalizedRootType = rootType.toLowerCase().trim();
769
+ const rootsByType = Object.values(investigation.observables).filter(
770
+ (obs) => obs.type.toLowerCase() === normalizedRootType
771
+ );
772
+ return {
773
+ rootKeys: new Set(rootsByType.map((obs) => obs.key)),
774
+ primaryRootId: rootsByType[0]?.key
775
+ };
789
776
  }, [investigation]);
790
777
  const { initialNodes, initialEdges } = useMemo2(() => {
791
- const nodes = createObservableNodes(investigation, rootObservables);
778
+ const nodes = createObservableNodes(investigation, rootKeys);
792
779
  const edges = createObservableEdges(investigation);
793
780
  return { initialNodes: nodes, initialEdges: edges };
794
- }, [investigation, rootObservables]);
781
+ }, [investigation, rootKeys]);
795
782
  return /* @__PURE__ */ jsx3(ReactFlowProvider, { children: /* @__PURE__ */ jsx3(
796
783
  ObservablesGraphInner,
797
784
  {
@@ -814,7 +801,6 @@ import {
814
801
  useEdgesState as useEdgesState2
815
802
  } from "@xyflow/react";
816
803
  import "@xyflow/react/dist/style.css";
817
- import { findRootObservables as findRootObservables2 } from "@cyvest/cyvest-js";
818
804
 
819
805
  // src/components/InvestigationNode.tsx
820
806
  import { memo as memo3 } from "react";
@@ -1020,10 +1006,14 @@ function flattenContainers(containers) {
1020
1006
  function createInvestigationGraph(investigation) {
1021
1007
  const nodes = [];
1022
1008
  const edges = [];
1023
- const roots = findRootObservables2(investigation);
1024
- const primaryRoot = roots.length > 0 ? roots[0] : null;
1025
- const rootKey = primaryRoot?.key ?? "investigation-root";
1026
- const rootValue = primaryRoot?.value ?? "Investigation";
1009
+ const rootType = investigation.data_extraction.root_type;
1010
+ const normalizedRootType = rootType?.toLowerCase().trim();
1011
+ const rootsByType = normalizedRootType ? Object.values(investigation.observables).filter(
1012
+ (obs) => obs.type.toLowerCase() === normalizedRootType
1013
+ ) : [];
1014
+ const primaryRoot = rootsByType[0] ?? null;
1015
+ const rootKey = primaryRoot?.key ?? investigation.investigation_id;
1016
+ const rootValue = primaryRoot?.value ?? investigation.investigation_name ?? investigation.investigation_id;
1027
1017
  const rootLevel = primaryRoot?.level ?? investigation.level;
1028
1018
  const rootNodeData = {
1029
1019
  label: truncateLabel(rootValue, 24),
package/package.json CHANGED
@@ -1,27 +1,27 @@
1
1
  {
2
2
  "name": "@cyvest/cyvest-vis",
3
- "version": "3.1.0",
3
+ "version": "4.0.0",
4
4
  "main": "dist/index.cjs",
5
5
  "module": "dist/index.mjs",
6
6
  "types": "dist/index.d.ts",
7
7
  "sideEffects": false,
8
8
  "peerDependencies": {
9
- "react": "^18.0.0",
10
- "react-dom": "^18.0.0"
9
+ "react": "^19.0.0",
10
+ "react-dom": "^19.0.0"
11
11
  },
12
12
  "dependencies": {
13
- "@dagrejs/dagre": "^1.1.4",
14
- "@xyflow/react": "^12.5.0",
13
+ "@dagrejs/dagre": "^1.1.8",
14
+ "@xyflow/react": "^12.10.0",
15
15
  "d3-force": "^3.0.0",
16
- "@cyvest/cyvest-js": "3.1.0"
16
+ "@cyvest/cyvest-js": "4.0.0"
17
17
  },
18
18
  "devDependencies": {
19
19
  "@types/d3-force": "^3.0.10",
20
20
  "@types/react": "^19.2.7",
21
21
  "@types/react-dom": "^19.2.3",
22
- "tsup": "^8.0.0",
23
- "typescript": "^5.6.0",
24
- "vitest": "^2.0.0"
22
+ "tsup": "^8.5.1",
23
+ "typescript": "^5.9.3",
24
+ "vitest": "^4.0.16"
25
25
  },
26
26
  "engines": {
27
27
  "node": ">=20"
@@ -18,7 +18,6 @@ import {
18
18
  import "@xyflow/react/dist/style.css";
19
19
 
20
20
  import type { CyvestInvestigation, Check, Container } from "@cyvest/cyvest-js";
21
- import { findRootObservables } from "@cyvest/cyvest-js";
22
21
 
23
22
  import type {
24
23
  InvestigationGraphProps,
@@ -67,13 +66,21 @@ function createInvestigationGraph(
67
66
  const nodes: Node<InvestigationNodeData>[] = [];
68
67
  const edges: Edge[] = [];
69
68
 
70
- // Find root observable(s)
71
- const roots = findRootObservables(investigation);
72
- const primaryRoot = roots.length > 0 ? roots[0] : null;
69
+ const rootType = investigation.data_extraction.root_type;
70
+ const normalizedRootType = rootType?.toLowerCase().trim();
71
+ const rootsByType = normalizedRootType
72
+ ? Object.values(investigation.observables).filter(
73
+ (obs) => obs.type.toLowerCase() === normalizedRootType
74
+ )
75
+ : [];
76
+ const primaryRoot = rootsByType[0] ?? null;
73
77
 
74
78
  // If no root found, use the first observable or create a placeholder
75
- const rootKey = primaryRoot?.key ?? "investigation-root";
76
- const rootValue = primaryRoot?.value ?? "Investigation";
79
+ const rootKey = primaryRoot?.key ?? investigation.investigation_id;
80
+ const rootValue =
81
+ primaryRoot?.value ??
82
+ investigation.investigation_name ??
83
+ investigation.investigation_id;
77
84
  const rootLevel = primaryRoot?.level ?? investigation.level;
78
85
 
79
86
  // Create root node
@@ -21,7 +21,7 @@ import {
21
21
  import "@xyflow/react/dist/style.css";
22
22
 
23
23
  import type { CyvestInvestigation } from "@cyvest/cyvest-js";
24
- import { getObservableGraph, findRootObservables } from "@cyvest/cyvest-js";
24
+ import { getObservableGraph } from "@cyvest/cyvest-js";
25
25
 
26
26
  import type {
27
27
  ObservablesGraphProps,
@@ -344,24 +344,29 @@ const ObservablesGraphInner: React.FC<
344
344
  export const ObservablesGraph: React.FC<ObservablesGraphProps> = (props) => {
345
345
  const { investigation } = props;
346
346
 
347
- // Find root observables
348
- const rootObservables = useMemo(() => {
349
- const roots = findRootObservables(investigation);
350
- return new Set(roots.map((r) => r.key));
351
- }, [investigation]);
347
+ const { rootKeys, primaryRootId } = useMemo(() => {
348
+ const rootType = investigation.data_extraction.root_type;
349
+ if (!rootType) {
350
+ return { rootKeys: new Set<string>(), primaryRootId: undefined };
351
+ }
352
+
353
+ const normalizedRootType = rootType.toLowerCase().trim();
354
+ const rootsByType = Object.values(investigation.observables).filter(
355
+ (obs) => obs.type.toLowerCase() === normalizedRootType
356
+ );
352
357
 
353
- // Get first root for centering
354
- const primaryRootId = useMemo(() => {
355
- const roots = findRootObservables(investigation);
356
- return roots.length > 0 ? roots[0].key : undefined;
358
+ return {
359
+ rootKeys: new Set(rootsByType.map((obs) => obs.key)),
360
+ primaryRootId: rootsByType[0]?.key,
361
+ };
357
362
  }, [investigation]);
358
363
 
359
364
  // Create initial nodes and edges
360
365
  const { initialNodes, initialEdges } = useMemo(() => {
361
- const nodes = createObservableNodes(investigation, rootObservables);
366
+ const nodes = createObservableNodes(investigation, rootKeys);
362
367
  const edges = createObservableEdges(investigation);
363
368
  return { initialNodes: nodes, initialEdges: edges };
364
- }, [investigation, rootObservables]);
369
+ }, [investigation, rootKeys]);
365
370
 
366
371
  return (
367
372
  <ReactFlowProvider>
@@ -2,6 +2,7 @@
2
2
  * Utility functions for observable visualization.
3
3
  */
4
4
 
5
+ import { getColorForLevel, type Level } from "@cyvest/cyvest-js";
5
6
  import type { ObservableShape } from "../types";
6
7
 
7
8
  /**
@@ -118,33 +119,33 @@ export function truncateLabel(
118
119
  /**
119
120
  * Get color for security level.
120
121
  */
121
- export function getLevelColor(level: string): string {
122
- const colors: Record<string, string> = {
123
- NONE: "#6b7280", // gray-500
124
- TRUSTED: "#22c55e", // green-500
125
- INFO: "#3b82f6", // blue-500
126
- SAFE: "#22c55e", // green-500
127
- NOTABLE: "#eab308", // yellow-500
128
- SUSPICIOUS: "#f97316", // orange-500
129
- MALICIOUS: "#ef4444", // red-500
130
- };
131
- return colors[level] ?? colors.NONE;
122
+ export function getLevelColor(level: Level): string {
123
+ return getColorForLevel(level);
124
+ }
125
+
126
+ function lightenHexColor(hex: string, amount: number): string {
127
+ const normalized = hex.startsWith("#") ? hex.slice(1) : hex;
128
+ if (normalized.length !== 6) {
129
+ return hex;
130
+ }
131
+
132
+ const r = parseInt(normalized.slice(0, 2), 16);
133
+ const g = parseInt(normalized.slice(2, 4), 16);
134
+ const b = parseInt(normalized.slice(4, 6), 16);
135
+
136
+ const mix = (channel: number) =>
137
+ Math.max(0, Math.min(255, Math.round(channel + (255 - channel) * amount)));
138
+
139
+ const toHex = (channel: number) => channel.toString(16).padStart(2, "0");
140
+
141
+ return `#${toHex(mix(r))}${toHex(mix(g))}${toHex(mix(b))}`;
132
142
  }
133
143
 
134
144
  /**
135
145
  * Get background color for security level (lighter version).
136
146
  */
137
- export function getLevelBackgroundColor(level: string): string {
138
- const colors: Record<string, string> = {
139
- NONE: "#f3f4f6", // gray-100
140
- TRUSTED: "#dcfce7", // green-100
141
- INFO: "#dbeafe", // blue-100
142
- SAFE: "#dcfce7", // green-100
143
- NOTABLE: "#fef9c3", // yellow-100
144
- SUSPICIOUS: "#ffedd5", // orange-100
145
- MALICIOUS: "#fee2e2", // red-100
146
- };
147
- return colors[level] ?? colors.NONE;
147
+ export function getLevelBackgroundColor(level: Level): string {
148
+ return lightenHexColor(getLevelColor(level), 0.85);
148
149
  }
149
150
 
150
151
  /**
@@ -1,5 +1,6 @@
1
1
  import { describe, expect, it } from "vitest";
2
2
 
3
+ import { LEVEL_COLORS } from "@cyvest/cyvest-js";
3
4
  import {
4
5
  getInvestigationNodeEmoji,
5
6
  getLevelBackgroundColor,
@@ -29,11 +30,9 @@ describe("observables utils", () => {
29
30
  expect(truncateLabel("averyverylongvalue", 10, false)).toBe("averyvery…");
30
31
  });
31
32
 
32
- it("maps levels to colors and falls back to NONE", () => {
33
- expect(getLevelColor("SUSPICIOUS")).toBe("#f97316");
34
- expect(getLevelColor("UNKNOWN")).toBe("#6b7280");
35
- expect(getLevelBackgroundColor("SUSPICIOUS")).toBe("#ffedd5");
36
- expect(getLevelBackgroundColor("UNKNOWN")).toBe("#f3f4f6");
33
+ it("maps levels to colors", () => {
34
+ expect(getLevelColor("SUSPICIOUS")).toBe(LEVEL_COLORS.SUSPICIOUS);
35
+ expect(getLevelBackgroundColor("SUSPICIOUS")).toBe("#feeadc");
37
36
  });
38
37
 
39
38
  it("returns investigation node emoji with fallback", () => {
@@ -0,0 +1,14 @@
1
+ import { resolve } from "node:path";
2
+ import { defineConfig } from "vitest/config";
3
+
4
+ export default defineConfig({
5
+ resolve: {
6
+ alias: {
7
+ "@cyvest/cyvest-js": resolve(__dirname, "../cyvest-js/src/index.ts"),
8
+ },
9
+ },
10
+ test: {
11
+ include: ["tests/**/*.{test,spec}.{ts,js,tsx,jsx}"],
12
+ name: "cyvest-vis",
13
+ },
14
+ });