@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,183 @@
1
+ import { Printer } from '@motiadev/core'
2
+ import path from 'path'
3
+ import type { Plugin, ViteDevServer } from 'vite'
4
+ import { generateCssImports, generatePluginCode, isValidCode } from './generator'
5
+ import { handlePluginHotUpdate } from './hmr'
6
+ import { createAliasConfig, resolvePluginPackage } from './resolver'
7
+ import type { WorkbenchPlugin } from './types'
8
+ import { CONSTANTS } from './types'
9
+ import { isLocalPlugin, normalizePath } from './utils'
10
+ import { validatePlugins } from './validator'
11
+
12
+ /**
13
+ * Vite plugin for loading and managing Motia workbench plugins.
14
+ *
15
+ * Features:
16
+ * - Hot Module Replacement (HMR) support
17
+ * - Runtime validation with detailed error messages
18
+ * - Verbose logging for debugging
19
+ * - CSS injection for plugin styles
20
+ *
21
+ * @param plugins - Array of plugin configurations
22
+ * @param options - Optional loader configuration
23
+ * @returns Vite plugin instance
24
+ *
25
+ * @example
26
+ * ```ts
27
+ * export default defineConfig({
28
+ * plugins: [
29
+ * motiaPluginsPlugin([
30
+ * { packageName: '@my-org/plugin', label: 'My Plugin' }
31
+ * ])
32
+ * ]
33
+ * })
34
+ * ```
35
+ */
36
+ const printer = new Printer(process.cwd())
37
+
38
+ export default function motiaPluginsPlugin(plugins: WorkbenchPlugin[]): Plugin {
39
+ let devServer: ViteDevServer | null = null
40
+
41
+ try {
42
+ const validationResult = validatePlugins(plugins, {
43
+ failFast: false,
44
+ })
45
+
46
+ if (!validationResult.valid) {
47
+ printer.printPluginError('Plugin configuration validation failed:')
48
+ for (const err of validationResult.errors) {
49
+ printer.printPluginError(` ${err}`)
50
+ }
51
+ throw new Error('Invalid plugin configuration. See errors above.')
52
+ }
53
+
54
+ if (validationResult.warnings.length > 0) {
55
+ for (const warning of validationResult.warnings) {
56
+ printer.printPluginWarn(warning)
57
+ }
58
+ }
59
+ } catch (error) {
60
+ printer.printPluginError(`Failed to validate plugins: ${error}`)
61
+ throw error
62
+ }
63
+
64
+ const alias = createAliasConfig(plugins)
65
+
66
+ printer.printPluginLog(`Initialized with ${plugins.length} plugin(s)`)
67
+
68
+ return {
69
+ name: 'vite-plugin-motia-plugins',
70
+ enforce: 'pre',
71
+
72
+ buildStart() {
73
+ printer.printPluginLog('Build started')
74
+ },
75
+
76
+ config: () => ({
77
+ resolve: {
78
+ alias,
79
+ },
80
+ }),
81
+
82
+ configureServer(server) {
83
+ devServer = server
84
+ printer.printPluginLog('Dev server configured, HMR enabled')
85
+
86
+ const configPaths = [path.join(process.cwd(), 'motia.config.ts'), path.join(process.cwd(), 'motia.config.js')]
87
+
88
+ for (const configPath of configPaths) {
89
+ server.watcher.add(configPath)
90
+ }
91
+ printer.printPluginLog('Watching for config file changes')
92
+
93
+ const localPlugins = plugins.filter((p) => isLocalPlugin(p.packageName))
94
+ if (localPlugins.length > 0) {
95
+ printer.printPluginLog(`Watching ${localPlugins.length} local plugin(s)`)
96
+
97
+ for (const plugin of localPlugins) {
98
+ const resolved = resolvePluginPackage(plugin)
99
+ const watchPath = resolved.resolvedPath
100
+
101
+ server.watcher.add(watchPath)
102
+ printer.printPluginLog(`Watching: ${watchPath}`)
103
+ }
104
+
105
+ server.watcher.on('change', (file) => {
106
+ const normalizedFile = normalizePath(file)
107
+ printer.printPluginLog(`File watcher detected change: ${normalizedFile}`)
108
+ })
109
+
110
+ server.watcher.on('add', (file) => {
111
+ const normalizedFile = normalizePath(file)
112
+ printer.printPluginLog(`File watcher detected new file: ${normalizedFile}`)
113
+ })
114
+ }
115
+ },
116
+
117
+ resolveId(id) {
118
+ if (id === CONSTANTS.VIRTUAL_MODULE_ID) {
119
+ return CONSTANTS.RESOLVED_VIRTUAL_MODULE_ID
120
+ }
121
+ },
122
+
123
+ load(id) {
124
+ if (id !== CONSTANTS.RESOLVED_VIRTUAL_MODULE_ID) {
125
+ return null
126
+ }
127
+
128
+ printer.printPluginLog('Loading plugins virtual module')
129
+ printer.printPluginLog('Generating plugin code...')
130
+
131
+ const code = generatePluginCode(plugins)
132
+
133
+ if (!isValidCode(code)) {
134
+ printer.printPluginError('Generated code is invalid or empty')
135
+ return 'export const plugins = []'
136
+ }
137
+
138
+ printer.printPluginLog('Plugin code generated successfully')
139
+
140
+ return code
141
+ },
142
+
143
+ async transform(code, id) {
144
+ const normalizedId = normalizePath(id)
145
+
146
+ if (!normalizedId.endsWith('src/index.css')) {
147
+ return null
148
+ }
149
+
150
+ printer.printPluginLog('Injecting plugin CSS imports')
151
+
152
+ const cssImports = generateCssImports(plugins)
153
+
154
+ if (!cssImports) {
155
+ return null
156
+ }
157
+
158
+ return {
159
+ code: `${cssImports}\n${code}`,
160
+ map: null,
161
+ }
162
+ },
163
+
164
+ handleHotUpdate(ctx) {
165
+ if (!devServer) {
166
+ printer.printPluginWarn('HMR: Dev server not available')
167
+ return
168
+ }
169
+
170
+ const modulesToUpdate = handlePluginHotUpdate(ctx, plugins, printer)
171
+
172
+ if (modulesToUpdate && modulesToUpdate.length > 0) {
173
+ const merged = Array.from(new Set([...(ctx.modules || []), ...modulesToUpdate]))
174
+ printer.printPluginLog(`HMR: Successfully updated ${merged.length} module(s)`)
175
+ return merged
176
+ }
177
+ },
178
+
179
+ buildEnd() {
180
+ printer.printPluginLog('Build ended')
181
+ },
182
+ }
183
+ }
@@ -1,4 +1,6 @@
1
- import type { ResolvedPackage, WorkbenchPlugin } from './types';
1
+ import type { ResolvedPackage, WorkbenchPlugin } from './types'
2
+ import { isLocalPlugin, normalizePath, resolveLocalPath, resolveNpmPath } from './utils'
3
+
2
4
  /**
3
5
  * Resolves a plugin package to its absolute path and creates an alias.
4
6
  *
@@ -26,7 +28,20 @@ import type { ResolvedPackage, WorkbenchPlugin } from './types';
26
28
  * // }
27
29
  * ```
28
30
  */
