@motiadev/workbench 0.14.0-beta.165-285707 → 0.15.0-beta.165

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 (236) hide show
  1. package/dist/index.d.ts +189 -10
  2. package/dist/index.html +1 -1
  3. package/dist/index.js +1065 -7
  4. package/dist/middleware.d.ts +66 -8
  5. package/dist/middleware.js +694 -86
  6. package/dist/motia-plugin/__tests__/generator.test.ts +129 -0
  7. package/dist/motia-plugin/__tests__/resolver.test.ts +82 -0
  8. package/dist/motia-plugin/__tests__/validator.test.ts +71 -0
  9. package/dist/motia-plugin/{generator.js → generator.ts} +37 -35
  10. package/dist/motia-plugin/hmr.ts +123 -0
  11. package/dist/motia-plugin/index.ts +183 -0
  12. package/dist/motia-plugin/{resolver.d.ts → resolver.ts} +38 -5
  13. package/dist/motia-plugin/types.ts +198 -0
  14. package/dist/motia-plugin/{utils.d.ts → utils.ts} +17 -4
  15. package/dist/motia-plugin/validator.ts +197 -0
  16. package/dist/src/App.tsx +41 -0
  17. package/dist/src/components/NotFoundPage.tsx +11 -0
  18. package/dist/src/components/bottom-panel.tsx +39 -0
  19. package/dist/src/components/flow/base-edge.tsx +61 -0
  20. package/dist/src/components/flow/flow-loader.tsx +3 -0
  21. package/dist/src/components/flow/flow-page.tsx +75 -0
  22. package/dist/src/components/flow/flow-tab-menu-item.tsx +52 -0
  23. package/dist/src/components/flow/flow-view.tsx +66 -0
  24. package/dist/src/components/flow/hooks/use-get-flow-state.tsx +171 -0
  25. package/dist/src/components/flow/hooks/use-save-workflow-config.ts +25 -0
  26. package/dist/src/components/flow/node-organizer.tsx +103 -0
  27. package/dist/src/components/flow/nodes/api-flow-node.tsx +6 -0
  28. package/dist/src/components/flow/nodes/cron-flow-node.tsx +6 -0
  29. package/dist/src/components/flow/nodes/event-flow-node.tsx +6 -0
  30. package/dist/src/components/flow/nodes/noop-flow-node.tsx +6 -0
  31. package/dist/src/components/header/deploy-button.tsx +110 -0
  32. package/dist/src/components/header/header.tsx +39 -0
  33. package/dist/src/components/root-motia.tsx +10 -0
  34. package/dist/src/components/top-panel.tsx +40 -0
  35. package/dist/src/components/tutorial/engine/tutorial-engine.ts +26 -0
  36. package/dist/src/components/tutorial/engine/tutorial-types.ts +26 -0
  37. package/dist/src/components/tutorial/engine/workbench-xpath.ts +53 -0
  38. package/dist/src/components/tutorial/hooks/tutorial-utils.ts +26 -0
  39. package/dist/src/components/tutorial/hooks/use-tutorial-engine.ts +213 -0
  40. package/dist/src/components/tutorial/hooks/use-tutorial.ts +14 -0
  41. package/dist/src/components/tutorial/tutorial-button.tsx +46 -0
  42. package/dist/src/components/tutorial/tutorial-step.tsx +82 -0
  43. package/dist/src/components/tutorial/tutorial.tsx +59 -0
  44. package/dist/src/components/ui/json-editor.tsx +68 -0
  45. package/dist/src/components/ui/table.tsx +75 -0
  46. package/dist/src/components/ui/theme-toggle.tsx +54 -0
  47. package/dist/src/components/ui/tooltip.tsx +26 -0
  48. package/dist/src/hooks/use-debounced.ts +22 -0
  49. package/dist/src/hooks/use-fetch-flows.ts +33 -0
  50. package/dist/src/hooks/use-mobile.ts +19 -0
  51. package/dist/src/hooks/use-update-handle-positions.ts +42 -0
  52. package/dist/src/index.css +5 -5
  53. package/dist/src/lib/__tests__/utils.test.ts +110 -0
  54. package/dist/src/lib/motia-analytics.ts +140 -0
  55. package/dist/src/lib/plugins.tsx +132 -0
  56. package/dist/src/lib/utils.ts +37 -0
  57. package/dist/src/main.tsx +30 -0
  58. package/dist/src/project-view-mode.tsx +32 -0
  59. package/dist/src/publicComponents/api-node.tsx +26 -0
  60. package/dist/src/publicComponents/base-node/base-handle.tsx +50 -0
  61. package/dist/src/publicComponents/base-node/base-node.tsx +114 -0
  62. package/dist/src/publicComponents/base-node/code-display.tsx +119 -0
  63. package/dist/src/publicComponents/base-node/emits.tsx +17 -0
  64. package/dist/src/publicComponents/base-node/feature-card.tsx +32 -0
  65. package/dist/src/publicComponents/base-node/language-indicator.tsx +131 -0
  66. package/dist/src/publicComponents/base-node/node-header.tsx +49 -0
  67. package/dist/src/publicComponents/base-node/node-sidebar.tsx +41 -0
  68. package/dist/src/publicComponents/base-node/subscribe.tsx +13 -0
  69. package/dist/src/publicComponents/cron-node.tsx +24 -0
  70. package/dist/src/publicComponents/event-node.tsx +20 -0
  71. package/dist/src/publicComponents/node-props.tsx +15 -0
  72. package/dist/src/publicComponents/noop-node.tsx +19 -0
  73. package/dist/src/setupTests.ts +1 -0
  74. package/dist/src/stores/use-app-tabs-store.ts +49 -0
  75. package/dist/src/stores/use-flow-store.ts +31 -0
  76. package/dist/src/stores/use-global-store.ts +24 -0
  77. package/dist/src/stores/use-motia-config-store.ts +36 -0
  78. package/dist/src/stores/use-tabs-store.ts +34 -0
  79. package/dist/src/system-view-mode.tsx +28 -0
  80. package/dist/src/types/endpoint.ts +12 -0
  81. package/dist/src/types/file.ts +7 -0
  82. package/dist/src/types/flow.ts +103 -0
  83. package/eslint.config.cjs +22 -0
  84. package/jest.config.cjs +68 -0
  85. package/package.json +53 -51
  86. package/dist/motia-plugin/__tests__/generator.test.d.ts +0 -1
  87. package/dist/motia-plugin/__tests__/generator.test.js +0 -97
  88. package/dist/motia-plugin/__tests__/resolver.test.d.ts +0 -1
  89. package/dist/motia-plugin/__tests__/resolver.test.js +0 -64
  90. package/dist/motia-plugin/__tests__/validator.test.d.ts +0 -1
  91. package/dist/motia-plugin/__tests__/validator.test.js +0 -59
  92. package/dist/motia-plugin/generator.d.ts +0 -78
  93. package/dist/motia-plugin/hmr.d.ts +0 -22
  94. package/dist/motia-plugin/hmr.js +0 -100
  95. package/dist/motia-plugin/index.d.ts +0 -3
  96. package/dist/motia-plugin/index.js +0 -153
  97. package/dist/motia-plugin/resolver.js +0 -92
  98. package/dist/motia-plugin/types.d.ts +0 -169
  99. package/dist/motia-plugin/types.js +0 -36
  100. package/dist/motia-plugin/utils.js +0 -75
  101. package/dist/motia-plugin/validator.d.ts +0 -19
  102. package/dist/motia-plugin/validator.js +0 -163
  103. package/dist/src/App.d.ts +0 -2
  104. package/dist/src/App.js +0 -35
  105. package/dist/src/components/NotFoundPage.d.ts +0 -1
  106. package/dist/src/components/NotFoundPage.js +0 -3
  107. package/dist/src/components/bottom-panel.d.ts +0 -1
  108. package/dist/src/components/bottom-panel.js +0 -15
  109. package/dist/src/components/flow/base-edge.d.ts +0 -3
  110. package/dist/src/components/flow/base-edge.js +0 -39
  111. package/dist/src/components/flow/flow-loader.d.ts +0 -1
  112. package/dist/src/components/flow/flow-loader.js +0 -4
  113. package/dist/src/components/flow/flow-page.d.ts +0 -1
  114. package/dist/src/components/flow/flow-page.js +0 -25
  115. package/dist/src/components/flow/flow-tab-menu-item.d.ts +0 -1
  116. package/dist/src/components/flow/flow-tab-menu-item.js +0 -18
  117. package/dist/src/components/flow/flow-view.d.ts +0 -12
  118. package/dist/src/components/flow/flow-view.js +0 -22
  119. package/dist/src/components/flow/hooks/use-get-flow-state.d.ts +0 -10
  120. package/dist/src/components/flow/hooks/use-get-flow-state.js +0 -133
  121. package/dist/src/components/flow/hooks/use-save-workflow-config.d.ts +0 -2
  122. package/dist/src/components/flow/hooks/use-save-workflow-config.js +0 -22
  123. package/dist/src/components/flow/node-organizer.d.ts +0 -10
  124. package/dist/src/components/flow/node-organizer.js +0 -82
  125. package/dist/src/components/flow/nodes/api-flow-node.d.ts +0 -2
  126. package/dist/src/components/flow/nodes/api-flow-node.js +0 -5
  127. package/dist/src/components/flow/nodes/cron-flow-node.d.ts +0 -2
  128. package/dist/src/components/flow/nodes/cron-flow-node.js +0 -5
  129. package/dist/src/components/flow/nodes/event-flow-node.d.ts +0 -2
  130. package/dist/src/components/flow/nodes/event-flow-node.js +0 -5
  131. package/dist/src/components/flow/nodes/noop-flow-node.d.ts +0 -2
  132. package/dist/src/components/flow/nodes/noop-flow-node.js +0 -5
  133. package/dist/src/components/header/deploy-button.d.ts +0 -1
  134. package/dist/src/components/header/deploy-button.js +0 -28
  135. package/dist/src/components/header/header.d.ts +0 -2
  136. package/dist/src/components/header/header.js +0 -23
  137. package/dist/src/components/root-motia.d.ts +0 -2
  138. package/dist/src/components/root-motia.js +0 -7
  139. package/dist/src/components/top-panel.d.ts +0 -1
  140. package/dist/src/components/top-panel.js +0 -15
  141. package/dist/src/components/tutorial/engine/tutorial-engine.d.ts +0 -12
  142. package/dist/src/components/tutorial/engine/tutorial-engine.js +0 -36
  143. package/dist/src/components/tutorial/engine/tutorial-types.d.ts +0 -22
  144. package/dist/src/components/tutorial/engine/tutorial-types.js +0 -1
  145. package/dist/src/components/tutorial/engine/workbench-xpath.d.ts +0 -45
  146. package/dist/src/components/tutorial/engine/workbench-xpath.js +0 -45
  147. package/dist/src/components/tutorial/hooks/tutorial-utils.d.ts +0 -1
  148. package/dist/src/components/tutorial/hooks/tutorial-utils.js +0 -17
  149. package/dist/src/components/tutorial/hooks/use-tutorial-engine.d.ts +0 -15
  150. package/dist/src/components/tutorial/hooks/use-tutorial-engine.js +0 -183
  151. package/dist/src/components/tutorial/hooks/use-tutorial.d.ts +0 -5
  152. package/dist/src/components/tutorial/hooks/use-tutorial.js +0 -10
  153. package/dist/src/components/tutorial/tutorial-button.d.ts +0 -2
  154. package/dist/src/components/tutorial/tutorial-button.js +0 -21
  155. package/dist/src/components/tutorial/tutorial-step.d.ts +0 -14
  156. package/dist/src/components/tutorial/tutorial-step.js +0 -19
  157. package/dist/src/components/tutorial/tutorial.d.ts +0 -2
  158. package/dist/src/components/tutorial/tutorial.js +0 -32
  159. package/dist/src/components/ui/json-editor.d.ts +0 -12
  160. package/dist/src/components/ui/json-editor.js +0 -35
  161. package/dist/src/components/ui/table.d.ts +0 -10
  162. package/dist/src/components/ui/table.js +0 -20
  163. package/dist/src/components/ui/theme-toggle.d.ts +0 -2
  164. package/dist/src/components/ui/theme-toggle.js +0 -19
  165. package/dist/src/components/ui/tooltip.d.ts +0 -6
  166. package/dist/src/components/ui/tooltip.js +0 -3
  167. package/dist/src/hooks/use-debounced.d.ts +0 -1
  168. package/dist/src/hooks/use-debounced.js +0 -18
  169. package/dist/src/hooks/use-fetch-flows.d.ts +0 -1
  170. package/dist/src/hooks/use-fetch-flows.js +0 -26
  171. package/dist/src/hooks/use-mobile.d.ts +0 -1
  172. package/dist/src/hooks/use-mobile.js +0 -15
  173. package/dist/src/hooks/use-update-handle-positions.d.ts +0 -10
  174. package/dist/src/hooks/use-update-handle-positions.js +0 -35
  175. package/dist/src/lib/__tests__/utils.test.d.ts +0 -1
  176. package/dist/src/lib/__tests__/utils.test.js +0 -94
  177. package/dist/src/lib/motia-analytics.d.ts +0 -38
  178. package/dist/src/lib/motia-analytics.js +0 -132
  179. package/dist/src/lib/plugins.d.ts +0 -2
  180. package/dist/src/lib/plugins.js +0 -105
  181. package/dist/src/lib/utils.d.ts +0 -7
  182. package/dist/src/lib/utils.js +0 -34
  183. package/dist/src/main.d.ts +0 -2
  184. package/dist/src/main.js +0 -17
  185. package/dist/src/project-view-mode.d.ts +0 -1
  186. package/dist/src/project-view-mode.js +0 -20
  187. package/dist/src/publicComponents/api-node.d.ts +0 -5
  188. package/dist/src/publicComponents/api-node.js +0 -5
  189. package/dist/src/publicComponents/base-node/base-handle.d.ts +0 -9
  190. package/dist/src/publicComponents/base-node/base-handle.js +0 -8
  191. package/dist/src/publicComponents/base-node/base-node.d.ts +0 -15
  192. package/dist/src/publicComponents/base-node/base-node.js +0 -30
  193. package/dist/src/publicComponents/base-node/code-display.d.ts +0 -9
  194. package/dist/src/publicComponents/base-node/code-display.js +0 -64
  195. package/dist/src/publicComponents/base-node/emits.d.ts +0 -5
  196. package/dist/src/publicComponents/base-node/emits.js +0 -5
  197. package/dist/src/publicComponents/base-node/feature-card.d.ts +0 -10
  198. package/dist/src/publicComponents/base-node/feature-card.js +0 -5
  199. package/dist/src/publicComponents/base-node/language-indicator.d.ts +0 -10
  200. package/dist/src/publicComponents/base-node/language-indicator.js +0 -29
  201. package/dist/src/publicComponents/base-node/node-header.d.ts +0 -13
  202. package/dist/src/publicComponents/base-node/node-header.js +0 -30
  203. package/dist/src/publicComponents/base-node/node-sidebar.d.ts +0 -14
  204. package/dist/src/publicComponents/base-node/node-sidebar.js +0 -9
  205. package/dist/src/publicComponents/base-node/subscribe.d.ts +0 -4
  206. package/dist/src/publicComponents/base-node/subscribe.js +0 -4
  207. package/dist/src/publicComponents/cron-node.d.ts +0 -4
  208. package/dist/src/publicComponents/cron-node.js +0 -6
  209. package/dist/src/publicComponents/event-node.d.ts +0 -4
  210. package/dist/src/publicComponents/event-node.js +0 -5
  211. package/dist/src/publicComponents/node-props.d.ts +0 -21
  212. package/dist/src/publicComponents/node-props.js +0 -1
  213. package/dist/src/publicComponents/noop-node.d.ts +0 -4
  214. package/dist/src/publicComponents/noop-node.js +0 -5
  215. package/dist/src/setupTests.d.ts +0 -1
  216. package/dist/src/setupTests.js +0 -1
  217. package/dist/src/stores/use-app-tabs-store.d.ts +0 -16
  218. package/dist/src/stores/use-app-tabs-store.js +0 -31
  219. package/dist/src/stores/use-flow-store.d.ts +0 -21
  220. package/dist/src/stores/use-flow-store.js +0 -16
  221. package/dist/src/stores/use-global-store.d.ts +0 -18
  222. package/dist/src/stores/use-global-store.js +0 -12
  223. package/dist/src/stores/use-motia-config-store.d.ts +0 -12
  224. package/dist/src/stores/use-motia-config-store.js +0 -24
  225. package/dist/src/stores/use-tabs-store.d.ts +0 -19
  226. package/dist/src/stores/use-tabs-store.js +0 -22
  227. package/dist/src/system-view-mode.d.ts +0 -1
  228. package/dist/src/system-view-mode.js +0 -10
  229. package/dist/src/types/endpoint.d.ts +0 -14
  230. package/dist/src/types/endpoint.js +0 -1
  231. package/dist/src/types/file.d.ts +0 -7
  232. package/dist/src/types/file.js +0 -1
  233. package/dist/src/types/flow.d.ts +0 -115
  234. package/dist/src/types/flow.js +0 -1
  235. package/dist/tsconfig.app.tsbuildinfo +0 -1
  236. package/dist/tsconfig.node.tsbuildinfo +0 -1
