@marktoflow/gui 2.0.0-alpha.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.
Files changed (165) hide show
  1. package/.turbo/turbo-build.log +26 -0
  2. package/.turbo/turbo-test.log +22 -0
  3. package/README.md +179 -0
  4. package/dist/client/assets/index-DwTI8opO.js +608 -0
  5. package/dist/client/assets/index-DwTI8opO.js.map +1 -0
  6. package/dist/client/assets/index-RoEdL6gO.css +1 -0
  7. package/dist/client/index.html +20 -0
  8. package/dist/client/vite.svg +9 -0
  9. package/dist/server/index.d.ts +3 -0
  10. package/dist/server/index.d.ts.map +1 -0
  11. package/dist/server/index.js +56 -0
  12. package/dist/server/index.js.map +1 -0
  13. package/dist/server/routes/ai.js +50 -0
  14. package/dist/server/routes/ai.js.map +1 -0
  15. package/dist/server/routes/execute.js +62 -0
  16. package/dist/server/routes/execute.js.map +1 -0
  17. package/dist/server/routes/workflows.js +99 -0
  18. package/dist/server/routes/workflows.js.map +1 -0
  19. package/dist/server/server/index.js +95 -0
  20. package/dist/server/server/index.js.map +1 -0
  21. package/dist/server/server/routes/ai.js +87 -0
  22. package/dist/server/server/routes/ai.js.map +1 -0
  23. package/dist/server/server/routes/execute.js +63 -0
  24. package/dist/server/server/routes/execute.js.map +1 -0
  25. package/dist/server/server/routes/tools.js +518 -0
  26. package/dist/server/server/routes/tools.js.map +1 -0
  27. package/dist/server/server/routes/workflows.js +99 -0
  28. package/dist/server/server/routes/workflows.js.map +1 -0
  29. package/dist/server/server/services/AIService.js +69 -0
  30. package/dist/server/server/services/AIService.js.map +1 -0
  31. package/dist/server/server/services/FileWatcher.js +60 -0
  32. package/dist/server/server/services/FileWatcher.js.map +1 -0
  33. package/dist/server/server/services/WorkflowService.js +363 -0
  34. package/dist/server/server/services/WorkflowService.js.map +1 -0
  35. package/dist/server/server/services/agents/claude-code-provider.js +250 -0
  36. package/dist/server/server/services/agents/claude-code-provider.js.map +1 -0
  37. package/dist/server/server/services/agents/claude-provider.js +204 -0
  38. package/dist/server/server/services/agents/claude-provider.js.map +1 -0
  39. package/dist/server/server/services/agents/copilot-provider.js +227 -0
  40. package/dist/server/server/services/agents/copilot-provider.js.map +1 -0
  41. package/dist/server/server/services/agents/demo-provider.js +167 -0
  42. package/dist/server/server/services/agents/demo-provider.js.map +1 -0
  43. package/dist/server/server/services/agents/index.js +31 -0
  44. package/dist/server/server/services/agents/index.js.map +1 -0
  45. package/dist/server/server/services/agents/ollama-provider.js +220 -0
  46. package/dist/server/server/services/agents/ollama-provider.js.map +1 -0
  47. package/dist/server/server/services/agents/prompts.js +436 -0
  48. package/dist/server/server/services/agents/prompts.js.map +1 -0
  49. package/dist/server/server/services/agents/registry.js +242 -0
  50. package/dist/server/server/services/agents/registry.js.map +1 -0
  51. package/dist/server/server/services/agents/types.js +6 -0
  52. package/dist/server/server/services/agents/types.js.map +1 -0
  53. package/dist/server/server/websocket/index.js +85 -0
  54. package/dist/server/server/websocket/index.js.map +1 -0
  55. package/dist/server/services/AIService.d.ts +30 -0
  56. package/dist/server/services/AIService.d.ts.map +1 -0
  57. package/dist/server/services/AIService.js +216 -0
  58. package/dist/server/services/AIService.js.map +1 -0
  59. package/dist/server/services/FileWatcher.d.ts +10 -0
  60. package/dist/server/services/FileWatcher.d.ts.map +1 -0
  61. package/dist/server/services/FileWatcher.js +62 -0
  62. package/dist/server/services/FileWatcher.js.map +1 -0
  63. package/dist/server/services/WorkflowService.d.ts +54 -0
  64. package/dist/server/services/WorkflowService.d.ts.map +1 -0
  65. package/dist/server/services/WorkflowService.js +323 -0
  66. package/dist/server/services/WorkflowService.js.map +1 -0
  67. package/dist/server/shared/constants.js +175 -0
  68. package/dist/server/shared/constants.js.map +1 -0
  69. package/dist/server/shared/types.js +3 -0
  70. package/dist/server/shared/types.js.map +1 -0
  71. package/dist/server/websocket/index.d.ts +10 -0
  72. package/dist/server/websocket/index.d.ts.map +1 -0
  73. package/dist/server/websocket/index.js +85 -0
  74. package/dist/server/websocket/index.js.map +1 -0
  75. package/index.html +19 -0
  76. package/package.json +96 -0
  77. package/playwright.config.ts +27 -0
  78. package/postcss.config.js +6 -0
  79. package/public/vite.svg +9 -0
  80. package/src/client/App.tsx +520 -0
  81. package/src/client/components/Canvas/Canvas.tsx +405 -0
  82. package/src/client/components/Canvas/ExecutionOverlay.tsx +847 -0
  83. package/src/client/components/Canvas/NodeContextMenu.tsx +188 -0
  84. package/src/client/components/Canvas/OutputNode.tsx +111 -0
  85. package/src/client/components/Canvas/StepNode.tsx +106 -0
  86. package/src/client/components/Canvas/SubWorkflowNode.tsx +141 -0
  87. package/src/client/components/Canvas/Toolbar.tsx +189 -0
  88. package/src/client/components/Canvas/TriggerNode.tsx +128 -0
  89. package/src/client/components/Editor/InputsEditor.tsx +458 -0
  90. package/src/client/components/Editor/NewStepWizard.tsx +344 -0
  91. package/src/client/components/Editor/StepEditor.tsx +532 -0
  92. package/src/client/components/Editor/YamlEditor.tsx +160 -0
  93. package/src/client/components/Panels/PropertiesPanel.tsx +589 -0
  94. package/src/client/components/Prompt/ChangePreview.tsx +281 -0
  95. package/src/client/components/Prompt/PromptHistoryPanel.tsx +209 -0
  96. package/src/client/components/Prompt/PromptInput.tsx +108 -0
  97. package/src/client/components/Sidebar/Sidebar.tsx +343 -0
  98. package/src/client/components/common/Breadcrumb.tsx +40 -0
  99. package/src/client/components/common/Button.tsx +68 -0
  100. package/src/client/components/common/ContextMenu.tsx +202 -0
  101. package/src/client/components/common/KeyboardShortcuts.tsx +143 -0
  102. package/src/client/components/common/Modal.tsx +93 -0
  103. package/src/client/components/common/Tabs.tsx +57 -0
  104. package/src/client/components/common/ThemeToggle.tsx +63 -0
  105. package/src/client/components/index.ts +32 -0
  106. package/src/client/hooks/index.ts +4 -0
  107. package/src/client/hooks/useAIPrompt.ts +108 -0
  108. package/src/client/hooks/useCanvas.ts +247 -0
  109. package/src/client/hooks/useWebSocket.ts +164 -0
  110. package/src/client/hooks/useWorkflow.ts +138 -0
  111. package/src/client/main.tsx +10 -0
  112. package/src/client/stores/canvasStore.ts +348 -0
  113. package/src/client/stores/editorStore.ts +133 -0
  114. package/src/client/stores/executionStore.ts +440 -0
  115. package/src/client/stores/index.ts +4 -0
  116. package/src/client/stores/layoutStore.ts +103 -0
  117. package/src/client/stores/navigationStore.ts +49 -0
  118. package/src/client/stores/promptStore.ts +113 -0
  119. package/src/client/stores/themeStore.ts +75 -0
  120. package/src/client/stores/workflowStore.ts +177 -0
  121. package/src/client/styles/globals.css +346 -0
  122. package/src/client/utils/cn.ts +9 -0
  123. package/src/client/utils/index.ts +4 -0
  124. package/src/client/utils/serviceIcons.tsx +64 -0
  125. package/src/client/utils/stepValidation.ts +155 -0
  126. package/src/client/utils/workflowToGraph.ts +299 -0
  127. package/src/server/index.ts +114 -0
  128. package/src/server/routes/ai.ts +91 -0
  129. package/src/server/routes/execute.ts +71 -0
  130. package/src/server/routes/tools.ts +564 -0
  131. package/src/server/routes/workflows.ts +106 -0
  132. package/src/server/services/AIService.ts +105 -0
  133. package/src/server/services/FileWatcher.ts +69 -0
  134. package/src/server/services/WorkflowService.ts +441 -0
  135. package/src/server/services/agents/claude-code-provider.ts +320 -0
  136. package/src/server/services/agents/claude-provider.ts +248 -0
  137. package/src/server/services/agents/copilot-provider.ts +311 -0
  138. package/src/server/services/agents/demo-provider.ts +184 -0
  139. package/src/server/services/agents/index.ts +31 -0
  140. package/src/server/services/agents/ollama-provider.ts +267 -0
  141. package/src/server/services/agents/prompts.ts +482 -0
  142. package/src/server/services/agents/registry.ts +289 -0
  143. package/src/server/services/agents/types.ts +146 -0
  144. package/src/server/websocket/index.ts +104 -0
  145. package/src/shared/constants.ts +180 -0
  146. package/src/shared/types.ts +179 -0
  147. package/tailwind.config.ts +73 -0
  148. package/tests/e2e/app.spec.ts +90 -0
  149. package/tests/e2e/canvas.spec.ts +128 -0
  150. package/tests/e2e/workflow.spec.ts +185 -0
  151. package/tests/integration/api.test.ts +250 -0
  152. package/tests/integration/testApp.ts +31 -0
  153. package/tests/setup.ts +37 -0
  154. package/tests/unit/canvasStore.test.ts +502 -0
  155. package/tests/unit/components.test.tsx +151 -0
  156. package/tests/unit/executionStore.test.ts +527 -0
  157. package/tests/unit/layoutStore.test.ts +194 -0
  158. package/tests/unit/navigationStore.test.ts +152 -0
  159. package/tests/unit/stepValidation.test.ts +226 -0
  160. package/tests/unit/themeStore.test.ts +141 -0
  161. package/tests/unit/workflowToGraph.test.ts +289 -0
  162. package/tsconfig.json +29 -0
  163. package/tsconfig.server.json +28 -0
  164. package/vite.config.ts +31 -0
  165. package/vitest.config.ts +26 -0