29
- export declare function resolvePluginPackage(plugin: WorkbenchPlugin): ResolvedPackage;
31
+ export function resolvePluginPackage(plugin: WorkbenchPlugin): ResolvedPackage {
32
+ const { packageName } = plugin
33
+ const local = isLocalPlugin(packageName)
34
+
35
+ const resolvedPath = local ? resolveLocalPath(packageName) : resolveNpmPath(packageName)
36
+
37
+ return {
38
+ packageName,
39
+ resolvedPath: normalizePath(resolvedPath),
40
+ isLocal: local,
41
+ alias: packageName,
42
+ }
43
+ }
44
+
30
45
  /**
31
46
  * Resolves all plugin packages and creates a Vite alias configuration.
32
47
  *
@@ -46,18 +61,36 @@ export declare function resolvePluginPackage(plugin: WorkbenchPlugin): ResolvedP
46
61
  * // }
47
62
  * ```
48
63
  */
49
- export declare function createAliasConfig(plugins: WorkbenchPlugin[]): Record<string, string>;
64
+ export function createAliasConfig(plugins: WorkbenchPlugin[]): Record<string, string> {
65
+ // Get unique package names to avoid duplicate aliases
66
+ const uniquePackages = Array.from(new Set(plugins.map((p) => p.packageName)))
67
+
68
+ const aliases: Record<string, string> = {}
69
+
70
+ for (const packageName of uniquePackages) {
71
+ const resolved = resolvePluginPackage({ packageName } as WorkbenchPlugin)
72
+ aliases[packageName] = resolved.resolvedPath
73
+ }
74
+
75
+ return aliases
76
+ }
77
+
50
78
  /**
51
79
  * Resolves all plugins and returns their resolved package information.
52
80
  *
53
81
  * @param plugins - Array of plugin configurations
54
82
  * @returns Array of resolved package information
55
83
  */
