testeranto 0.233.2 → 0.233.4

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/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "name": "testeranto",
3
3
  "displayName": "Testeranto",
4
4
  "description": "the Dockerized, AI powered, BDD test framework for polyglot projects",
5
- "version": "0.233.2",
5
+ "version": "0.233.4",
6
6
  "engines": {
7
7
  "node": ">=20.19.0",
8
8
  "vscode": "^1.60.0",
@@ -345,7 +345,6 @@
345
345
  "url": "^0.11.4",
346
346
  "uuid": "^10.0.0",
347
347
  "validate-with-xmllint": "^1.2.1",
348
- "viz": "^0.1.0",
349
348
  "ws": "^8.18.3",
350
349
  "xmldom": "^0.6.0",
351
350
  "xterm": "^5.3.0",
@@ -2,14 +2,14 @@ this file documents how test results can be visualized.
2
2
 
3
3
  testeranto includes user-defined visualizations, in the form of react components passed via configuration.
4
4
 
5
- The viz library is a set of react components to assist that. At it's core, it takes graph data, and projects it onto a 2d space, similar to d3. The goal is to provide the right hooks to allow the creation of multiple popular charts.
5
+ The grafeovidajo library is a set of react components to assist that. At it's core, it takes graph data, and projects it onto a 2d space, similar to d3. The goal is to provide the right hooks to allow the creation of multiple popular charts.
6
6
 
7
7
  - eishhower matrix
8
8
  - gantt chart
9
9
  - kanan board
10
10
  - trees and graphs
11
11
 
12
- given 2 attributes on the nodes and some customizations, the viz library project your graph data onto the 2d screen.
12
+ given 2 attributes on the nodes and some customizations, the grafeovidajo library project your graph data onto the 2d screen.
13
13
 
14
14
  First, an x and y attribute are chosen
15
15
  Second, if those attributes are a set or continuous
@@ -46,18 +46,18 @@ interface Edge {
46
46
  interface ProjectionConfig {
47
47
  xAttribute: string;
48
48
  yAttribute: string;
49
- xType: 'categorical' | 'continuous' | 'ordinal' | 'temporal';
50
- yType: 'categorical' | 'continuous' | 'ordinal' | 'temporal';
51
-
52
- layout?: 'grid' | 'force' | 'tree' | 'timeline';
49
+ xType: "categorical" | "continuous" | "ordinal" | "temporal";
50
+ yType: "categorical" | "continuous" | "ordinal" | "temporal";
51
+
52
+ layout?: "grid" | "force" | "tree" | "timeline";
53
53
  spacing?: {
54
54
  x: number;
55
55
  y: number;
56
56
  };
57
-
57
+
58
58
  xDomain?: [min: number, max: number] | string[];
59
59
  yDomain?: [min: number, max: number] | string[];
60
-
60
+
61
61
  xTransform?: (value: any) => number;
62
62
  yTransform?: (value: any) => number;
63
63
  }
@@ -66,7 +66,7 @@ interface ProjectionConfig {
66
66
  interface StyleConfig {
67
67
  nodeSize: number | ((node: Node) => number);
68
68
  nodeColor: string | ((node: Node) => string);
69
- nodeShape: 'circle' | 'square' | 'diamond' | ((node: Node) => string);
69
+ nodeShape: "circle" | "square" | "diamond" | ((node: Node) => string);
70
70
  edgeColor?: string;
71
71
  edgeWidth?: number;
72
72
  labels?: {
@@ -94,8 +94,8 @@ interface VizConfig {
94
94
  ```typescript
95
95
  // Project graph data to screen coordinates
96
96
  function projectGraph(
97
- graph: GraphData,
98
- config: ProjectionConfig
97
+ graph: GraphData,
98
+ config: ProjectionConfig,
99
99
  ): ProjectedGraph;
100
100
 
101
101
  interface ProjectedGraph {
@@ -115,15 +115,25 @@ interface ProjectedNode extends Node {
115
115
  }
116
116
 
117
117
  // Layout algorithms
118
- function layoutGrid(nodes: ProjectedNode[], spacing: {x: number, y: number}): ProjectedNode[];
118
+ function layoutGrid(
119
+ nodes: ProjectedNode[],
120
+ spacing: { x: number; y: number },
121
+ ): ProjectedNode[];
119
122
  function layoutForce(nodes: ProjectedNode[], edges?: Edge[]): ProjectedNode[];
120
- function layoutTree(nodes: ProjectedNode[], edges: Edge[], rootId?: string): ProjectedNode[];
121
- function layoutTimeline(nodes: ProjectedNode[], timeAttribute: string): ProjectedNode[];
123
+ function layoutTree(
124
+ nodes: ProjectedNode[],
125
+ edges: Edge[],
126
+ rootId?: string,
127
+ ): ProjectedNode[];
128
+ function layoutTimeline(
129
+ nodes: ProjectedNode[],
130
+ timeAttribute: string,
131
+ ): ProjectedNode[];
122
132
 
123
133
  // Apply styles to projected graph
124
134
  function applyStyles(
125
135
  projectedGraph: ProjectedGraph,
126
- styleConfig: StyleConfig
136
+ styleConfig: StyleConfig,
127
137
  ): StyledGraph;
128
138
 
129
139
  interface StyledGraph extends ProjectedGraph {
@@ -154,10 +164,10 @@ interface VizComponentProps {
154
164
  // Eisenhower Matrix
155
165
  interface EisenhowerConfig extends VizConfig {
156
166
  quadrants: {
157
- urgentImportant: { x: [0, 0.5], y: [0, 0.5] };
158
- notUrgentImportant: { x: [0.5, 1], y: [0, 0.5] };
159
- urgentNotImportant: { x: [0, 0.5], y: [0.5, 1] };
160
- notUrgentNotImportant: { x: [0.5, 1], y: [0.5, 1] };
167
+ urgentImportant: { x: [0, 0.5]; y: [0, 0.5] };
168
+ notUrgentImportant: { x: [0.5, 1]; y: [0, 0.5] };
169
+ urgentNotImportant: { x: [0, 0.5]; y: [0.5, 1] };
170
+ notUrgentNotImportant: { x: [0.5, 1]; y: [0.5, 1] };
161
171
  };
162
172
  }
163
173
 
@@ -181,7 +191,7 @@ interface KanbanConfig extends VizConfig {
181
191
  // Tree/Graph
182
192
  interface TreeConfig extends VizConfig {
183
193
  rootId?: string;
184
- orientation: 'horizontal' | 'vertical';
194
+ orientation: "horizontal" | "vertical";
185
195
  nodeSeparation: number;
186
196
  levelSeparation: number;
187
197
  }
@@ -221,7 +231,7 @@ const eisenhowerConfig: EisenhowerConfig = {
221
231
  };
222
232
 
223
233
  // Usage in React component
224
- <EisenhowerMatrix
234
+ <EisenhowerMatrix
225
235
  data={testResultsGraph}
226
236
  config={eisenhowerConfig}
227
237
  width={800}
@@ -237,4 +247,7 @@ const eisenhowerConfig: EisenhowerConfig = {
237
247
  3. **Extensible Layouts**: Easy to add new layout algorithms
238
248
  4. **Responsive Design**: Works with any screen size
239
249
  5. **Interactive**: Built-in support for hover, click, drag interactions
250
+
251
+ ```
252
+
240
253
  ```
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "grafeovidajo",
3
- "version": "0.0.1",
3
+ "version": "0.0.4",
4
4
  "description": "Visualization library for Testeranto",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -50,4 +50,4 @@
50
50
  "files": [
51
51
  "dist"
52
52
  ]
53
- }
53
+ }
@@ -318,7 +318,7 @@ export class Server_HTTP_utils {
318
318
  fileContents: await this.extractFileContentsFromTree(collatedFilesTree),
319
319
  // Add feature graph for visualization
320
320
  featureGraph: featureGraph,
321
- // Add viz configuration
321
+ // Add grafeovidajo configuration
322
322
  vizConfig: {
323
323
  projection: {
324
324
  xAttribute: 'status',
@@ -353,7 +353,7 @@ export class Server_HTTP_utils {
353
353
  const jsonString = JSON.stringify(embeddedData);
354
354
  // Encode to base64 to avoid any HTML/JS escaping issues
355
355
  const base64String = Buffer.from(jsonString, 'utf8').toString('base64');
356
-
356
+
357
357
  // Create a script tag that decodes the base64 string
358
358
  const scriptTag = `<script>
359
359
  window.TESTERANTO_EMBEDDED_DATA = JSON.parse(atob('${base64String}'));
@@ -466,20 +466,20 @@ window.TESTERANTO_EMBEDDED_DATA = JSON.parse(atob('${base64String}'));
466
466
  // Try to read and parse markdown file
467
467
  let frontmatter: Record<string, any> = {};
468
468
  let content: string | undefined;
469
-
469
+
470
470
  try {
471
471
  const fullPath = path.join(process.cwd(), normalizedPath);
472
472
  if (fs.existsSync(fullPath)) {
473
473
  const fileContent = await fs.promises.readFile(fullPath, 'utf-8');
474
474
  content = fileContent;
475
-
475
+
476
476
  // Parse markdown frontmatter
477
477
  const match = fileContent.match(/^---\s*\n([\s\S]*?)\n---\s*\n([\s\S]*)$/);
478
478
  if (match) {
479
479
  const [, frontmatterStr] = match;
480
480
  frontmatter = this.parseYamlFrontmatter(frontmatterStr);
481
481
  }
482
-
482
+
483
483
  // Create feature node for graph
484
484
  const nodeId = `feature:${normalizedPath}`;
485
485
  featureNodes.push({
@@ -492,13 +492,13 @@ window.TESTERANTO_EMBEDDED_DATA = JSON.parse(atob('${base64String}'));
492
492
  _feature: feature
493
493
  }
494
494
  });
495
-
495
+
496
496
  // Add edges for dependencies
497
497
  if (frontmatter.dependsUpon) {
498
- const dependencies = Array.isArray(frontmatter.dependsUpon)
499
- ? frontmatter.dependsUpon
498
+ const dependencies = Array.isArray(frontmatter.dependsUpon)
499
+ ? frontmatter.dependsUpon
500
500
  : [frontmatter.dependsUpon];
501
-
501
+
502
502
  for (const dep of dependencies) {
503
503
  let depPath = dep;
504
504
  if (depPath.startsWith('./')) {
@@ -508,7 +508,7 @@ window.TESTERANTO_EMBEDDED_DATA = JSON.parse(atob('${base64String}'));
508
508
  depPath = depPath.substring(1);
509
509
  }
510
510
  depPath = depPath.replace(/\\/g, '/');
511
-
511
+
512
512
  const depNodeId = `feature:${depPath}`;
513
513
  featureEdges.push({
514
514
  source: depNodeId,
@@ -544,7 +544,7 @@ window.TESTERANTO_EMBEDDED_DATA = JSON.parse(atob('${base64String}'));
544
544
  feature,
545
545
  isDocumentation: false,
546
546
  });
547
-
547
+
548
548
  // Create node for plain feature
549
549
  const nodeId = `feature:plain:${feature.replace(/[^a-zA-Z0-9]/g, '_')}`;
550
550
  featureNodes.push({
@@ -583,16 +583,16 @@ window.TESTERANTO_EMBEDDED_DATA = JSON.parse(atob('${base64String}'));
583
583
  try {
584
584
  const result: Record<string, any> = {};
585
585
  const lines = yamlStr.split('\n');
586
-
586
+
587
587
  for (const line of lines) {
588
588
  const trimmed = line.trim();
589
589
  if (!trimmed || trimmed.startsWith('#')) continue;
590
-
590
+
591
591
  const colonIndex = trimmed.indexOf(':');
592
592
  if (colonIndex > 0) {
593
593
  const key = trimmed.substring(0, colonIndex).trim();
594
594
  let value = trimmed.substring(colonIndex + 1).trim();
595
-
595
+
596
596
  // Try to parse values
597
597
  if (value === 'true') value = true;
598
598
  else if (value === 'false') value = false;
@@ -610,11 +610,11 @@ window.TESTERANTO_EMBEDDED_DATA = JSON.parse(atob('${base64String}'));
610
610
  // Keep as string
611
611
  }
612
612
  }
613
-
613
+
614
614
  result[key] = value;
615
615
  }
616
616
  }
617
-
617
+
618
618
  return result;
619
619
  } catch (error) {
620
620
  console.warn('Failed to parse YAML frontmatter:', error);
@@ -854,7 +854,7 @@ window.TESTERANTO_EMBEDDED_DATA = JSON.parse(atob('${base64String}'));
854
854
 
855
855
  private static async readDocumentationFiles(files: string[]): Promise<Record<string, string>> {
856
856
  const contents: Record<string, string> = {};
857
-
857
+
858
858
  for (const file of files) {
859
859
  try {
860
860
  const fullPath = path.join(process.cwd(), file);
@@ -866,16 +866,16 @@ window.TESTERANTO_EMBEDDED_DATA = JSON.parse(atob('${base64String}'));
866
866
  console.warn(`Could not read documentation file ${file}:`, error);
867
867
  }
868
868
  }
869
-
869
+
870
870
  return contents;
871
871
  }
872
872
 
873
873
  private static async extractFileContentsFromTree(tree: any): Promise<Record<string, string>> {
874
874
  const contents: Record<string, string> = {};
875
-
875
+
876
876
  const traverse = async (node: any, currentPath: string = '') => {
877
877
  if (!node) return;
878
-
878
+
879
879
  if (node.type === 'file' && node.path) {
880
880
  try {
881
881
  const fullPath = path.join(process.cwd(), node.path);
@@ -887,14 +887,14 @@ window.TESTERANTO_EMBEDDED_DATA = JSON.parse(atob('${base64String}'));
887
887
  console.warn(`Could not read file ${node.path}:`, error);
888
888
  }
889
889
  }
890
-
890
+
891
891
  if (node.children) {
892
892
  for (const [key, child] of Object.entries(node.children)) {
893
893
  await traverse(child, currentPath ? `${currentPath}/${key}` : key);
894
894
  }
895
895
  }
896
896
  };
897
-
897
+
898
898
  await traverse(tree);
899
899
  return contents;
900
900
  }
package/src/viz.md CHANGED
@@ -22,9 +22,9 @@ dependsUpon: ./someOtherFeature.md
22
22
 
23
23
  This graph is precomputed and embedded in the html. The front end presents it. WE can still use fetch command, so we do not necessarily need to embed the file contents int the graph, only theirilfenames.
24
24
 
25
- ## viz
25
+ ## grafeovidajo
26
26
 
27
- the viz library works by mapping graph data to 2d plain. We need to achieve the falling charts
27
+ the grafeovidajo library works by mapping graph data to 2d plain. We need to achieve the falling charts
28
28
 
29
29
  - esenhow matric
30
30
  - gantt
@@ -1,115 +0,0 @@
1
- import React, { useMemo, useCallback } from 'react';
2
- import { GraphData, VizComponentProps, Node } from 'viz';
3
- import { MarkdownChartProps } from './types';
4
- import { useMarkdownFiles } from './hooks/useMarkdownFiles';
5
- import { useChartSync } from './hooks/useChartSync';
6
- import { markdownFilesToGraphData } from './markdown/parser';
7
-
8
- export const MarkdownChart: React.FC<MarkdownChartProps> = ({
9
- filePattern,
10
- watchFiles = false,
11
- chartComponent: ChartComponent,
12
- chartConfig,
13
- attributeMapping,
14
- onFileChange,
15
- onNodeUpdate,
16
- width,
17
- height
18
- }) => {
19
- const {
20
- files,
21
- loading,
22
- error,
23
- updateFile
24
- } = useMarkdownFiles(filePattern, watchFiles);
25
-
26
- const { handleNodeUpdate, handleNodeDrag } = useChartSync(
27
- files,
28
- updateFile,
29
- attributeMapping
30
- );
31
-
32
- // Convert markdown files to graph data
33
- const graphData = useMemo<GraphData>(() => {
34
- return markdownFilesToGraphData(files, attributeMapping);
35
- }, [files, attributeMapping]);
36
-
37
- // Handle node interactions
38
- const handleNodeClick = useCallback((node: Node) => {
39
- // Could open the markdown file for editing
40
- console.log('Node clicked:', node);
41
- }, []);
42
-
43
- const handleNodeHover = useCallback((node: Node | null) => {
44
- // Could show tooltip with file info
45
- console.log('Node hover:', node);
46
- }, []);
47
-
48
- const handleNodeInteraction = useCallback(async (
49
- node: Node,
50
- interactionType: 'drag' | 'attributeChange',
51
- data: any
52
- ) => {
53
- try {
54
- if (interactionType === 'drag') {
55
- await handleNodeDrag(node, data);
56
- } else if (interactionType === 'attributeChange') {
57
- await handleNodeUpdate(node, data);
58
- }
59
-
60
- // Notify parent component
61
- onNodeUpdate?.(node, data);
62
- } catch (error) {
63
- console.error('Failed to handle node interaction:', error);
64
- }
65
- }, [handleNodeDrag, handleNodeUpdate, onNodeUpdate]);
66
-
67
- if (loading) {
68
- return <div>Loading markdown files...</div>;
69
- }
70
-
71
- if (error) {
72
- return <div>Error loading files: {error.message}</div>;
73
- }
74
-
75
- if (files.length === 0) {
76
- return <div>No markdown files found matching pattern: {filePattern}</div>;
77
- }
78
-
79
- // Enhance chart config with attribute mapping
80
- const enhancedConfig = {
81
- ...chartConfig,
82
- projection: {
83
- ...chartConfig.projection,
84
- xAttribute: attributeMapping.xAttribute || chartConfig.projection.xAttribute,
85
- yAttribute: attributeMapping.yAttribute || chartConfig.projection.yAttribute
86
- }
87
- };
88
-
89
- return (
90
- <div className="markdown-chart-container">
91
- <div className="chart-header">
92
- <h3>Visualizing {files.length} markdown files</h3>
93
- <div className="file-info">
94
- Files will be updated as you interact with the chart
95
- </div>
96
- </div>
97
-
98
- <ChartComponent
99
- data={graphData}
100
- config={enhancedConfig}
101
- width={width}
102
- height={height}
103
- onNodeClick={handleNodeClick}
104
- onNodeHover={handleNodeHover}
105
- // Additional props for drag interactions could be passed here
106
- />
107
-
108
- <div className="chart-footer">
109
- <small>
110
- Each node represents a markdown file. Drag nodes to update their attributes.
111
- </small>
112
- </div>
113
- </div>
114
- );
115
- };
@@ -1,66 +0,0 @@
1
- import { useCallback } from 'react';
2
- import { Node } from 'viz';
3
- import { MarkdownFile } from '../types';
4
-
5
- export function useChartSync(
6
- files: MarkdownFile[],
7
- updateFile: (filePath: string, updates: Record<string, any>) => Promise<MarkdownFile>,
8
- attributeMapping: { idAttribute: string; xAttribute?: string; yAttribute?: string }
9
- ) {
10
- const handleNodeUpdate = useCallback(async (
11
- node: Node,
12
- updates: Record<string, any>
13
- ) => {
14
- const filePath = node.attributes._path;
15
- if (!filePath) {
16
- console.warn('Node has no associated file path:', node.id);
17
- return;
18
- }
19
-
20
- // Remove internal attributes from updates
21
- const cleanUpdates = { ...updates };
22
- delete cleanUpdates._path;
23
- delete cleanUpdates._body;
24
-
25
- try {
26
- await updateFile(filePath, cleanUpdates);
27
- } catch (error) {
28
- console.error('Failed to update file:', error);
29
- throw error;
30
- }
31
- }, [updateFile]);
32
-
33
- const handleNodeDrag = useCallback(async (
34
- node: Node,
35
- newPosition: { x: number; y: number }
36
- ) => {
37
- // Map screen coordinates back to attribute values
38
- // This depends on the projection configuration
39
- const updates: Record<string, any> = {};
40
-
41
- if (attributeMapping.xAttribute) {
42
- // This is a simplification - actual mapping would depend on projection config
43
- updates[attributeMapping.xAttribute] = newPosition.x;
44
- }
45
-
46
- if (attributeMapping.yAttribute) {
47
- updates[attributeMapping.yAttribute] = newPosition.y;
48
- }
49
-
50
- return handleNodeUpdate(node, updates);
51
- }, [handleNodeUpdate, attributeMapping]);
52
-
53
- const handleNodeAttributeChange = useCallback(async (
54
- node: Node,
55
- attribute: string,
56
- value: any
57
- ) => {
58
- return handleNodeUpdate(node, { [attribute]: value });
59
- }, [handleNodeUpdate]);
60
-
61
- return {
62
- handleNodeUpdate,
63
- handleNodeDrag,
64
- handleNodeAttributeChange
65
- };
66
- }
@@ -1,84 +0,0 @@
1
- import { useState, useEffect, useCallback } from 'react';
2
- import { MarkdownFile, FileSystemAdapter } from '../types';
3
- import { parseMarkdownFile, updateMarkdownFile } from '../markdown/parser';
4
-
5
- // Default file system adapter (can be overridden)
6
- const defaultFileSystemAdapter: FileSystemAdapter = {
7
- async readFiles(pattern: string | string[]): Promise<MarkdownFile[]> {
8
- // This would be implemented with actual file system access
9
- // For now, return empty array - implementation depends on environment
10
- console.warn('Default file system adapter not implemented');
11
- return [];
12
- },
13
-
14
- async writeFile(file: MarkdownFile): Promise<void> {
15
- console.warn('Default file system adapter not implemented');
16
- },
17
-
18
- watchFiles(pattern: string | string[], callback: (files: MarkdownFile[]) => void): () => void {
19
- console.warn('Default file system adapter not implemented');
20
- return () => {};
21
- }
22
- };
23
-
24
- export function useMarkdownFiles(
25
- filePattern: string | string[],
26
- watchFiles: boolean = false,
27
- fileSystemAdapter: FileSystemAdapter = defaultFileSystemAdapter
28
- ) {
29
- const [files, setFiles] = useState<MarkdownFile[]>([]);
30
- const [loading, setLoading] = useState(true);
31
- const [error, setError] = useState<Error | null>(null);
32
-
33
- const loadFiles = useCallback(async () => {
34
- try {
35
- setLoading(true);
36
- const fileContents = await fileSystemAdapter.readFiles(filePattern);
37
- setFiles(fileContents);
38
- setError(null);
39
- } catch (err) {
40
- setError(err instanceof Error ? err : new Error('Failed to load files'));
41
- } finally {
42
- setLoading(false);
43
- }
44
- }, [filePattern, fileSystemAdapter]);
45
-
46
- const updateFile = useCallback(async (
47
- filePath: string,
48
- updates: Record<string, any>
49
- ) => {
50
- const file = files.find(f => f.path === filePath);
51
- if (!file) throw new Error(`File not found: ${filePath}`);
52
-
53
- const updatedFile = updateMarkdownFile(file, updates);
54
-
55
- await fileSystemAdapter.writeFile(updatedFile);
56
-
57
- // Update local state
58
- setFiles(prev => prev.map(f =>
59
- f.path === filePath ? updatedFile : f
60
- ));
61
-
62
- return updatedFile;
63
- }, [files, fileSystemAdapter]);
64
-
65
- useEffect(() => {
66
- loadFiles();
67
-
68
- if (watchFiles) {
69
- const unwatch = fileSystemAdapter.watchFiles(filePattern, (newFiles) => {
70
- setFiles(newFiles);
71
- });
72
-
73
- return unwatch;
74
- }
75
- }, [filePattern, watchFiles, fileSystemAdapter, loadFiles]);
76
-
77
- return {
78
- files,
79
- loading,
80
- error,
81
- reload: loadFiles,
82
- updateFile
83
- };
84
- }
@@ -1,10 +0,0 @@
1
- export { MarkdownChart } from './MarkdownChart';
2
- export { useMarkdownFiles } from './hooks/useMarkdownFiles';
3
- export { useChartSync } from './hooks/useChartSync';
4
- export { parseMarkdownFile, markdownFilesToGraphData, updateMarkdownFile } from './markdown/parser';
5
-
6
- export type {
7
- MarkdownChartProps,
8
- MarkdownFile,
9
- FileSystemAdapter
10
- } from './types';
@@ -1,115 +0,0 @@
1
- import { MarkdownFile } from '../types';
2
- import { GraphData } from 'viz';
3
-
4
- const FRONTMATTER_REGEX = /^---\s*\n([\s\S]*?)\n---\s*\n([\s\S]*)$/;
5
-
6
- export function parseMarkdownFile(path: string, content: string): MarkdownFile {
7
- const match = content.match(FRONTMATTER_REGEX);
8
-
9
- if (match) {
10
- const [, frontmatterStr, body] = match;
11
- const frontmatter = parseYAML(frontmatterStr);
12
-
13
- return {
14
- path,
15
- content,
16
- frontmatter,
17
- body: body.trim()
18
- };
19
- }
20
-
21
- // No frontmatter - treat entire content as body
22
- return {
23
- path,
24
- content,
25
- frontmatter: {},
26
- body: content.trim()
27
- };
28
- }
29
-
30
- function parseYAML(yamlStr: string): Record<string, any> {
31
- try {
32
- // Simple YAML parser for basic key-value pairs
33
- const result: Record<string, any> = {};
34
- const lines = yamlStr.split('\n');
35
-
36
- for (const line of lines) {
37
- const trimmed = line.trim();
38
- if (!trimmed || trimmed.startsWith('#')) continue;
39
-
40
- const colonIndex = trimmed.indexOf(':');
41
- if (colonIndex > 0) {
42
- const key = trimmed.substring(0, colonIndex).trim();
43
- let value = trimmed.substring(colonIndex + 1).trim();
44
-
45
- // Try to parse values
46
- if (value === 'true') value = true;
47
- else if (value === 'false') value = false;
48
- else if (value === 'null') value = null;
49
- else if (!isNaN(Number(value)) && value !== '') value = Number(value);
50
- else if (value.startsWith('"') && value.endsWith('"')) {
51
- value = value.slice(1, -1);
52
- } else if (value.startsWith("'") && value.endsWith("'")) {
53
- value = value.slice(1, -1);
54
- }
55
-
56
- result[key] = value;
57
- }
58
- }
59
-
60
- return result;
61
- } catch (error) {
62
- console.warn('Failed to parse YAML frontmatter:', error);
63
- return {};
64
- }
65
- }
66
-
67
- export function markdownFilesToGraphData(
68
- files: MarkdownFile[],
69
- attributeMapping: { idAttribute: string; xAttribute?: string; yAttribute?: string }
70
- ): GraphData {
71
- const nodes = files.map(file => {
72
- const id = file.frontmatter[attributeMapping.idAttribute] ||
73
- file.path.split('/').pop()?.replace('.md', '') ||
74
- `node-${Math.random().toString(36).substr(2, 9)}`;
75
-
76
- return {
77
- id,
78
- attributes: {
79
- ...file.frontmatter,
80
- _path: file.path,
81
- _body: file.body
82
- }
83
- };
84
- });
85
-
86
- return { nodes };
87
- }
88
-
89
- export function updateMarkdownFile(
90
- file: MarkdownFile,
91
- updatedAttributes: Record<string, any>
92
- ): MarkdownFile {
93
- const newFrontmatter = { ...file.frontmatter, ...updatedAttributes };
94
-
95
- // Remove internal attributes that shouldn't be in frontmatter
96
- delete newFrontmatter._path;
97
- delete newFrontmatter._body;
98
-
99
- // Convert frontmatter back to YAML
100
- const frontmatterLines = Object.entries(newFrontmatter)
101
- .map(([key, value]) => {
102
- if (value === null || value === undefined) return `${key}: null`;
103
- if (typeof value === 'string') return `${key}: "${value}"`;
104
- if (typeof value === 'boolean') return `${key}: ${value}`;
105
- return `${key}: ${value}`;
106
- });
107
-
108
- const newContent = `---\n${frontmatterLines.join('\n')}\n---\n\n${file.body}`;
109
-
110
- return {
111
- ...file,
112
- content: newContent,
113
- frontmatter: newFrontmatter
114
- };
115
- }
@@ -1,40 +0,0 @@
1
- import { GraphData, Node, VizConfig, VizComponentProps } from 'viz';
2
-
3
- export interface MarkdownFile {
4
- path: string;
5
- content: string;
6
- frontmatter: Record<string, any>;
7
- body: string;
8
- }
9
-
10
- export interface MarkdownChartProps {
11
- // File system configuration
12
- filePattern: string | string[]; // Glob pattern(s) for markdown files
13
- watchFiles?: boolean; // Watch for file changes
14
-
15
- // Chart configuration
16
- chartComponent: React.ComponentType<VizComponentProps>;
17
- chartConfig: VizConfig;
18
-
19
- // Attribute mapping
20
- attributeMapping: {
21
- idAttribute: string; // Which frontmatter field becomes node.id
22
- xAttribute?: string; // Maps to projection.xAttribute
23
- yAttribute?: string; // Maps to projection.yAttribute
24
- // Additional attribute mappings can be inferred from frontmatter
25
- };
26
-
27
- // Callbacks
28
- onFileChange?: (file: MarkdownFile) => void;
29
- onNodeUpdate?: (node: Node, oldAttributes: Record<string, any>) => void;
30
-
31
- // Display
32
- width: number;
33
- height: number;
34
- }
35
-
36
- export interface FileSystemAdapter {
37
- readFiles(pattern: string | string[]): Promise<MarkdownFile[]>;
38
- writeFile(file: MarkdownFile): Promise<void>;
39
- watchFiles(pattern: string | string[], callback: (files: MarkdownFile[]) => void): () => void;
40
- }