@@ -0,0 +1,141 @@
1
+ import { describe, it, expect, beforeEach, vi } from 'vitest';
2
+ import { useThemeStore } from '../../src/client/stores/themeStore';
3
+
4
+ // Mock localStorage
5
+ const localStorageMock = (() => {
6
+ let store: Record<string, string> = {};
7
+ return {
8
+ getItem: (key: string) => store[key] || null,
9
+ setItem: (key: string, value: string) => { store[key] = value; },
10
+ removeItem: (key: string) => { delete store[key]; },
11
+ clear: () => { store = {}; },
12
+ };
13
+ })();
14
+
15
+ Object.defineProperty(window, 'localStorage', { value: localStorageMock });
16
+
17
+ // Mock matchMedia
18
+ Object.defineProperty(window, 'matchMedia', {
19
+ value: vi.fn().mockImplementation(query => ({
20
+ matches: query === '(prefers-color-scheme: dark)',
21
+ media: query,
22
+ onchange: null,
23
+ addListener: vi.fn(),
24
+ removeListener: vi.fn(),
25
+ addEventListener: vi.fn(),
26
+ removeEventListener: vi.fn(),
27
+ dispatchEvent: vi.fn(),
28
+ })),
29
+ });
30
+
31
+ describe('themeStore', () => {
32
+ beforeEach(() => {
33
+ // Reset store state
34
+ useThemeStore.setState({
35
+ theme: 'dark',
36
+ resolvedTheme: 'dark',
37
+ });
38
+ localStorageMock.clear();
39
+
40
+ // Reset document classes
41
+ document.documentElement.classList.remove('dark', 'light');
42
+ });
43
+
44
+ describe('initial state', () => {
45
+ it('should default to dark theme', () => {
46
+ const state = useThemeStore.getState();
47
+ expect(state.theme).toBe('dark');
48
+ expect(state.resolvedTheme).toBe('dark');
49
+ });
50
+ });
51
+
52
+ describe('setTheme', () => {
53
+ it('should set theme to light', () => {
54
+ const { setTheme } = useThemeStore.getState();
55
+
56
+ setTheme('light');
57
+
58
+ const state = useThemeStore.getState();
59
+ expect(state.theme).toBe('light');
60
+ expect(state.resolvedTheme).toBe('light');
61
+ });
62
+
63
+ it('should set theme to dark', () => {
64
+ const { setTheme } = useThemeStore.getState();
65
+
66
+ setTheme('light');
67
+ setTheme('dark');
68
+
69
+ const state = useThemeStore.getState();
70
+ expect(state.theme).toBe('dark');
71
+ expect(state.resolvedTheme).toBe('dark');
72
+ });
73
+
74
+ it('should resolve system theme', () => {
75
+ const { setTheme } = useThemeStore.getState();
76
+
77
+ setTheme('system');
78
+
79
+ const state = useThemeStore.getState();
80
+ expect(state.theme).toBe('system');
81
+ // Our mock returns dark for prefers-color-scheme: dark
82
+ expect(state.resolvedTheme).toBe('dark');
83
+ });
84
+
85
+ it('should apply dark class to document', () => {
86
+ const { setTheme } = useThemeStore.getState();
87
+
88
+ setTheme('dark');
89
+
90
+ expect(document.documentElement.classList.contains('dark')).toBe(true);
91
+ expect(document.documentElement.classList.contains('light')).toBe(false);
92
+ });
93
+
94
+ it('should apply light class to document', () => {
95
+ const { setTheme } = useThemeStore.getState();
96
+
97
+ setTheme('light');
98
+
99
+ expect(document.documentElement.classList.contains('light')).toBe(true);
100
+ expect(document.documentElement.classList.contains('dark')).toBe(false);
101
+ });
102
+ });
103
+
104
+ describe('toggleTheme', () => {
105
+ it('should toggle from dark to light', () => {
106
+ const { toggleTheme } = useThemeStore.getState();
107
+
108
+ toggleTheme();
109
+
110
+ const state = useThemeStore.getState();
111
+ expect(state.theme).toBe('light');
112
+ expect(state.resolvedTheme).toBe('light');
113
+ });
114
+
115
+ it('should toggle from light to dark', () => {
116
+ const { setTheme, toggleTheme } = useThemeStore.getState();
117
+
118
+ setTheme('light');
119
+ toggleTheme();
120
+
121
+ const state = useThemeStore.getState();
122
+ expect(state.theme).toBe('dark');
123
+ expect(state.resolvedTheme).toBe('dark');
124
+ });
125
+
126
+ it('should apply correct classes on toggle', () => {
127
+ const { setTheme, toggleTheme } = useThemeStore.getState();
128
+
129
+ setTheme('dark');
130
+ expect(document.documentElement.classList.contains('dark')).toBe(true);
131
+
132
+ toggleTheme();
133
+ expect(document.documentElement.classList.contains('light')).toBe(true);
134
+ expect(document.documentElement.classList.contains('dark')).toBe(false);
135
+
136
+ toggleTheme();
137
+ expect(document.documentElement.classList.contains('dark')).toBe(true);
138
+ expect(document.documentElement.classList.contains('light')).toBe(false);
139
+ });
140
+ });
141
+ });
@@ -0,0 +1,289 @@
1
+ import { describe, it, expect } from 'vitest';
2
+ import { workflowToGraph, graphToWorkflow } from '../../src/client/utils/workflowToGraph';
3
+
4
+ describe('workflowToGraph', () => {
5
+ describe('basic conversion', () => {
6
+ it('should convert a simple workflow to nodes and edges', () => {
7
+ const workflow = {
8
+ metadata: { id: 'test-1', name: 'Test Workflow' },
9
+ steps: [
10
+ { id: 'step-1', name: 'Step 1', action: 'http.get', inputs: {} },
11
+ { id: 'step-2', name: 'Step 2', action: 'http.post', inputs: {} },
12
+ ],
13
+ };
14
+
15
+ const { nodes, edges } = workflowToGraph(workflow);
16
+
17
+ // Should have 2 step nodes + 1 output node
18
+ expect(nodes).toHaveLength(3);
19
+ expect(nodes[0].id).toBe('step-1');
20
+ expect(nodes[0].type).toBe('step');
21
+ expect(nodes[1].id).toBe('step-2');
22
+ expect(nodes[1].type).toBe('step');
23
+ expect(nodes[2].type).toBe('output');
24
+
25
+ // Should have edge between steps and to output
26
+ expect(edges.length).toBeGreaterThanOrEqual(2);
27
+ expect(edges.find(e => e.source === 'step-1' && e.target === 'step-2')).toBeDefined();
28
+ });
29
+
30
+ it('should create nodes with correct data', () => {
31
+ const workflow = {
32
+ metadata: { id: 'test-1', name: 'Test Workflow' },
33
+ steps: [
34
+ {
35
+ id: 'fetch',
36
+ name: 'Fetch Data',
37
+ action: 'github.pulls.get',
38
+ inputs: { owner: 'test' },
39
+ outputVariable: 'pr_data',
40
+ },
41
+ ],
42
+ };
43
+
44
+ const { nodes } = workflowToGraph(workflow);
45
+ const stepNode = nodes.find(n => n.id === 'fetch');
46
+
47
+ expect(stepNode?.data.id).toBe('fetch');
48
+ expect(stepNode?.data.name).toBe('Fetch Data');
49
+ expect(stepNode?.data.action).toBe('github.pulls.get');
50
+ expect(stepNode?.data.status).toBe('pending');
51
+ });
52
+ });
53
+
54
+ describe('trigger nodes', () => {
55
+ it('should add a trigger node when triggers are defined', () => {
56
+ const workflow = {
57
+ metadata: { id: 'test-1', name: 'Test Workflow' },
58
+ steps: [{ id: 'step-1', action: 'http.get', inputs: {} }],
59
+ triggers: [{ type: 'webhook' as const, path: '/api/trigger' }],
60
+ };
61
+
62
+ const { nodes, edges } = workflowToGraph(workflow);
63
+
64
+ const triggerNode = nodes.find(n => n.type === 'trigger');
65
+ expect(triggerNode).toBeDefined();
66
+ expect(triggerNode?.data.type).toBe('webhook');
67
+ expect(triggerNode?.data.path).toBe('/api/trigger');
68
+
69
+ // Should have edge from trigger to first step
70
+ const triggerEdge = edges.find(e => e.source === triggerNode?.id && e.target === 'step-1');
71
+ expect(triggerEdge).toBeDefined();
72
+ });
73
+
74
+ it('should handle schedule trigger with cron', () => {
75
+ const workflow = {
76
+ metadata: { id: 'test-1', name: 'Test Workflow' },
77
+ steps: [{ id: 'step-1', action: 'http.get', inputs: {} }],
78
+ triggers: [{ type: 'schedule' as const, cron: '0 9 * * *' }],
79
+ };
80
+
81
+ const { nodes } = workflowToGraph(workflow);
82
+ const triggerNode = nodes.find(n => n.type === 'trigger');
83
+
84
+ expect(triggerNode?.data.type).toBe('schedule');
85
+ expect(triggerNode?.data.cron).toBe('0 9 * * *');
86
+ });
87
+ });
88
+
89
+ describe('sub-workflow nodes', () => {
90
+ it('should create subworkflow nodes for steps with workflow property', () => {
91
+ const workflow = {
92
+ metadata: { id: 'test-1', name: 'Test Workflow' },
93
+ steps: [
94
+ { id: 'step-1', action: 'http.get', inputs: {} },
95
+ { id: 'sub-1', name: 'Sub Process', workflow: '/workflows/sub.md', inputs: {} },
96
+ ],
97
+ };
98
+
99
+ const { nodes } = workflowToGraph(workflow);
100
+ const subNode = nodes.find(n => n.id === 'sub-1');
101
+
102
+ expect(subNode?.type).toBe('subworkflow');
103
+ expect(subNode?.data.workflowPath).toBe('/workflows/sub.md');
104
+ });
105
+ });
106
+
107
+ describe('output nodes', () => {
108
+ it('should add output node at the end', () => {
109
+ const workflow = {
110
+ metadata: { id: 'test-1', name: 'Test Workflow' },
111
+ steps: [
112
+ { id: 'step-1', action: 'http.get', inputs: {}, outputVariable: 'result1' },
113
+ { id: 'step-2', action: 'http.post', inputs: {}, outputVariable: 'result2' },
114
+ ],
115
+ };
116
+
117
+ const { nodes, edges } = workflowToGraph(workflow);
118
+ const outputNode = nodes.find(n => n.type === 'output');
119
+
120
+ expect(outputNode).toBeDefined();
121
+ expect(outputNode?.data.variables).toEqual(['result1', 'result2']);
122
+
123
+ // Should have edge from last step to output
124
+ const outputEdge = edges.find(e => e.source === 'step-2' && e.target === outputNode?.id);
125
+ expect(outputEdge).toBeDefined();
126
+ });
127
+ });
128
+
129
+ describe('variable dependency edges', () => {
130
+ it('should create data flow edges for variable references', () => {
131
+ const workflow = {
132
+ metadata: { id: 'test-1', name: 'Test Workflow' },
133
+ steps: [
134
+ { id: 'step-1', action: 'http.get', inputs: {}, outputVariable: 'response' },
135
+ { id: 'step-2', action: 'process', inputs: { data: '{{ response.body }}' } },
136
+ ],
137
+ };
138
+
139
+ const { edges } = workflowToGraph(workflow);
140
+ const dataEdge = edges.find(e => e.id.startsWith('data-') && e.source === 'step-1' && e.target === 'step-2');
141
+
142
+ expect(dataEdge).toBeDefined();
143
+ expect(dataEdge?.label).toBe('response');
144
+ expect(dataEdge?.animated).toBe(true);
145
+ });
146
+
147
+ it('should not create edges for inputs references', () => {
148
+ const workflow = {
149
+ metadata: { id: 'test-1', name: 'Test Workflow' },
150
+ steps: [
151
+ { id: 'step-1', action: 'http.get', inputs: { url: '{{ inputs.url }}' } },
152
+ ],
153
+ };
154
+
155
+ const { edges } = workflowToGraph(workflow);
156
+ const dataEdges = edges.filter(e => e.id.startsWith('data-'));
157
+
158
+ expect(dataEdges).toHaveLength(0);
159
+ });
160
+ });
161
+
162
+ describe('conditional edges', () => {
163
+ it('should label conditional edges', () => {
164
+ const workflow = {
165
+ metadata: { id: 'test-1', name: 'Test Workflow' },
166
+ steps: [
167
+ { id: 'step-1', action: 'check', inputs: {} },
168
+ { id: 'step-2', action: 'process', inputs: {}, conditions: ['result.success == true'] },
169
+ ],
170
+ };
171
+
172
+ const { edges } = workflowToGraph(workflow);
173
+ const conditionalEdge = edges.find(e => e.source === 'step-1' && e.target === 'step-2');
174
+
175
+ expect(conditionalEdge?.label).toBe('conditional');
176
+ });
177
+ });
178
+
179
+ describe('node positioning', () => {
180
+ it('should position nodes vertically', () => {
181
+ const workflow = {
182
+ metadata: { id: 'test-1', name: 'Test Workflow' },
183
+ steps: [
184
+ { id: 'step-1', action: 'a', inputs: {} },
185
+ { id: 'step-2', action: 'b', inputs: {} },
186
+ { id: 'step-3', action: 'c', inputs: {} },
187
+ ],
188
+ };
189
+
190
+ const { nodes } = workflowToGraph(workflow);
191
+ const stepNodes = nodes.filter(n => n.type === 'step');
192
+
193
+ // Each step should be below the previous one
194
+ expect(stepNodes[1].position.y).toBeGreaterThan(stepNodes[0].position.y);
195
+ expect(stepNodes[2].position.y).toBeGreaterThan(stepNodes[1].position.y);
196
+ });
197
+ });
198
+ });
199
+
200
+ describe('graphToWorkflow', () => {
201
+ it('should convert nodes back to workflow steps', () => {
202
+ const nodes = [
203
+ {
204
+ id: 'step-1',
205
+ type: 'step',
206
+ position: { x: 250, y: 0 },
207
+ data: { id: 'step-1', name: 'Step 1', action: 'http.get', inputs: {} },
208
+ },
209
+ {
210
+ id: 'step-2',
211
+ type: 'step',
212
+ position: { x: 250, y: 120 },
213
+ data: { id: 'step-2', name: 'Step 2', action: 'http.post', inputs: {}, outputVariable: 'result' },
214
+ },
215
+ ];
216
+
217
+ const edges = [{ id: 'e-1-2', source: 'step-1', target: 'step-2' }];
218
+ const metadata = { id: 'test', name: 'Test Workflow' };
219
+
220
+ const workflow = graphToWorkflow(nodes, edges, metadata);
221
+
222
+ expect(workflow.metadata).toEqual(metadata);
223
+ expect(workflow.steps).toHaveLength(2);
224
+ expect(workflow.steps[0].id).toBe('step-1');
225
+ expect(workflow.steps[0].action).toBe('http.get');
226
+ expect(workflow.steps[1].outputVariable).toBe('result');
227
+ });
228
+
229
+ it('should filter out trigger and output nodes', () => {
230
+ const nodes = [
231
+ { id: 'trigger-1', type: 'trigger', position: { x: 250, y: 0 }, data: { type: 'manual' } },
232
+ { id: 'step-1', type: 'step', position: { x: 250, y: 120 }, data: { id: 'step-1', action: 'test', inputs: {} } },
233
+ { id: 'output-1', type: 'output', position: { x: 250, y: 240 }, data: { variables: [] } },
234
+ ];
235
+
236
+ const workflow = graphToWorkflow(nodes, [], { id: 'test', name: 'Test' });
237
+
238
+ expect(workflow.steps).toHaveLength(1);
239
+ expect(workflow.steps[0].id).toBe('step-1');
240
+ });
241
+
242
+ it('should extract trigger info', () => {
243
+ const nodes = [
244
+ {
245
+ id: 'trigger-1',
246
+ type: 'trigger',
247
+ position: { x: 250, y: 0 },
248
+ data: { type: 'webhook', path: '/api/hook' },
249
+ },
250
+ { id: 'step-1', type: 'step', position: { x: 250, y: 120 }, data: { id: 'step-1', inputs: {} } },
251
+ ];
252
+
253
+ const workflow = graphToWorkflow(nodes, [], { id: 'test', name: 'Test' });
254
+
255
+ expect(workflow.triggers).toHaveLength(1);
256
+ expect(workflow.triggers?.[0].type).toBe('webhook');
257
+ expect(workflow.triggers?.[0].path).toBe('/api/hook');
258
+ });
259
+
260
+ it('should sort steps by vertical position', () => {
261
+ const nodes = [
262
+ { id: 'step-3', type: 'step', position: { x: 250, y: 240 }, data: { id: 'step-3', inputs: {} } },
263
+ { id: 'step-1', type: 'step', position: { x: 250, y: 0 }, data: { id: 'step-1', inputs: {} } },
264
+ { id: 'step-2', type: 'step', position: { x: 250, y: 120 }, data: { id: 'step-2', inputs: {} } },
265
+ ];
266
+
267
+ const workflow = graphToWorkflow(nodes, [], { id: 'test', name: 'Test' });
268
+
269
+ expect(workflow.steps[0].id).toBe('step-1');
270
+ expect(workflow.steps[1].id).toBe('step-2');
271
+ expect(workflow.steps[2].id).toBe('step-3');
272
+ });
273
+
274
+ it('should handle sub-workflow nodes', () => {
275
+ const nodes = [
276
+ {
277
+ id: 'sub-1',
278
+ type: 'subworkflow',
279
+ position: { x: 250, y: 0 },
280
+ data: { id: 'sub-1', name: 'Sub Workflow', workflowPath: '/sub.md', inputs: {} },
281
+ },
282
+ ];
283
+
284
+ const workflow = graphToWorkflow(nodes, [], { id: 'test', name: 'Test' });
285
+
286
+ expect(workflow.steps).toHaveLength(1);
287
+ expect(workflow.steps[0].workflow).toBe('/sub.md');
288
+ });
289
+ });
package/tsconfig.json ADDED
@@ -0,0 +1,29 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2022",
4
+ "lib": ["ES2022", "DOM", "DOM.Iterable"],
5
+ "module": "ESNext",
6
+ "moduleResolution": "bundler",
7
+ "resolveJsonModule": true,
8
+ "isolatedModules": true,
9
+ "jsx": "react-jsx",
10
+ "strict": true,
11
+ "noUnusedLocals": true,
12
+ "noUnusedParameters": true,
13
+ "noFallthroughCasesInSwitch": true,
14
+ "skipLibCheck": true,
15
+ "esModuleInterop": true,
16
+ "allowSyntheticDefaultImports": true,
17
+ "forceConsistentCasingInFileNames": true,
18
+ "declaration": true,
19
+ "declarationMap": true,
20
+ "sourceMap": true,
21
+ "baseUrl": ".",
22
+ "paths": {
23
+ "@/*": ["./src/client/*"],
24
+ "@shared/*": ["./src/shared/*"]
25
+ }
26
+ },
27
+ "include": ["src/client/**/*", "src/shared/**/*"],
28
+ "exclude": ["node_modules", "dist"]
29
+ }
@@ -0,0 +1,28 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2022",
4
+ "lib": ["ES2022"],
5
+ "module": "NodeNext",
6
+ "moduleResolution": "NodeNext",
7
+ "resolveJsonModule": true,
8
+ "isolatedModules": true,
9
+ "strict": true,
10
+ "noUnusedLocals": true,
11
+ "noUnusedParameters": true,
12
+ "noFallthroughCasesInSwitch": true,
13
+ "skipLibCheck": true,
14
+ "esModuleInterop": true,
15
+ "allowSyntheticDefaultImports": true,
16
+ "forceConsistentCasingInFileNames": true,
17
+ "declaration": false,
18
+ "sourceMap": true,
19
+ "outDir": "./dist/server",
20
+ "rootDir": "./src",
21
+ "baseUrl": ".",
22
+ "paths": {
23
+ "@shared/*": ["./src/shared/*"]
24
+ }
25
+ },
26
+ "include": ["src/server/**/*", "src/shared/**/*"],
27
+ "exclude": ["node_modules", "dist"]
28
+ }
package/vite.config.ts ADDED
@@ -0,0 +1,31 @@
1
+ import { defineConfig } from 'vite';
2
+ import react from '@vitejs/plugin-react';
3
+ import path from 'path';
4
+
5
+ export default defineConfig({
6
+ plugins: [react()],
7
+ resolve: {
8
+ alias: {
9
+ '@': path.resolve(__dirname, './src/client'),
10
+ '@shared': path.resolve(__dirname, './src/shared'),
11
+ },
12
+ },
13
+ server: {
14
+ port: 5173,
15
+ proxy: {
16
+ '/api': {
17
+ target: 'http://localhost:3001',
18
+ changeOrigin: true,
19
+ },
20
+ '/socket.io': {
21
+ target: 'http://localhost:3001',
22
+ changeOrigin: true,
23
+ ws: true,
24
+ },
25
+ },
26
+ },
27
+ build: {
28
+ outDir: 'dist/client',
29
+ sourcemap: true,
30
+ },
31
+ });
@@ -0,0 +1,26 @@
1
+ import { defineConfig } from 'vitest/config';
2
+ import react from '@vitejs/plugin-react';
3
+ import path from 'path';
4
+
5
+ export default defineConfig({
6
+ plugins: [react()],
7
+ resolve: {
8
+ alias: {
9
+ '@': path.resolve(__dirname, './src/client'),
10
+ '@shared': path.resolve(__dirname, './src/shared'),
11
+ },
12
+ },
13
+ test: {
14
+ globals: true,
15
+ environment: 'jsdom',
16
+ setupFiles: ['./tests/setup.ts'],
17
+ include: ['tests/**/*.test.{ts,tsx}'],
18
+ exclude: ['tests/e2e/**'],
19
+ coverage: {
20
+ provider: 'v8',
21
+ reporter: ['text', 'json', 'html'],
22
+ include: ['src/**/*.{ts,tsx}'],
23
+ exclude: ['src/**/*.d.ts', 'src/server/**'],
24
+ },
25
+ },
26
+ });