56
- export declare function resolveAllPlugins(plugins: WorkbenchPlugin[]): ResolvedPackage[];
84
+ export function resolveAllPlugins(plugins: WorkbenchPlugin[]): ResolvedPackage[] {
85
+ return plugins.map((plugin) => resolvePluginPackage(plugin))
86
+ }
87
+
57
88
  /**
58
89
  * Gets the unique set of package names from plugins.
59
90
  *
60
91
  * @param plugins - Array of plugin configurations
61
92
  * @returns Array of unique package names
62
93
  */
63
- export declare function getUniquePackageNames(plugins: WorkbenchPlugin[]): string[];
94
+ export function getUniquePackageNames(plugins: WorkbenchPlugin[]): string[] {
95
+ return Array.from(new Set(plugins.map((p) => p.packageName)))
96
+ }
@@ -0,0 +1,198 @@
1
+ /**
2
+ * Configuration for a single workbench plugin.
3
+ * This interface defines how plugins are registered and configured in the Motia workbench.
4
+ */
5
+ export interface WorkbenchPlugin {
6
+ /**
7
+ * The package name or local path to the plugin.
8
+ * - For npm packages: use the package name (e.g., '@my-org/my-plugin')
9
+ * - For local plugins: use the tilde prefix (e.g., '~/plugins/my-plugin')
10
+ */
11
+ packageName: string
12
+
13
+ /**
14
+ * Optional named export to use from the plugin package.
15
+ * If not specified, the default export will be used.
16
+ * Can be overridden by the plugin's own config.
17
+ */
18
+ componentName?: string
19
+
20
+ /**
21
+ * Position where the plugin tab should appear in the workbench.
22
+ * - 'top': Display in the top tab bar
23
+ * - 'bottom': Display in the bottom tab bar
24
+ * @default 'top'
25
+ */
26
+ position?: 'top' | 'bottom'
27
+
28
+ /**
29
+ * Display label for the plugin tab.
30
+ * Can be overridden by the plugin's own config.
31
+ */
32
+ label?: string
33
+
34
+ /**
35
+ * Icon name from lucide-react to display next to the label.
36
+ * Can be overridden by the plugin's own config.
37
+ * @default 'toy-brick'
38
+ */
39
+ labelIcon?: string
40
+
41
+ /**
42
+ * Array of CSS package imports to inject into the workbench.
43
+ * These will be added to the main index.css file.
44
+ * Example: ['@my-org/my-plugin/styles.css']
45
+ */
46
+ cssImports?: string[]
47
+
48
+ /**
49
+ * Props to pass to the plugin component when it's rendered.
50
+ * Can be overridden by the plugin's own config.
51
+ */
52
+ props?: Record<string, any>
53
+ }
54
+
55
+ /**
56
+ * Result of validating a plugin configuration.
57
+ */
58
+ export interface ValidationResult {
59
+ /**
60
+ * Whether the validation passed
61
+ */
62
+ valid: boolean
63
+
64
+ /**
65
+ * Array of error messages if validation failed
66
+ */
67
+ errors: string[]
68
+
69
+ /**
70
+ * Array of warning messages for non-critical issues
71
+ */
72
+ warnings: string[]
73
+
74
+ /**
75
+ * The validated and normalized plugin configuration (if valid)
76
+ */
77
+ plugin?: WorkbenchPlugin
78
+ }
79
+
80
+ /**
81
+ * Context object passed to various plugin internals functions.
82
+ * Contains shared state and configuration.
83
+ */
84
+ export interface PluginContext {
85
+ /**
86
+ * Array of plugin configurations
87
+ */
88
+ plugins: WorkbenchPlugin[]
89
+
90
+ /**
91
+ * Vite dev server instance (only available in dev mode)
92
+ */
93
+ server?: any
94
+ }
95
+
96
+ /**
97
+ * Package resolution result.
98
+ */
99
+ export interface ResolvedPackage {
100
+ /**
101
+ * The original package name from the configuration
102
+ */
103
+ packageName: string
104
+
105
+ /**
106
+ * Resolved absolute path to the package
107
+ */
108
+ resolvedPath: string
109
+
110
+ /**
111
+ * Whether this is a local plugin (starts with ~/)
112
+ */
113
+ isLocal: boolean
114
+
115
+ /**
116
+ * Alias to use for imports
117
+ */
118
+ alias: string
119
+ }
120
+
121
+ /**
122
+ * Generated virtual module exports interface.
123
+ * This is what consumers will import from 'virtual:motia-plugins'.
124
+ */
125
+ export interface VirtualModuleExports {
126
+ /**
127
+ * Array of processed plugin configurations ready to be registered
128
+ */
129
+ plugins: ProcessedPlugin[]
130
+ }
131
+
132
+ /**
133
+ * A plugin configuration after processing and normalization.
134
+ * This is the format used by the workbench to register tabs.
135
+ */
136
+ export interface ProcessedPlugin {
137
+ /**
138
+ * Display label for the plugin tab
139
+ */
140
+ label: string
141
+
142
+ /**
143
+ * Icon name from lucide-react
144
+ */
145
+ labelIcon: string
146
+
147
+ /**
148
+ * Position in the workbench ('top' or 'bottom')
149
+ */
150
+ position: 'top' | 'bottom'
151
+
152
+ /**
153
+ * Props to pass to the component
154
+ */
155
+ props: Record<string, any>
156
+
157
+ /**
158
+ * The React component to render
159
+ */
160
+ component: any
161
+ }
162
+
163
+ /**
164
+ * Type guard to check if position is valid.
165
+ */
166
+ export function isValidPosition(position: any): position is 'top' | 'bottom' {
167
+ return position === 'top' || position === 'bottom'
168
+ }
169
+
170
+ /**
171
+ * Constants used throughout the plugin system.
172
+ */
173
+ export const CONSTANTS = {
174
+ /**
175
+ * Virtual module ID for importing plugins
176
+ */
177
+ VIRTUAL_MODULE_ID: 'virtual:motia-plugins',
178
+
179
+ /**
180
+ * Resolved virtual module ID (with null byte prefix for Vite)
181
+ */
182
+ RESOLVED_VIRTUAL_MODULE_ID: '\0virtual:motia-plugins',
183
+
184
+ /**
185
+ * Log prefix for all plugin messages
186
+ */
187
+ LOG_PREFIX: '[motia-plugins]',
188
+
189
+ /**
190
+ * Default values for optional plugin fields
191
+ */
192
+ DEFAULTS: {
193
+ POSITION: 'top' as const,
194
+ LABEL: 'Plugin label',
195
+ ICON: 'toy-brick',
196
+ PROPS: {},
197
+ },
198
+ } as const
@@ -1,3 +1,5 @@
1
+ import path from 'path'
2
+
1
3
  /**
2
4
  * Normalizes a file path by replacing backslashes with forward slashes.
3
5
  * This is useful for consistent path comparisons across different operating systems.
@@ -11,7 +13,10 @@
11
13
  * normalizePath('/Users/file.ts') // Returns: '/Users/file.ts'
12
14
  * ```
13
15
  */