@@ -0,0 +1,129 @@
1
+ import {
2
+ generateCssImports,
3
+ generateImports,
4
+ generatePackageMap,
5
+ generatePluginCode,
6
+ generatePluginLogic,
7
+ isValidCode,
8
+ } from '../generator'
9
+ import type { WorkbenchPlugin } from '../types'
10
+
11
+ describe('Generator', () => {
12
+ describe('generateImports', () => {
13
+ it('should generate import statements', () => {
14
+ const packages = ['@test/plugin-1', '@test/plugin-2']
15
+ const imports = generateImports(packages)
16
+
17
+ expect(imports).toContain("import * as plugin_0 from '@test/plugin-1'")
18
+ expect(imports).toContain("import * as plugin_1 from '@test/plugin-2'")
19
+ })
20
+
21
+ it('should handle empty package array', () => {
22
+ const imports = generateImports([])
23
+
24
+ expect(imports).toBe('')
25
+ })
26
+ })
27
+
28
+ describe('generatePackageMap', () => {
29
+ it('should generate package map', () => {
30
+ const packages = ['@test/plugin-1', '@test/plugin-2']
31
+ const map = generatePackageMap(packages)
32
+
33
+ expect(map).toContain('const packageMap')
34
+ expect(map).toContain("'@test/plugin-1': plugin_0")
35
+ expect(map).toContain("'@test/plugin-2': plugin_1")
36
+ })
37
+ })
38
+
39
+ describe('generatePluginLogic', () => {
40
+ it('should generate plugin processing logic', () => {
41
+ const plugins: WorkbenchPlugin[] = [{ packageName: '@test/plugin', label: 'Test' }]
42
+ const logic = generatePluginLogic(plugins)
43
+
44
+ expect(logic).toContain('const motiaPlugins')
45
+ expect(logic).toContain('export const plugins')
46
+ expect(logic).toContain('packageMap[plugin.packageName]')
47
+ })
48
+ })
49
+
50
+ describe('generatePluginCode', () => {
51
+ it('should generate complete module code', () => {
52
+ const plugins: WorkbenchPlugin[] = [{ packageName: '@test/plugin', label: 'Test' }]
53
+ const code = generatePluginCode(plugins)
54
+
55
+ expect(code).toContain('import * as plugin_0')
56
+ expect(code).toContain('const packageMap')
57
+ expect(code).toContain('export const plugins')
58
+ })
59
+
60
+ it('should handle empty plugins array', () => {
61
+ const code = generatePluginCode([])
62
+
63
+ expect(code).toBe('export const plugins = []')
64
+ })
65
+
66
+ it('should handle multiple plugins', () => {
67
+ const plugins: WorkbenchPlugin[] = [{ packageName: '@test/plugin-1' }, { packageName: '@test/plugin-2' }]
68
+ const code = generatePluginCode(plugins)
69
+
70
+ expect(code).toContain("'@test/plugin-1'")
71
+ expect(code).toContain("'@test/plugin-2'")
72
+ })
73
+ })
74
+
75
+ describe('generateCssImports', () => {
76
+ it('should generate CSS import statements', () => {
77
+ const plugins: WorkbenchPlugin[] = [{ packageName: '@test/plugin', cssImports: ['styles.css', 'theme.css'] }]
78
+ const css = generateCssImports(plugins)
79
+
80
+ expect(css).toContain("@import 'styles.css';")
81
+ expect(css).toContain("@import 'theme.css';")
82
+ })
83
+
84
+ it('should filter empty CSS imports', () => {
85
+ const plugins: WorkbenchPlugin[] = [{ packageName: '@test/plugin', cssImports: ['styles.css', '', ' '] }]
86
+ const css = generateCssImports(plugins)
87
+
88
+ expect(css).toContain("@import 'styles.css';")
89
+ expect(css).not.toContain("@import '';")
90
+ })
91
+
92
+ it('should return empty string for plugins without CSS imports', () => {
93
+ const plugins: WorkbenchPlugin[] = [{ packageName: '@test/plugin' }]
94
+ const css = generateCssImports(plugins)
95
+
96
+ expect(css).toBe('')
97
+ })
98
+
99
+ it('should flatten CSS imports from multiple plugins', () => {
100
+ const plugins: WorkbenchPlugin[] = [
101
+ { packageName: '@test/plugin-1', cssImports: ['a.css'] },
102
+ { packageName: '@test/plugin-2', cssImports: ['b.css'] },
103
+ ]
104
+ const css = generateCssImports(plugins)
105
+
106
+ expect(css).toContain("@import 'a.css';")
107
+ expect(css).toContain("@import 'b.css';")
108
+ })
109
+ })
110
+
111
+ describe('isValidCode', () => {
112
+ it('should return true for valid code', () => {
113
+ expect(isValidCode('export const plugins = []')).toBe(true)
114
+ })
115
+
116
+ it('should return false for empty string', () => {
117
+ expect(isValidCode('')).toBe(false)
118
+ })
119
+
120
+ it('should return false for whitespace only', () => {
121
+ expect(isValidCode(' \n\t ')).toBe(false)
122
+ })
123
+
124
+ it('should return false for non-string', () => {
125
+ expect(isValidCode(null as any)).toBe(false)
126
+ expect(isValidCode(undefined as any)).toBe(false)
127
+ })
128
+ })
129
+ })
@@ -0,0 +1,82 @@
1
+ import { createAliasConfig, getUniquePackageNames, resolvePluginPackage } from '../resolver'
2
+ import type { WorkbenchPlugin } from '../types'
3
+
4
+ describe('Resolver', () => {
5
+ describe('resolvePluginPackage', () => {
6
+ it('should resolve local plugin', () => {
7
+ const plugin: WorkbenchPlugin = { packageName: '~/plugins/local' }
8
+ const resolved = resolvePluginPackage(plugin)
9
+
10
+ expect(resolved.packageName).toBe('~/plugins/local')
11
+ expect(resolved.isLocal).toBe(true)
12
+ expect(resolved.resolvedPath).toContain('plugins/local')
13
+ expect(resolved.alias).toBe('~/plugins/local')
14
+ })
15
+
16
+ it('should resolve npm package', () => {
17
+ const plugin: WorkbenchPlugin = { packageName: '@test/plugin' }
18
+ const resolved = resolvePluginPackage(plugin)
19
+
20
+ expect(resolved.packageName).toBe('@test/plugin')
21
+ expect(resolved.isLocal).toBe(false)
22
+ expect(resolved.resolvedPath).toContain('node_modules')
23
+ expect(resolved.resolvedPath).toContain('@test/plugin')
24
+ expect(resolved.alias).toBe('@test/plugin')
25
+ })
26
+
27
+ it('should normalize paths', () => {
28
+ const plugin: WorkbenchPlugin = { packageName: 'simple-plugin' }
29
+ const resolved = resolvePluginPackage(plugin)
30
+
31
+ expect(resolved.resolvedPath).not.toContain('\\')
32
+ })
33
+ })
34
+
35
+ describe('createAliasConfig', () => {
36
+ it('should create aliases for all plugins', () => {
37
+ const plugins: WorkbenchPlugin[] = [{ packageName: '~/plugins/local' }, { packageName: '@test/npm' }]
38
+
39
+ const aliases = createAliasConfig(plugins)
40
+
41
+ expect(aliases['~/plugins/local']).toBeDefined()
42
+ expect(aliases['@test/npm']).toBeDefined()
43
+ })
44
+
45
+ it('should handle duplicate package names', () => {
46
+ const plugins: WorkbenchPlugin[] = [{ packageName: '@test/plugin' }, { packageName: '@test/plugin' }]
47
+
48
+ const aliases = createAliasConfig(plugins)
49
+
50
+ expect(Object.keys(aliases)).toHaveLength(1)
51
+ expect(aliases['@test/plugin']).toBeDefined()
52
+ })
53
+
54
+ it('should return empty object for empty plugins array', () => {
55
+ const aliases = createAliasConfig([])
56
+
57
+ expect(aliases).toEqual({})
58
+ })
59
+ })
60
+
61
+ describe('getUniquePackageNames', () => {
62
+ it('should return unique package names', () => {
63
+ const plugins: WorkbenchPlugin[] = [
64
+ { packageName: '@test/a' },
65
+ { packageName: '@test/b' },
66
+ { packageName: '@test/a' },
67
+ ]
68
+
69
+ const unique = getUniquePackageNames(plugins)
70
+
71
+ expect(unique).toHaveLength(2)
72
+ expect(unique).toContain('@test/a')
73
+ expect(unique).toContain('@test/b')
74
+ })
75
+
76
+ it('should return empty array for no plugins', () => {
77
+ const unique = getUniquePackageNames([])
78
+
79
+ expect(unique).toEqual([])
80
+ })
81
+ })
82
+ })
@@ -0,0 +1,71 @@
1
+ import { validatePlugins } from '../validator'
2
+
3
+ describe('Validator', () => {
4
+ describe('validatePlugins', () => {
5
+ it('should validate array of valid plugins', () => {
6
+ const plugins = [
7
+ { packageName: '@test/plugin-1', label: 'Plugin 1' },
8
+ { packageName: '@test/plugin-2', label: 'Plugin 2' },
9
+ ]
10
+ const result = validatePlugins(plugins)
11
+
12
+ expect(result.valid).toBe(true)
13
+ expect(result.errors).toHaveLength(0)
14
+ })
15
+
16
+ it('should reject non-array input', () => {
17
+ const result = validatePlugins('not-an-array' as any)
18
+
19
+ expect(result.valid).toBe(false)
20
+ expect(result.errors.some((err) => err.includes('array'))).toBe(true)
21
+ })
22
+
23
+ it('should handle empty array', () => {
24
+ const result = validatePlugins([])
25
+
26
+ expect(result.valid).toBe(true)
27
+ expect(result.warnings.some((w) => w.includes('No plugins'))).toBe(true)
28
+ })
29
+
30
+ it('should collect all errors from multiple plugins', () => {
31
+ const plugins = [{ packageName: '' }, { packageName: '' }, { packageName: 'valid' }]
32
+ const result = validatePlugins(plugins)
33
+
34
+ expect(result.valid).toBe(false)
35
+ expect(result.errors.length).toBeGreaterThanOrEqual(2)
36
+ })
37
+
38
+ it('should warn about duplicate package names', () => {
39
+ const plugins = [
40
+ { packageName: '@test/plugin', label: 'Plugin 1' },
41
+ { packageName: '@test/plugin', label: 'Plugin 2' },
42
+ ]
43
+ const result = validatePlugins(plugins)
44
+
45
+ expect(result.valid).toBe(true)
46
+ expect(result.warnings.some((w) => w.includes('Duplicate'))).toBe(true)
47
+ })
48
+
49
+ it('should support failFast option', () => {
50
+ const plugins = [{ packageName: '' }, { packageName: '' }]
51
+ const result = validatePlugins(plugins, { failFast: true })
52
+
53
+ expect(result.valid).toBe(false)
54
+ // With failFast, should stop after first plugin validation fails
55
+ // Each plugin with empty packageName generates 2 errors (min length + invalid format)
56
+ // So failFast should result in only the first plugin's errors
57
+ expect(result.errors.length).toBeLessThanOrEqual(2)
58
+ // Verify it didn't process second plugin by checking error messages don't mention index 1
59
+ const hasIndex1Error = result.errors.some((err) => err.includes('index 1'))
60
+ expect(hasIndex1Error).toBe(false)
61
+ })
62
+
63
+ it('should handle mixed valid and invalid plugins', () => {
64
+ const plugins = [{ packageName: '@test/valid' }, { packageName: '' }, { packageName: '@test/another-valid' }]
65
+ const result = validatePlugins(plugins)
66
+
67
+ expect(result.valid).toBe(false)
68
+ expect(result.errors.length).toBeGreaterThan(0)
69
+ })
70
+ })
71
+ })
@@ -1,12 +1,6 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.generateImports = generateImports;
4
- exports.generatePackageMap = generatePackageMap;
5
- exports.generatePluginLogic = generatePluginLogic;
6
- exports.generatePluginCode = generatePluginCode;
7
- exports.generateCssImports = generateCssImports;
8
- exports.isValidCode = isValidCode;
9
- const resolver_1 = require("./resolver");
1
+ import { getUniquePackageNames } from './resolver'
2
+ import type { WorkbenchPlugin } from './types'
3
+
10
4
  /**
11
5
  * Generates import statements for all unique plugin packages.
12
6
  *
@@ -21,9 +15,10 @@ const resolver_1 = require("./resolver");
21
15
  * // import * as plugin_1 from '~/plugins/local'
22
16
  * ```
23
17
  */