14
- export declare function normalizePath(filePath: string): string;
16
+ export function normalizePath(filePath: string): string {
17
+ return filePath.replace(/\\/g, '/')
18
+ }
19
+
15
20
  /**
16
21
  * Checks if a package name represents a local plugin (starts with ~/).
17
22
  *
@@ -25,7 +30,10 @@ export declare function normalizePath(filePath: string): string;
25
30
  * isLocalPlugin('my-plugin') // Returns: false
26
31
  * ```
27
32
  */
28
- export declare function isLocalPlugin(packageName: string): boolean;
33
+ export function isLocalPlugin(packageName: string): boolean {
34
+ return packageName.startsWith('~/')
35
+ }
36
+
29
37
  /**
30
38
  * Resolves a local plugin path to an absolute path.
31
39
  * Strips the ~/ prefix and joins with the current working directory.
@@ -40,7 +48,10 @@ export declare function isLocalPlugin(packageName: string): boolean;
40
48
  * // Returns: '/Users/project/plugins/my-plugin'
41
49
  * ```
42
50
  */
43
- export declare function resolveLocalPath(packageName: string): string;
51
+ export function resolveLocalPath(packageName: string): string {
52
+ return path.join(process.cwd(), packageName.replace('~/', ''))
53
+ }
54
+
44
55
  /**
45
56
  * Resolves an npm package path to the node_modules directory.
46
57
  *
@@ -54,4 +65,6 @@ export declare function resolveLocalPath(packageName: string): string;
54
65
  * // Returns: '/Users/project/node_modules/@my-org/my-plugin'
55
66
  * ```
56
67
  */
57
- export declare function resolveNpmPath(packageName: string): string;
68
+ export function resolveNpmPath(packageName: string): string {
69
+ return path.join(process.cwd(), 'node_modules', packageName)
70
+ }
@@ -0,0 +1,197 @@
1
+ import { existsSync } from 'fs'
2
+ import { z } from 'zod'
3
+ import type { ValidationResult, WorkbenchPlugin } from './types'
4
+ import { CONSTANTS, isValidPosition } from './types'
5
+ import { isLocalPlugin, resolveLocalPath } from './utils'
6
+
7
+ /**
8
+ * Zod schema for WorkbenchPlugin configuration.
9
+ * Provides runtime type validation with detailed error messages.
10
+ */
11
+ const WorkbenchPluginSchema = z.object({
12
+ packageName: z
13
+ .string()
14
+ .min(1, 'packageName is required and cannot be empty')
15
+ .refine((name) => name.startsWith('~/') || name.startsWith('@') || /^[a-z0-9-_]+$/i.test(name), {
16
+ message: 'packageName must be a valid npm package name or local path (starting with ~/)',
17
+ }),
18
+
19
+ componentName: z.string().optional(),
20
+
21
+ position: z
22
+ .enum(['top', 'bottom'])
23
+ .optional()
24
+ .refine((pos) => pos === undefined || isValidPosition(pos), {
25
+ message: 'position must be either "top" or "bottom"',
26
+ }),
27
+
28
+ label: z.string().optional(),
29
+
30
+ labelIcon: z.string().optional(),
31
+
32
+ cssImports: z.array(z.string()).optional(),
33
+
34
+ props: z.record(z.any(), z.any()).optional(),
35
+ })
36
+
37
+ /**
38
+ * Validates a single plugin configuration.
39
+ *
40
+ * @param plugin - The plugin configuration to validate
41
+ * @param index - The index of the plugin in the array (for error messages)
42
+ * @returns A validation result with errors, warnings, and normalized plugin
43
+ */
44
+ export function validatePluginConfig(plugin: any, index: number): ValidationResult {
45
+ const errors: string[] = []
46
+ const warnings: string[] = []
47
+
48
+ if (typeof plugin !== 'object' || plugin === null) {
49
+ return {
50
+ valid: false,
51
+ errors: [`Plugin at index ${index}: expected object, got ${typeof plugin}`],
52
+ warnings: [],
53
+ }
54
+ }
55
+
56
+ try {
57
+ const result = WorkbenchPluginSchema.safeParse(plugin)
58
+
59
+ if (!result.success) {
60
+ result.error.issues.forEach((err: z.core.$ZodIssue) => {
61
+ const path = err.path.join('.')
62
+ errors.push(`Plugin at index ${index}, field "${path}": ${err.message}`)
63
+ })
64
+
65
+ return { valid: false, errors, warnings }
66
+ }
67
+
68
+ const validatedPlugin = result.data as WorkbenchPlugin
69
+
70
+ if (isLocalPlugin(validatedPlugin.packageName)) {
71
+ const resolvedPath = resolveLocalPath(validatedPlugin.packageName)
72
+ if (!existsSync(resolvedPath)) {
73
+ warnings.push(
74
+ `Plugin at index ${index}: local path "${validatedPlugin.packageName}" does not exist at "${resolvedPath}". ` +
75
+ `Make sure the path is correct relative to the project root.`,
76
+ )
77
+ }
78
+ }
79
+
80
+ if (!validatedPlugin.label) {
81
+ warnings.push(`Plugin at index ${index}: "label" not specified, will use default "${CONSTANTS.DEFAULTS.LABEL}"`)
82
+ }
83
+
84
+ if (!validatedPlugin.labelIcon) {
85
+ warnings.push(
86
+ `Plugin at index ${index}: "labelIcon" not specified, will use default "${CONSTANTS.DEFAULTS.ICON}"`,
87
+ )
88
+ }
89
+
90
+ if (!validatedPlugin.position) {
91
+ warnings.push(
92
+ `Plugin at index ${index}: "position" not specified, will use default "${CONSTANTS.DEFAULTS.POSITION}"`,
93
+ )
94
+ }
95
+
96
+ if (validatedPlugin.props && Object.keys(validatedPlugin.props).length === 0) {
97
+ warnings.push(`Plugin at index ${index}: "props" is an empty object`)
98
+ }
99
+
100
+ if (validatedPlugin.cssImports) {
101
+ if (validatedPlugin.cssImports.length === 0) {
102
+ warnings.push(`Plugin at index ${index}: "cssImports" is an empty array`)
103
+ }
104
+
105
+ validatedPlugin.cssImports.forEach((cssImport, cssIndex) => {
106
+ if (!cssImport || cssImport.trim() === '') {
107
+ warnings.push(`Plugin at index ${index}: cssImport at index ${cssIndex} is empty or whitespace`)
108
+ }
109
+ })
110
+ }
111
+
112
+ return {
113
+ valid: true,
114
+ errors: [],
115
+ warnings,
116
+ plugin: validatedPlugin,
117
+ }
118
+ } catch (error) {
119
+ return {
120
+ valid: false,
121
+ errors: [`Plugin at index ${index}: unexpected validation error: ${error}`],
122
+ warnings: [],
123
+ }
124
+ }
125
+ }
126
+
127
+ /**
128
+ * Validates an array of plugin configurations.
129
+ *
130
+ * @param plugins - Array of plugin configurations to validate
131
+ * @param options - Validation options
132
+ * @returns Combined validation result for all plugins
133
+ */
134
+ export function validatePlugins(plugins: any[], options: { failFast?: boolean } = {}): ValidationResult {
135
+ const allErrors: string[] = []
136
+ const allWarnings: string[] = []
137
+ const validatedPlugins: WorkbenchPlugin[] = []
138
+
139
+ if (!Array.isArray(plugins)) {
140
+ return {
141
+ valid: false,
142
+ errors: [`Expected plugins to be an array, got ${typeof plugins}`],
143
+ warnings: [],
144
+ }
145
+ }
146
+
147
+ if (plugins.length === 0) {
148
+ console.warn('[motia-plugins] No plugins provided to validate')
149
+ return {
150
+ valid: true,
151
+ errors: [],
152
+ warnings: ['No plugins configured'],
153
+ }
154
+ }
155
+
156
+ for (let i = 0; i < plugins.length; i++) {
157
+ const result = validatePluginConfig(plugins[i], i)
158
+
159
+ allErrors.push(...result.errors)
160
+ allWarnings.push(...result.warnings)
161
+
162
+ if (result.valid && result.plugin) {
163
+ validatedPlugins.push(result.plugin)
164
+ }
165
+
166
+ if (options.failFast && result.errors.length > 0) {
167
+ break
168
+ }
169
+ }
170
+
171
+ const packageNames = validatedPlugins.map((p) => p.packageName)
172
+ const duplicates = packageNames.filter((name, index) => packageNames.indexOf(name) !== index)
173
+
174
+ if (duplicates.length > 0) {
175
+ const uniqueDuplicates = [...new Set(duplicates)]
176
+ uniqueDuplicates.forEach((dup) => {
177
+ allWarnings.push(`Duplicate package name found: "${dup}". This may cause conflicts.`)
178
+ })
179
+ }
180
+
181
+ const valid = allErrors.length === 0
182
+
183
+ if (valid) {
184
+ console.log(`[motia-plugins] ✓ Validated ${validatedPlugins.length} plugin(s) successfully`)
185
+ if (allWarnings.length > 0) {
186
+ console.warn(`[motia-plugins] Found ${allWarnings.length} warning(s)`)
187
+ }
188
+ } else {
189
+ console.error(`[motia-plugins] ✗ Validation failed with ${allErrors.length} error(s)`)
190
+ }
191
+
192
+ return {
193
+ valid,
194
+ errors: allErrors,
195
+ warnings: allWarnings,
196
+ }
197
+ }