24
- function generateImports(packages) {
25
- return packages.map((packageName, index) => `import * as plugin_${index} from '${packageName}'`).join('\n');
18
+ export function generateImports(packages: string[]): string {
19
+ return packages.map((packageName, index) => `import * as plugin_${index} from '${packageName}'`).join('\n')
26
20
  }
21
+
27
22
  /**
28
23
  * Generates the package map that links package names to their imported modules.
29
24
  *
@@ -36,18 +31,19 @@ function generateImports(packages) {
36
31
  * // Returns: const packageMap = {'@org/plugin-1': plugin_0,'~/plugins/local': plugin_1}
37
32
  * ```
38
33
  */
39
- function generatePackageMap(packages) {
40
- const entries = packages.map((packageName, index) => `'${packageName}': plugin_${index}`);
41
- return `const packageMap = {${entries.join(',')}}`;
34
+ export function generatePackageMap(packages: string[]): string {
35
+ const entries = packages.map((packageName, index) => `'${packageName}': plugin_${index}`)
36
+ return `const packageMap = {${entries.join(',')}}`
42
37
  }
38
+
43
39
  /**
44
40
  * Generates the plugin transformation logic that processes plugin configurations.
45
41
  *
46
42
  * @param plugins - Array of plugin configurations
47
43
  * @returns JavaScript code string with plugin processing logic
48
44
  */
49
- function generatePluginLogic(plugins) {
50
- return `
45
+ export function generatePluginLogic(plugins: WorkbenchPlugin[]): string {
46
+ return `
51
47
  const motiaPlugins = ${JSON.stringify(plugins)}
52
48
 
53
49
  export const plugins = motiaPlugins.map((plugin) => {
@@ -63,8 +59,9 @@ function generatePluginLogic(plugins) {
63
59
  component: componentName ? component[componentName] : component.default,
64
60
  }
65
61
  })
66
- `;
62
+ `
67
63
  }
64
+
68
65
  /**
69
66
  * Generates the complete virtual module code for all plugins.
70
67
  * This is the main code generation function that combines all parts.
@@ -81,18 +78,21 @@ function generatePluginLogic(plugins) {
81
78
  * // Returns complete module code with imports, map, and logic
82
79
  * ```
83
80
  */
84
- function generatePluginCode(plugins) {
85
- if (!plugins || plugins.length === 0) {
86
- return 'export const plugins = []';
87
- }
88
- const packages = (0, resolver_1.getUniquePackageNames)(plugins);
89
- const imports = generateImports(packages);
90
- const packageMap = generatePackageMap(packages);
91
- const logic = generatePluginLogic(plugins);
92
- return `${imports}
81
+ export function generatePluginCode(plugins: WorkbenchPlugin[]): string {
82
+ if (!plugins || plugins.length === 0) {
83
+ return 'export const plugins = []'
84
+ }
85
+
86
+ const packages = getUniquePackageNames(plugins)
87
+ const imports = generateImports(packages)
88
+ const packageMap = generatePackageMap(packages)
89
+ const logic = generatePluginLogic(plugins)
90
+
91
+ return `${imports}
93
92
  ${packageMap}
94
- ${logic}`;
93
+ ${logic}`
95
94
  }
95
+
96
96
  /**
97
97
  * Generates CSS imports for plugins that specify cssImports.
98
98
  *
@@ -110,19 +110,21 @@ ${logic}`;
110
110
  * // @import 'theme.css';
111
111
  * ```
112
112
  */
113
- function generateCssImports(plugins) {
114
- const cssImports = plugins
115
- .flatMap((plugin) => plugin.cssImports || [])
116
- .filter((cssImport) => cssImport && cssImport.trim() !== '')
117
- .map((cssImport) => `@import '${cssImport}';`);
118
- return cssImports.join('\n');
113
+ export function generateCssImports(plugins: WorkbenchPlugin[]): string {
114
+ const cssImports = plugins
115
+ .flatMap((plugin) => plugin.cssImports || [])
116
+ .filter((cssImport) => cssImport && cssImport.trim() !== '')
117
+ .map((cssImport) => `@import '${cssImport}';`)
118
+
119
+ return cssImports.join('\n')
119
120
  }
121
+
120
122
  /**
121
123
  * Checks if the generated code is valid (non-empty and has content).
122
124
  *
123
125
  * @param code - The generated code to check
124
126
  * @returns True if code is valid
125
127
  */
126
- function isValidCode(code) {
127
- return typeof code === 'string' && code.trim().length > 0;
128
+ export function isValidCode(code: string): boolean {
129
+ return typeof code === 'string' && code.trim().length > 0
128
130
  }
@@ -0,0 +1,123 @@
1
+ import type { Printer } from '@motiadev/core'
2
+ import path from 'path'
3
+ import type { HmrContext, ModuleNode } from 'vite'
4
+ import { resolvePluginPackage } from './resolver'
5
+ import type { WorkbenchPlugin } from './types'
6
+ import { CONSTANTS } from './types'
7
+ import { isLocalPlugin, normalizePath } from './utils'
8
+
9
+ const WATCHED_EXTENSIONS = ['.ts', '.tsx', '.js', '.jsx', '.css', '.scss', '.less']
10
+
11
+ export function isConfigFile(file: string): boolean {
12
+ const normalizedFile = normalizePath(file)
13
+ return normalizedFile.endsWith('motia.config.ts') || normalizedFile.endsWith('motia.config.js')
14
+ }
15
+
16
+ /**
17
+ * Checks if a file change should trigger HMR for plugins.
18
+ *
19
+ * @param file - The file path that changed
20
+ * @param plugins - Current plugin configurations
21
+ * @returns True if the change affects plugins
22
+ */
23
+ export function shouldInvalidatePlugins(file: string, plugins: WorkbenchPlugin[]): boolean {
24
+ const normalizedFile = normalizePath(file)
25
+ const absoluteFile = path.isAbsolute(normalizedFile) ? normalizedFile : path.resolve(process.cwd(), normalizedFile)
26
+
27
+ if (isConfigFile(file)) {
28
+ return true
29
+ }
30
+
31
+ const hasWatchedExtension = WATCHED_EXTENSIONS.some((ext) => absoluteFile.endsWith(ext))
32
+ if (!hasWatchedExtension) {
33
+ return false
34
+ }
35
+
36
+ for (const plugin of plugins) {
37
+ if (isLocalPlugin(plugin.packageName)) {
38
+ const resolved = resolvePluginPackage(plugin)
39
+ const pluginAbsolutePath = path.isAbsolute(resolved.resolvedPath)
40
+ ? resolved.resolvedPath
41
+ : path.resolve(process.cwd(), resolved.resolvedPath)
42
+
43
+ const normalizedPluginPath = pluginAbsolutePath.endsWith(path.sep)
44
+ ? pluginAbsolutePath
45
+ : `${pluginAbsolutePath}${path.sep}`
46
+
47
+ if (absoluteFile.startsWith(normalizedPluginPath) || absoluteFile === pluginAbsolutePath) {
48
+ return true
49
+ }
50
+ }
51
+ }
52
+
53
+ return false
54
+ }
55
+
56
+ /**
57
+ * Handles hot updates for the plugin system.
58
+ * This function is called by Vite's handleHotUpdate hook.
59
+ *
60
+ * @param ctx - Vite's HMR context
61
+ * @param plugins - Current plugin configurations
62
+ * @param printer - Printer instance for logging
63
+ * @returns Array of modules to update, or undefined to continue with default behavior
64
+ */
65
+ export function handlePluginHotUpdate(
66
+ ctx: HmrContext,
67
+ plugins: WorkbenchPlugin[],
68
+ printer: Printer,
69
+ ): ModuleNode[] | undefined {
70
+ const { file, server, timestamp } = ctx
71
+
72
+ printer.printPluginLog(`HMR: File changed: ${normalizePath(file)}`)
73
+
74
+ if (isConfigFile(file)) {
75
+ printer.printPluginLog('HMR: Config file changed, triggering full page reload')
76
+ printer.printPluginWarn(
77
+ 'Configuration changes require a server restart for full effect. Please restart the dev server to apply all changes.',
78
+ )
79
+ server.ws.send({
80
+ type: 'full-reload',
81
+ path: '*',
82
+ })
83
+ return
84
+ }
85
+
86
+ if (!shouldInvalidatePlugins(file, plugins)) {
87
+ printer.printPluginLog('HMR: Change outside plugin scope, delegating to Vite default handling')
88
+ return
89
+ }
90
+
91
+ printer.printPluginLog('HMR: Plugin change detected, invalidating virtual module')
92
+
93
+ const virtualModule = server.moduleGraph.getModuleById(CONSTANTS.RESOLVED_VIRTUAL_MODULE_ID)
94
+
95
+ if (!virtualModule) {
96
+ printer.printPluginWarn('HMR: Virtual module not found, triggering full reload as fallback')
97
+ server.ws.send({
98
+ type: 'full-reload',
99
+ path: '*',
100
+ })
101
+ return
102
+ }
103
+
104
+ server.moduleGraph.invalidateModule(virtualModule, new Set(), timestamp)
105
+ printer.printPluginLog('HMR: Virtual module invalidated')
106
+
107
+ const modulesToUpdateSet = new Set<ModuleNode>([virtualModule])
108
+ const processedModules = new Set<ModuleNode>([virtualModule])
109
+
110
+ for (const importer of virtualModule.importers) {
111
+ if (!processedModules.has(importer)) {
112
+ processedModules.add(importer)
113
+ modulesToUpdateSet.add(importer)
114
+ server.moduleGraph.invalidateModule(importer, new Set(), timestamp)
115
+ }
116
+ }
117
+
118
+ const modulesToUpdate = Array.from(modulesToUpdateSet)
119
+
120
+ printer.printPluginLog(`HMR: Updated ${modulesToUpdate.length} module(s)`)
121
+
122
+ return modulesToUpdate
123
+ }