@motiadev/workbench 0.13.0-beta.161 → 0.13.0-beta.162-439402

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 (240) hide show
  1. package/dist/index.d.ts +191 -10
  2. package/dist/index.html +1 -1
  3. package/dist/index.js +1066 -7
  4. package/dist/index.js.map +1 -0
  5. package/dist/middleware.d.ts +67 -8
  6. package/dist/middleware.js +685 -86
  7. package/dist/middleware.js.map +1 -0
  8. package/dist/motia-plugin/__tests__/generator.test.ts +129 -0
  9. package/dist/motia-plugin/__tests__/resolver.test.ts +82 -0
  10. package/dist/motia-plugin/__tests__/validator.test.ts +71 -0
  11. package/dist/motia-plugin/{generator.js → generator.ts} +37 -35
  12. package/dist/motia-plugin/hmr.ts +123 -0
  13. package/dist/motia-plugin/index.ts +183 -0
  14. package/dist/motia-plugin/{resolver.d.ts → resolver.ts} +38 -5
  15. package/dist/motia-plugin/types.ts +198 -0
  16. package/dist/motia-plugin/{utils.d.ts → utils.ts} +17 -4
  17. package/dist/motia-plugin/validator.ts +197 -0
  18. package/dist/src/src/App.tsx +41 -0
  19. package/dist/src/src/components/NotFoundPage.tsx +11 -0
  20. package/dist/src/src/components/bottom-panel.tsx +39 -0
  21. package/dist/src/src/components/flow/base-edge.tsx +61 -0
  22. package/dist/src/src/components/flow/flow-loader.tsx +3 -0
  23. package/dist/src/src/components/flow/flow-page.tsx +75 -0
  24. package/dist/src/src/components/flow/flow-tab-menu-item.tsx +50 -0
  25. package/dist/src/src/components/flow/flow-view.tsx +66 -0
  26. package/dist/src/src/components/flow/hooks/use-get-flow-state.tsx +171 -0
  27. package/dist/src/src/components/flow/hooks/use-save-workflow-config.ts +25 -0
  28. package/dist/src/src/components/flow/node-organizer.tsx +103 -0
  29. package/dist/src/src/components/flow/nodes/api-flow-node.tsx +6 -0
  30. package/dist/src/src/components/flow/nodes/cron-flow-node.tsx +6 -0
  31. package/dist/src/src/components/flow/nodes/event-flow-node.tsx +6 -0
  32. package/dist/src/src/components/flow/nodes/noop-flow-node.tsx +6 -0
  33. package/dist/src/src/components/header/deploy-button.tsx +110 -0
  34. package/dist/src/src/components/header/header.tsx +37 -0
  35. package/dist/src/src/components/root-motia.tsx +10 -0
  36. package/dist/src/src/components/top-panel.tsx +40 -0
  37. package/dist/src/src/components/tutorial/engine/tutorial-engine.ts +26 -0
  38. package/dist/src/src/components/tutorial/engine/tutorial-types.ts +26 -0
  39. package/dist/src/src/components/tutorial/engine/workbench-xpath.ts +53 -0
  40. package/dist/src/src/components/tutorial/hooks/tutorial-utils.ts +26 -0
  41. package/dist/src/src/components/tutorial/hooks/use-tutorial-engine.ts +213 -0
  42. package/dist/src/src/components/tutorial/hooks/use-tutorial.ts +14 -0
  43. package/dist/src/src/components/tutorial/tutorial-button.tsx +46 -0
  44. package/dist/src/src/components/tutorial/tutorial-step.tsx +82 -0
  45. package/dist/src/{components → src/components}/tutorial/tutorial.css +2 -2
  46. package/dist/src/src/components/tutorial/tutorial.tsx +59 -0
  47. package/dist/src/src/components/ui/json-editor.tsx +68 -0
  48. package/dist/src/src/components/ui/table.tsx +75 -0
  49. package/dist/src/src/components/ui/theme-toggle.tsx +54 -0
  50. package/dist/src/src/components/ui/tooltip.tsx +26 -0
  51. package/dist/src/src/hooks/use-debounced.ts +22 -0
  52. package/dist/src/src/hooks/use-fetch-flows.ts +33 -0
  53. package/dist/src/src/hooks/use-mobile.ts +19 -0
  54. package/dist/src/src/hooks/use-update-handle-positions.ts +42 -0
  55. package/dist/src/{index.css → src/index.css} +5 -5
  56. package/dist/src/src/lib/__tests__/utils.test.ts +110 -0
  57. package/dist/src/src/lib/motia-analytics.ts +140 -0
  58. package/dist/src/src/lib/plugins.tsx +132 -0
  59. package/dist/src/src/lib/utils.ts +37 -0
  60. package/dist/src/src/main.tsx +30 -0
  61. package/dist/src/src/project-view-mode.tsx +32 -0
  62. package/dist/src/src/publicComponents/api-node.tsx +26 -0
  63. package/dist/src/src/publicComponents/base-node/base-handle.tsx +50 -0
  64. package/dist/src/src/publicComponents/base-node/base-node.tsx +114 -0
  65. package/dist/src/src/publicComponents/base-node/code-display.tsx +119 -0
  66. package/dist/src/src/publicComponents/base-node/emits.tsx +17 -0
  67. package/dist/src/src/publicComponents/base-node/feature-card.tsx +32 -0
  68. package/dist/src/src/publicComponents/base-node/language-indicator.tsx +131 -0
  69. package/dist/src/src/publicComponents/base-node/node-header.tsx +49 -0
  70. package/dist/src/src/publicComponents/base-node/node-sidebar.tsx +41 -0
  71. package/dist/src/src/publicComponents/base-node/subscribe.tsx +13 -0
  72. package/dist/src/src/publicComponents/cron-node.tsx +24 -0
  73. package/dist/src/src/publicComponents/event-node.tsx +20 -0
  74. package/dist/src/src/publicComponents/node-props.tsx +15 -0
  75. package/dist/src/src/publicComponents/noop-node.tsx +19 -0
  76. package/dist/src/src/setupTests.ts +1 -0
  77. package/dist/src/src/stores/use-app-tabs-store.ts +49 -0
  78. package/dist/src/src/stores/use-flow-store.ts +31 -0
  79. package/dist/src/src/stores/use-global-store.ts +24 -0
  80. package/dist/src/src/stores/use-motia-config-store.ts +36 -0
  81. package/dist/src/src/stores/use-tabs-store.ts +34 -0
  82. package/dist/src/src/system-view-mode.tsx +28 -0
  83. package/dist/src/src/types/endpoint.ts +12 -0
  84. package/dist/src/src/types/file.ts +7 -0
  85. package/dist/src/src/types/flow.ts +103 -0
  86. package/package.json +64 -49
  87. package/dist/motia-plugin/__tests__/generator.test.d.ts +0 -1
  88. package/dist/motia-plugin/__tests__/generator.test.js +0 -97
  89. package/dist/motia-plugin/__tests__/resolver.test.d.ts +0 -1
  90. package/dist/motia-plugin/__tests__/resolver.test.js +0 -64
  91. package/dist/motia-plugin/__tests__/validator.test.d.ts +0 -1
  92. package/dist/motia-plugin/__tests__/validator.test.js +0 -59
  93. package/dist/motia-plugin/generator.d.ts +0 -78
  94. package/dist/motia-plugin/hmr.d.ts +0 -22
  95. package/dist/motia-plugin/hmr.js +0 -100
  96. package/dist/motia-plugin/index.d.ts +0 -3
  97. package/dist/motia-plugin/index.js +0 -153
  98. package/dist/motia-plugin/resolver.js +0 -92
  99. package/dist/motia-plugin/types.d.ts +0 -169
  100. package/dist/motia-plugin/types.js +0 -36
  101. package/dist/motia-plugin/utils.js +0 -75
  102. package/dist/motia-plugin/validator.d.ts +0 -19
  103. package/dist/motia-plugin/validator.js +0 -163
  104. package/dist/src/App.d.ts +0 -2
  105. package/dist/src/App.js +0 -35
  106. package/dist/src/components/NotFoundPage.d.ts +0 -1
  107. package/dist/src/components/NotFoundPage.js +0 -3
  108. package/dist/src/components/bottom-panel.d.ts +0 -1
  109. package/dist/src/components/bottom-panel.js +0 -15
  110. package/dist/src/components/flow/base-edge.d.ts +0 -3
  111. package/dist/src/components/flow/base-edge.js +0 -39
  112. package/dist/src/components/flow/flow-loader.d.ts +0 -1
  113. package/dist/src/components/flow/flow-loader.js +0 -4
  114. package/dist/src/components/flow/flow-page.d.ts +0 -1
  115. package/dist/src/components/flow/flow-page.js +0 -25
  116. package/dist/src/components/flow/flow-tab-menu-item.d.ts +0 -1
  117. package/dist/src/components/flow/flow-tab-menu-item.js +0 -18
  118. package/dist/src/components/flow/flow-view.d.ts +0 -12
  119. package/dist/src/components/flow/flow-view.js +0 -22
  120. package/dist/src/components/flow/hooks/use-get-flow-state.d.ts +0 -10
  121. package/dist/src/components/flow/hooks/use-get-flow-state.js +0 -133
  122. package/dist/src/components/flow/hooks/use-save-workflow-config.d.ts +0 -2
  123. package/dist/src/components/flow/hooks/use-save-workflow-config.js +0 -22
  124. package/dist/src/components/flow/node-organizer.d.ts +0 -10
  125. package/dist/src/components/flow/node-organizer.js +0 -82
  126. package/dist/src/components/flow/nodes/api-flow-node.d.ts +0 -2
  127. package/dist/src/components/flow/nodes/api-flow-node.js +0 -5
  128. package/dist/src/components/flow/nodes/cron-flow-node.d.ts +0 -2
  129. package/dist/src/components/flow/nodes/cron-flow-node.js +0 -5
  130. package/dist/src/components/flow/nodes/event-flow-node.d.ts +0 -2
  131. package/dist/src/components/flow/nodes/event-flow-node.js +0 -5
  132. package/dist/src/components/flow/nodes/noop-flow-node.d.ts +0 -2
  133. package/dist/src/components/flow/nodes/noop-flow-node.js +0 -5
  134. package/dist/src/components/header/deploy-button.d.ts +0 -1
  135. package/dist/src/components/header/deploy-button.js +0 -28
  136. package/dist/src/components/header/header.d.ts +0 -2
  137. package/dist/src/components/header/header.js +0 -23
  138. package/dist/src/components/root-motia.d.ts +0 -2
  139. package/dist/src/components/root-motia.js +0 -7
  140. package/dist/src/components/top-panel.d.ts +0 -1
  141. package/dist/src/components/top-panel.js +0 -15
  142. package/dist/src/components/tutorial/engine/tutorial-engine.d.ts +0 -12
  143. package/dist/src/components/tutorial/engine/tutorial-engine.js +0 -36
  144. package/dist/src/components/tutorial/engine/tutorial-types.d.ts +0 -22
  145. package/dist/src/components/tutorial/engine/tutorial-types.js +0 -1
  146. package/dist/src/components/tutorial/engine/workbench-xpath.d.ts +0 -45
  147. package/dist/src/components/tutorial/engine/workbench-xpath.js +0 -45
  148. package/dist/src/components/tutorial/hooks/tutorial-utils.d.ts +0 -1
  149. package/dist/src/components/tutorial/hooks/tutorial-utils.js +0 -17
  150. package/dist/src/components/tutorial/hooks/use-tutorial-engine.d.ts +0 -15
  151. package/dist/src/components/tutorial/hooks/use-tutorial-engine.js +0 -183
  152. package/dist/src/components/tutorial/hooks/use-tutorial.d.ts +0 -5
  153. package/dist/src/components/tutorial/hooks/use-tutorial.js +0 -10
  154. package/dist/src/components/tutorial/tutorial-button.d.ts +0 -2
  155. package/dist/src/components/tutorial/tutorial-button.js +0 -21
  156. package/dist/src/components/tutorial/tutorial-step.d.ts +0 -14
  157. package/dist/src/components/tutorial/tutorial-step.js +0 -19
  158. package/dist/src/components/tutorial/tutorial.d.ts +0 -2
  159. package/dist/src/components/tutorial/tutorial.js +0 -32
  160. package/dist/src/components/ui/json-editor.d.ts +0 -12
  161. package/dist/src/components/ui/json-editor.js +0 -35
  162. package/dist/src/components/ui/table.d.ts +0 -10
  163. package/dist/src/components/ui/table.js +0 -20
  164. package/dist/src/components/ui/theme-toggle.d.ts +0 -2
  165. package/dist/src/components/ui/theme-toggle.js +0 -19
  166. package/dist/src/components/ui/tooltip.d.ts +0 -6
  167. package/dist/src/components/ui/tooltip.js +0 -3
  168. package/dist/src/hooks/use-debounced.d.ts +0 -1
  169. package/dist/src/hooks/use-debounced.js +0 -18
  170. package/dist/src/hooks/use-fetch-flows.d.ts +0 -1
  171. package/dist/src/hooks/use-fetch-flows.js +0 -26
  172. package/dist/src/hooks/use-mobile.d.ts +0 -1
  173. package/dist/src/hooks/use-mobile.js +0 -15
  174. package/dist/src/hooks/use-update-handle-positions.d.ts +0 -10
  175. package/dist/src/hooks/use-update-handle-positions.js +0 -35
  176. package/dist/src/lib/__tests__/utils.test.d.ts +0 -1
  177. package/dist/src/lib/__tests__/utils.test.js +0 -94
  178. package/dist/src/lib/motia-analytics.d.ts +0 -38
  179. package/dist/src/lib/motia-analytics.js +0 -132
  180. package/dist/src/lib/plugins.d.ts +0 -2
  181. package/dist/src/lib/plugins.js +0 -105
  182. package/dist/src/lib/utils.d.ts +0 -7
  183. package/dist/src/lib/utils.js +0 -34
  184. package/dist/src/main.d.ts +0 -2
  185. package/dist/src/main.js +0 -17
  186. package/dist/src/project-view-mode.d.ts +0 -1
  187. package/dist/src/project-view-mode.js +0 -20
  188. package/dist/src/publicComponents/api-node.d.ts +0 -5
  189. package/dist/src/publicComponents/api-node.js +0 -5
  190. package/dist/src/publicComponents/base-node/base-handle.d.ts +0 -9
  191. package/dist/src/publicComponents/base-node/base-handle.js +0 -8
  192. package/dist/src/publicComponents/base-node/base-node.d.ts +0 -15
  193. package/dist/src/publicComponents/base-node/base-node.js +0 -30
  194. package/dist/src/publicComponents/base-node/code-display.d.ts +0 -9
  195. package/dist/src/publicComponents/base-node/code-display.js +0 -64
  196. package/dist/src/publicComponents/base-node/emits.d.ts +0 -5
  197. package/dist/src/publicComponents/base-node/emits.js +0 -5
  198. package/dist/src/publicComponents/base-node/feature-card.d.ts +0 -10
  199. package/dist/src/publicComponents/base-node/feature-card.js +0 -5
  200. package/dist/src/publicComponents/base-node/language-indicator.d.ts +0 -10
  201. package/dist/src/publicComponents/base-node/language-indicator.js +0 -29
  202. package/dist/src/publicComponents/base-node/node-header.d.ts +0 -13
  203. package/dist/src/publicComponents/base-node/node-header.js +0 -30
  204. package/dist/src/publicComponents/base-node/node-sidebar.d.ts +0 -14
  205. package/dist/src/publicComponents/base-node/node-sidebar.js +0 -9
  206. package/dist/src/publicComponents/base-node/subscribe.d.ts +0 -4
  207. package/dist/src/publicComponents/base-node/subscribe.js +0 -4
  208. package/dist/src/publicComponents/cron-node.d.ts +0 -4
  209. package/dist/src/publicComponents/cron-node.js +0 -6
  210. package/dist/src/publicComponents/event-node.d.ts +0 -4
  211. package/dist/src/publicComponents/event-node.js +0 -5
  212. package/dist/src/publicComponents/node-props.d.ts +0 -21
  213. package/dist/src/publicComponents/node-props.js +0 -1
  214. package/dist/src/publicComponents/noop-node.d.ts +0 -4
  215. package/dist/src/publicComponents/noop-node.js +0 -5
  216. package/dist/src/setupTests.d.ts +0 -1
  217. package/dist/src/setupTests.js +0 -1
  218. package/dist/src/stores/use-app-tabs-store.d.ts +0 -16
  219. package/dist/src/stores/use-app-tabs-store.js +0 -31
  220. package/dist/src/stores/use-flow-store.d.ts +0 -21
  221. package/dist/src/stores/use-flow-store.js +0 -16
  222. package/dist/src/stores/use-global-store.d.ts +0 -18
  223. package/dist/src/stores/use-global-store.js +0 -12
  224. package/dist/src/stores/use-motia-config-store.d.ts +0 -12
  225. package/dist/src/stores/use-motia-config-store.js +0 -24
  226. package/dist/src/stores/use-tabs-store.d.ts +0 -19
  227. package/dist/src/stores/use-tabs-store.js +0 -22
  228. package/dist/src/system-view-mode.d.ts +0 -1
  229. package/dist/src/system-view-mode.js +0 -10
  230. package/dist/src/types/endpoint.d.ts +0 -14
  231. package/dist/src/types/endpoint.js +0 -1
  232. package/dist/src/types/file.d.ts +0 -7
  233. package/dist/src/types/file.js +0 -1
  234. package/dist/src/types/flow.d.ts +0 -115
  235. package/dist/src/types/flow.js +0 -1
  236. package/dist/tsconfig.app.tsbuildinfo +0 -1
  237. package/dist/tsconfig.node.tsbuildinfo +0 -1
  238. /package/dist/src/{assets → src/assets}/.empty +0 -0
  239. /package/dist/src/{assets → src/assets}/motia-dark.png +0 -0
  240. /package/dist/src/{assets → src/assets}/motia-light.png +0 -0
@@ -0,0 +1 @@
1
+ {"version":3,"file":"middleware.js","names":["path","normalizePath","filePath","replace","isLocalPlugin","packageName","startsWith","resolveLocalPath","join","process","cwd","resolveNpmPath","ResolvedPackage","WorkbenchPlugin","isLocalPlugin","normalizePath","resolveLocalPath","resolveNpmPath","resolvePluginPackage","plugin","packageName","local","resolvedPath","isLocal","alias","createAliasConfig","plugins","Record","uniquePackages","Array","from","Set","map","p","aliases","resolved","resolveAllPlugins","getUniquePackageNames","getUniquePackageNames","WorkbenchPlugin","generateImports","packages","map","packageName","index","join","generatePackageMap","entries","generatePluginLogic","plugins","JSON","stringify","generatePluginCode","length","imports","packageMap","logic","generateCssImports","cssImports","flatMap","plugin","filter","cssImport","trim","isValidCode","code","WorkbenchPlugin","packageName","componentName","position","label","labelIcon","cssImports","props","Record","ValidationResult","valid","errors","warnings","plugin","PluginContext","plugins","server","ResolvedPackage","resolvedPath","isLocal","alias","VirtualModuleExports","ProcessedPlugin","component","isValidPosition","CONSTANTS","VIRTUAL_MODULE_ID","RESOLVED_VIRTUAL_MODULE_ID","LOG_PREFIX","DEFAULTS","POSITION","const","LABEL","ICON","PROPS","Printer","path","HmrContext","ModuleNode","resolvePluginPackage","WorkbenchPlugin","CONSTANTS","isLocalPlugin","normalizePath","WATCHED_EXTENSIONS","isConfigFile","file","normalizedFile","endsWith","shouldInvalidatePlugins","plugins","absoluteFile","isAbsolute","resolve","process","cwd","hasWatchedExtension","some","ext","plugin","packageName","resolved","pluginAbsolutePath","resolvedPath","normalizedPluginPath","sep","startsWith","handlePluginHotUpdate","ctx","printer","server","timestamp","printPluginLog","printPluginWarn","ws","send","type","virtualModule","moduleGraph","getModuleById","RESOLVED_VIRTUAL_MODULE_ID","invalidateModule","Set","modulesToUpdateSet","processedModules","importer","importers","has","add","modulesToUpdate","Array","from","length","existsSync","z","ValidationResult","WorkbenchPlugin","CONSTANTS","isValidPosition","isLocalPlugin","resolveLocalPath","WorkbenchPluginSchema","object","packageName","string","min","refine","name","startsWith","test","message","componentName","optional","position","enum","pos","undefined","label","labelIcon","cssImports","array","props","record","any","validatePluginConfig","plugin","index","errors","warnings","valid","result","safeParse","success","error","issues","forEach","err","core","$ZodIssue","path","join","push","validatedPlugin","data","resolvedPath","DEFAULTS","LABEL","ICON","POSITION","Object","keys","length","cssImport","cssIndex","trim","validatePlugins","plugins","options","failFast","allErrors","allWarnings","validatedPlugins","Array","isArray","console","warn","i","packageNames","map","p","duplicates","filter","indexOf","uniqueDuplicates","Set","dup","log","Printer","path","Plugin","ViteDevServer","generateCssImports","generatePluginCode","isValidCode","handlePluginHotUpdate","createAliasConfig","resolvePluginPackage","WorkbenchPlugin","CONSTANTS","isLocalPlugin","normalizePath","validatePlugins","printer","process","cwd","motiaPluginsPlugin","plugins","devServer","validationResult","failFast","valid","printPluginError","err","errors","Error","warnings","length","warning","printPluginWarn","error","alias","printPluginLog","name","enforce","buildStart","config","resolve","configureServer","server","configPaths","join","configPath","watcher","add","localPlugins","filter","p","packageName","plugin","resolved","watchPath","resolvedPath","on","file","normalizedFile","resolveId","id","VIRTUAL_MODULE_ID","RESOLVED_VIRTUAL_MODULE_ID","load","code","transform","normalizedId","endsWith","cssImports","map","handleHotUpdate","ctx","modulesToUpdate","merged","Array","from","Set","modules","buildEnd","react","Express","NextFunction","Request","Response","fs","path","fileURLToPath","createServer","createViteServer","motiaPluginsPlugin","WorkbenchPlugin","workbenchBasePlugin","workbenchBase","name","transformIndexHtml","html","replace","JSON","stringify","processCwdPlugin","cwd","process","reoPlugin","isAnalyticsEnabled","env","MOTIA_ANALYTICS_DISABLED","ApplyMiddlewareParams","app","port","plugins","__dirname","dirname","import","meta","url","applyMiddleware","vite","appType","root","base","server","middlewareMode","allowedHosts","host","hmr","allow","join","resolve","alias","assetsInclude","use","middlewares","req","res","next","originalUrl","index","readFileSync","status","set","end","e"],"sources":["../motia-plugin/utils.ts","../motia-plugin/resolver.ts","../motia-plugin/generator.ts","../motia-plugin/types.ts","../motia-plugin/hmr.ts","../motia-plugin/validator.ts","../motia-plugin/index.ts","../middleware.ts"],"sourcesContent":["import path from 'path'\n\n/**\n * Normalizes a file path by replacing backslashes with forward slashes.\n * This is useful for consistent path comparisons across different operating systems.\n *\n * @param filePath - The file path to normalize\n * @returns The normalized file path with forward slashes\n *\n * @example\n * ```ts\n * normalizePath('C:\\\\Users\\\\file.ts') // Returns: 'C:/Users/file.ts'\n * normalizePath('/Users/file.ts') // Returns: '/Users/file.ts'\n * ```\n */\nexport function normalizePath(filePath: string): string {\n return filePath.replace(/\\\\/g, '/')\n}\n\n/**\n * Checks if a package name represents a local plugin (starts with ~/).\n *\n * @param packageName - The package name to check\n * @returns True if the package is a local plugin\n *\n * @example\n * ```ts\n * isLocalPlugin('~/plugins/my-plugin') // Returns: true\n * isLocalPlugin('@my-org/my-plugin') // Returns: false\n * isLocalPlugin('my-plugin') // Returns: false\n * ```\n */\nexport function isLocalPlugin(packageName: string): boolean {\n return packageName.startsWith('~/')\n}\n\n/**\n * Resolves a local plugin path to an absolute path.\n * Strips the ~/ prefix and joins with the current working directory.\n *\n * @param packageName - The local plugin package name (must start with ~/)\n * @returns The absolute path to the local plugin\n *\n * @example\n * ```ts\n * // If cwd is /Users/project\n * resolveLocalPath('~/plugins/my-plugin')\n * // Returns: '/Users/project/plugins/my-plugin'\n * ```\n */\nexport function resolveLocalPath(packageName: string): string {\n return path.join(process.cwd(), packageName.replace('~/', ''))\n}\n\n/**\n * Resolves an npm package path to the node_modules directory.\n *\n * @param packageName - The npm package name\n * @returns The absolute path to the package in node_modules\n *\n * @example\n * ```ts\n * // If cwd is /Users/project\n * resolveNpmPath('@my-org/my-plugin')\n * // Returns: '/Users/project/node_modules/@my-org/my-plugin'\n * ```\n */\nexport function resolveNpmPath(packageName: string): string {\n return path.join(process.cwd(), 'node_modules', packageName)\n}\n","import type { ResolvedPackage, WorkbenchPlugin } from './types'\nimport { isLocalPlugin, normalizePath, resolveLocalPath, resolveNpmPath } from './utils'\n\n/**\n * Resolves a plugin package to its absolute path and creates an alias.\n *\n * @param plugin - The plugin configuration to resolve\n * @returns Resolved package information including path and alias\n *\n * @example\n * ```ts\n * // Local plugin\n * resolvePluginPackage({ packageName: '~/plugins/my-plugin' })\n * // Returns: {\n * // packageName: '~/plugins/my-plugin',\n * // resolvedPath: '/Users/project/plugins/my-plugin',\n * // isLocal: true,\n * // alias: '~/plugins/my-plugin'\n * // }\n *\n * // NPM package\n * resolvePluginPackage({ packageName: '@org/plugin' })\n * // Returns: {\n * // packageName: '@org/plugin',\n * // resolvedPath: '/Users/project/node_modules/@org/plugin',\n * // isLocal: false,\n * // alias: '@org/plugin'\n * // }\n * ```\n */\nexport function resolvePluginPackage(plugin: WorkbenchPlugin): ResolvedPackage {\n const { packageName } = plugin\n const local = isLocalPlugin(packageName)\n\n const resolvedPath = local ? resolveLocalPath(packageName) : resolveNpmPath(packageName)\n\n return {\n packageName,\n resolvedPath: normalizePath(resolvedPath),\n isLocal: local,\n alias: packageName,\n }\n}\n\n/**\n * Resolves all plugin packages and creates a Vite alias configuration.\n *\n * @param plugins - Array of plugin configurations\n * @returns Vite alias configuration object\n *\n * @example\n * ```ts\n * const plugins = [\n * { packageName: '~/plugins/local' },\n * { packageName: '@org/npm-plugin' }\n * ]\n * const aliases = createAliasConfig(plugins)\n * // Returns: {\n * // '~/plugins/local': '/Users/project/plugins/local',\n * // '@org/npm-plugin': '/Users/project/node_modules/@org/npm-plugin'\n * // }\n * ```\n */\nexport function createAliasConfig(plugins: WorkbenchPlugin[]): Record<string, string> {\n // Get unique package names to avoid duplicate aliases\n const uniquePackages = Array.from(new Set(plugins.map((p) => p.packageName)))\n\n const aliases: Record<string, string> = {}\n\n for (const packageName of uniquePackages) {\n const resolved = resolvePluginPackage({ packageName } as WorkbenchPlugin)\n aliases[packageName] = resolved.resolvedPath\n }\n\n return aliases\n}\n\n/**\n * Resolves all plugins and returns their resolved package information.\n *\n * @param plugins - Array of plugin configurations\n * @returns Array of resolved package information\n */\nexport function resolveAllPlugins(plugins: WorkbenchPlugin[]): ResolvedPackage[] {\n return plugins.map((plugin) => resolvePluginPackage(plugin))\n}\n\n/**\n * Gets the unique set of package names from plugins.\n *\n * @param plugins - Array of plugin configurations\n * @returns Array of unique package names\n */\nexport function getUniquePackageNames(plugins: WorkbenchPlugin[]): string[] {\n return Array.from(new Set(plugins.map((p) => p.packageName)))\n}\n","import { getUniquePackageNames } from './resolver'\nimport type { WorkbenchPlugin } from './types'\n\n/**\n * Generates import statements for all unique plugin packages.\n *\n * @param packages - Array of unique package names\n * @returns JavaScript code string with import statements\n *\n * @example\n * ```ts\n * generateImports(['@org/plugin-1', '~/plugins/local'])\n * // Returns:\n * // import * as plugin_0 from '@org/plugin-1'\n * // import * as plugin_1 from '~/plugins/local'\n * ```\n */\nexport function generateImports(packages: string[]): string {\n return packages.map((packageName, index) => `import * as plugin_${index} from '${packageName}'`).join('\\n')\n}\n\n/**\n * Generates the package map that links package names to their imported modules.\n *\n * @param packages - Array of unique package names\n * @returns JavaScript code string defining the package map\n *\n * @example\n * ```ts\n * generatePackageMap(['@org/plugin-1', '~/plugins/local'])\n * // Returns: const packageMap = {'@org/plugin-1': plugin_0,'~/plugins/local': plugin_1}\n * ```\n */\nexport function generatePackageMap(packages: string[]): string {\n const entries = packages.map((packageName, index) => `'${packageName}': plugin_${index}`)\n return `const packageMap = {${entries.join(',')}}`\n}\n\n/**\n * Generates the plugin transformation logic that processes plugin configurations.\n *\n * @param plugins - Array of plugin configurations\n * @returns JavaScript code string with plugin processing logic\n */\nexport function generatePluginLogic(plugins: WorkbenchPlugin[]): string {\n return `\n const motiaPlugins = ${JSON.stringify(plugins)}\n\n export const plugins = motiaPlugins.map((plugin) => {\n const component = packageMap[plugin.packageName]\n const config = component.config || {}\n const componentName = config.componentName || plugin.componentName\n\n return {\n label: plugin.label || config.label || 'Plugin label',\n labelIcon: plugin.labelIcon || config.labelIcon || 'toy-brick',\n position: plugin.position || config.position || 'top',\n props: plugin.props || config.props || {},\n component: componentName ? component[componentName] : component.default,\n }\n })\n`\n}\n\n/**\n * Generates the complete virtual module code for all plugins.\n * This is the main code generation function that combines all parts.\n *\n * @param plugins - Array of plugin configurations\n * @returns Complete JavaScript code string for the virtual module\n *\n * @example\n * ```ts\n * const plugins = [\n * { packageName: '@test/plugin', label: 'Test' }\n * ]\n * const code = generatePluginCode(plugins)\n * // Returns complete module code with imports, map, and logic\n * ```\n */\nexport function generatePluginCode(plugins: WorkbenchPlugin[]): string {\n if (!plugins || plugins.length === 0) {\n return 'export const plugins = []'\n }\n\n const packages = getUniquePackageNames(plugins)\n const imports = generateImports(packages)\n const packageMap = generatePackageMap(packages)\n const logic = generatePluginLogic(plugins)\n\n return `${imports}\n${packageMap}\n${logic}`\n}\n\n/**\n * Generates CSS imports for plugins that specify cssImports.\n *\n * @param plugins - Array of plugin configurations\n * @returns CSS import statements as a string\n *\n * @example\n * ```ts\n * const plugins = [\n * { packageName: '@test/plugin', cssImports: ['styles.css', 'theme.css'] }\n * ]\n * generateCssImports(plugins)\n * // Returns:\n * // @import 'styles.css';\n * // @import 'theme.css';\n * ```\n */\nexport function generateCssImports(plugins: WorkbenchPlugin[]): string {\n const cssImports = plugins\n .flatMap((plugin) => plugin.cssImports || [])\n .filter((cssImport) => cssImport && cssImport.trim() !== '')\n .map((cssImport) => `@import '${cssImport}';`)\n\n return cssImports.join('\\n')\n}\n\n/**\n * Checks if the generated code is valid (non-empty and has content).\n *\n * @param code - The generated code to check\n * @returns True if code is valid\n */\nexport function isValidCode(code: string): boolean {\n return typeof code === 'string' && code.trim().length > 0\n}\n","/**\n * Configuration for a single workbench plugin.\n * This interface defines how plugins are registered and configured in the Motia workbench.\n */\nexport interface WorkbenchPlugin {\n /**\n * The package name or local path to the plugin.\n * - For npm packages: use the package name (e.g., '@my-org/my-plugin')\n * - For local plugins: use the tilde prefix (e.g., '~/plugins/my-plugin')\n */\n packageName: string\n\n /**\n * Optional named export to use from the plugin package.\n * If not specified, the default export will be used.\n * Can be overridden by the plugin's own config.\n */\n componentName?: string\n\n /**\n * Position where the plugin tab should appear in the workbench.\n * - 'top': Display in the top tab bar\n * - 'bottom': Display in the bottom tab bar\n * @default 'top'\n */\n position?: 'top' | 'bottom'\n\n /**\n * Display label for the plugin tab.\n * Can be overridden by the plugin's own config.\n */\n label?: string\n\n /**\n * Icon name from lucide-react to display next to the label.\n * Can be overridden by the plugin's own config.\n * @default 'toy-brick'\n */\n labelIcon?: string\n\n /**\n * Array of CSS package imports to inject into the workbench.\n * These will be added to the main index.css file.\n * Example: ['@my-org/my-plugin/styles.css']\n */\n cssImports?: string[]\n\n /**\n * Props to pass to the plugin component when it's rendered.\n * Can be overridden by the plugin's own config.\n */\n props?: Record<string, any>\n}\n\n/**\n * Result of validating a plugin configuration.\n */\nexport interface ValidationResult {\n /**\n * Whether the validation passed\n */\n valid: boolean\n\n /**\n * Array of error messages if validation failed\n */\n errors: string[]\n\n /**\n * Array of warning messages for non-critical issues\n */\n warnings: string[]\n\n /**\n * The validated and normalized plugin configuration (if valid)\n */\n plugin?: WorkbenchPlugin\n}\n\n/**\n * Context object passed to various plugin internals functions.\n * Contains shared state and configuration.\n */\nexport interface PluginContext {\n /**\n * Array of plugin configurations\n */\n plugins: WorkbenchPlugin[]\n\n /**\n * Vite dev server instance (only available in dev mode)\n */\n server?: any\n}\n\n/**\n * Package resolution result.\n */\nexport interface ResolvedPackage {\n /**\n * The original package name from the configuration\n */\n packageName: string\n\n /**\n * Resolved absolute path to the package\n */\n resolvedPath: string\n\n /**\n * Whether this is a local plugin (starts with ~/)\n */\n isLocal: boolean\n\n /**\n * Alias to use for imports\n */\n alias: string\n}\n\n/**\n * Generated virtual module exports interface.\n * This is what consumers will import from 'virtual:motia-plugins'.\n */\nexport interface VirtualModuleExports {\n /**\n * Array of processed plugin configurations ready to be registered\n */\n plugins: ProcessedPlugin[]\n}\n\n/**\n * A plugin configuration after processing and normalization.\n * This is the format used by the workbench to register tabs.\n */\nexport interface ProcessedPlugin {\n /**\n * Display label for the plugin tab\n */\n label: string\n\n /**\n * Icon name from lucide-react\n */\n labelIcon: string\n\n /**\n * Position in the workbench ('top' or 'bottom')\n */\n position: 'top' | 'bottom'\n\n /**\n * Props to pass to the component\n */\n props: Record<string, any>\n\n /**\n * The React component to render\n */\n component: any\n}\n\n/**\n * Type guard to check if position is valid.\n */\nexport function isValidPosition(position: any): position is 'top' | 'bottom' {\n return position === 'top' || position === 'bottom'\n}\n\n/**\n * Constants used throughout the plugin system.\n */\nexport const CONSTANTS = {\n /**\n * Virtual module ID for importing plugins\n */\n VIRTUAL_MODULE_ID: 'virtual:motia-plugins',\n\n /**\n * Resolved virtual module ID (with null byte prefix for Vite)\n */\n RESOLVED_VIRTUAL_MODULE_ID: '\\0virtual:motia-plugins',\n\n /**\n * Log prefix for all plugin messages\n */\n LOG_PREFIX: '[motia-plugins]',\n\n /**\n * Default values for optional plugin fields\n */\n DEFAULTS: {\n POSITION: 'top' as const,\n LABEL: 'Plugin label',\n ICON: 'toy-brick',\n PROPS: {},\n },\n} as const\n","import type { Printer } from '@motiadev/core'\nimport path from 'path'\nimport type { HmrContext, ModuleNode } from 'vite'\nimport { resolvePluginPackage } from './resolver'\nimport type { WorkbenchPlugin } from './types'\nimport { CONSTANTS } from './types'\nimport { isLocalPlugin, normalizePath } from './utils'\n\nconst WATCHED_EXTENSIONS = ['.ts', '.tsx', '.js', '.jsx', '.css', '.scss', '.less']\n\nexport function isConfigFile(file: string): boolean {\n const normalizedFile = normalizePath(file)\n return normalizedFile.endsWith('motia.config.ts') || normalizedFile.endsWith('motia.config.js')\n}\n\n/**\n * Checks if a file change should trigger HMR for plugins.\n *\n * @param file - The file path that changed\n * @param plugins - Current plugin configurations\n * @returns True if the change affects plugins\n */\nexport function shouldInvalidatePlugins(file: string, plugins: WorkbenchPlugin[]): boolean {\n const normalizedFile = normalizePath(file)\n const absoluteFile = path.isAbsolute(normalizedFile) ? normalizedFile : path.resolve(process.cwd(), normalizedFile)\n\n if (isConfigFile(file)) {\n return true\n }\n\n const hasWatchedExtension = WATCHED_EXTENSIONS.some((ext) => absoluteFile.endsWith(ext))\n if (!hasWatchedExtension) {\n return false\n }\n\n for (const plugin of plugins) {\n if (isLocalPlugin(plugin.packageName)) {\n const resolved = resolvePluginPackage(plugin)\n const pluginAbsolutePath = path.isAbsolute(resolved.resolvedPath)\n ? resolved.resolvedPath\n : path.resolve(process.cwd(), resolved.resolvedPath)\n\n const normalizedPluginPath = pluginAbsolutePath.endsWith(path.sep)\n ? pluginAbsolutePath\n : `${pluginAbsolutePath}${path.sep}`\n\n if (absoluteFile.startsWith(normalizedPluginPath) || absoluteFile === pluginAbsolutePath) {\n return true\n }\n }\n }\n\n return false\n}\n\n/**\n * Handles hot updates for the plugin system.\n * This function is called by Vite's handleHotUpdate hook.\n *\n * @param ctx - Vite's HMR context\n * @param plugins - Current plugin configurations\n * @param printer - Printer instance for logging\n * @returns Array of modules to update, or undefined to continue with default behavior\n */\nexport function handlePluginHotUpdate(\n ctx: HmrContext,\n plugins: WorkbenchPlugin[],\n printer: Printer,\n): ModuleNode[] | undefined {\n const { file, server, timestamp } = ctx\n\n printer.printPluginLog(`HMR: File changed: ${normalizePath(file)}`)\n\n if (isConfigFile(file)) {\n printer.printPluginLog('HMR: Config file changed, triggering full page reload')\n printer.printPluginWarn(\n 'Configuration changes require a server restart for full effect. Please restart the dev server to apply all changes.',\n )\n server.ws.send({\n type: 'full-reload',\n path: '*',\n })\n return\n }\n\n if (!shouldInvalidatePlugins(file, plugins)) {\n printer.printPluginLog('HMR: Change outside plugin scope, delegating to Vite default handling')\n return\n }\n\n printer.printPluginLog('HMR: Plugin change detected, invalidating virtual module')\n\n const virtualModule = server.moduleGraph.getModuleById(CONSTANTS.RESOLVED_VIRTUAL_MODULE_ID)\n\n if (!virtualModule) {\n printer.printPluginWarn('HMR: Virtual module not found, triggering full reload as fallback')\n server.ws.send({\n type: 'full-reload',\n path: '*',\n })\n return\n }\n\n server.moduleGraph.invalidateModule(virtualModule, new Set(), timestamp)\n printer.printPluginLog('HMR: Virtual module invalidated')\n\n const modulesToUpdateSet = new Set<ModuleNode>([virtualModule])\n const processedModules = new Set<ModuleNode>([virtualModule])\n\n for (const importer of virtualModule.importers) {\n if (!processedModules.has(importer)) {\n processedModules.add(importer)\n modulesToUpdateSet.add(importer)\n server.moduleGraph.invalidateModule(importer, new Set(), timestamp)\n }\n }\n\n const modulesToUpdate = Array.from(modulesToUpdateSet)\n\n printer.printPluginLog(`HMR: Updated ${modulesToUpdate.length} module(s)`)\n\n return modulesToUpdate\n}\n","import { existsSync } from 'fs'\nimport { z } from 'zod'\nimport type { ValidationResult, WorkbenchPlugin } from './types'\nimport { CONSTANTS, isValidPosition } from './types'\nimport { isLocalPlugin, resolveLocalPath } from './utils'\n\n/**\n * Zod schema for WorkbenchPlugin configuration.\n * Provides runtime type validation with detailed error messages.\n */\nconst WorkbenchPluginSchema = z.object({\n packageName: z\n .string()\n .min(1, 'packageName is required and cannot be empty')\n .refine((name) => name.startsWith('~/') || name.startsWith('@') || /^[a-z0-9-_]+$/i.test(name), {\n message: 'packageName must be a valid npm package name or local path (starting with ~/)',\n }),\n\n componentName: z.string().optional(),\n\n position: z\n .enum(['top', 'bottom'])\n .optional()\n .refine((pos) => pos === undefined || isValidPosition(pos), {\n message: 'position must be either \"top\" or \"bottom\"',\n }),\n\n label: z.string().optional(),\n\n labelIcon: z.string().optional(),\n\n cssImports: z.array(z.string()).optional(),\n\n props: z.record(z.any(), z.any()).optional(),\n})\n\n/**\n * Validates a single plugin configuration.\n *\n * @param plugin - The plugin configuration to validate\n * @param index - The index of the plugin in the array (for error messages)\n * @returns A validation result with errors, warnings, and normalized plugin\n */\nexport function validatePluginConfig(plugin: any, index: number): ValidationResult {\n const errors: string[] = []\n const warnings: string[] = []\n\n if (typeof plugin !== 'object' || plugin === null) {\n return {\n valid: false,\n errors: [`Plugin at index ${index}: expected object, got ${typeof plugin}`],\n warnings: [],\n }\n }\n\n try {\n const result = WorkbenchPluginSchema.safeParse(plugin)\n\n if (!result.success) {\n result.error.issues.forEach((err: z.core.$ZodIssue) => {\n const path = err.path.join('.')\n errors.push(`Plugin at index ${index}, field \"${path}\": ${err.message}`)\n })\n\n return { valid: false, errors, warnings }\n }\n\n const validatedPlugin = result.data as WorkbenchPlugin\n\n if (isLocalPlugin(validatedPlugin.packageName)) {\n const resolvedPath = resolveLocalPath(validatedPlugin.packageName)\n if (!existsSync(resolvedPath)) {\n warnings.push(\n `Plugin at index ${index}: local path \"${validatedPlugin.packageName}\" does not exist at \"${resolvedPath}\". ` +\n `Make sure the path is correct relative to the project root.`,\n )\n }\n }\n\n if (!validatedPlugin.label) {\n warnings.push(`Plugin at index ${index}: \"label\" not specified, will use default \"${CONSTANTS.DEFAULTS.LABEL}\"`)\n }\n\n if (!validatedPlugin.labelIcon) {\n warnings.push(\n `Plugin at index ${index}: \"labelIcon\" not specified, will use default \"${CONSTANTS.DEFAULTS.ICON}\"`,\n )\n }\n\n if (!validatedPlugin.position) {\n warnings.push(\n `Plugin at index ${index}: \"position\" not specified, will use default \"${CONSTANTS.DEFAULTS.POSITION}\"`,\n )\n }\n\n if (validatedPlugin.props && Object.keys(validatedPlugin.props).length === 0) {\n warnings.push(`Plugin at index ${index}: \"props\" is an empty object`)\n }\n\n if (validatedPlugin.cssImports) {\n if (validatedPlugin.cssImports.length === 0) {\n warnings.push(`Plugin at index ${index}: \"cssImports\" is an empty array`)\n }\n\n validatedPlugin.cssImports.forEach((cssImport, cssIndex) => {\n if (!cssImport || cssImport.trim() === '') {\n warnings.push(`Plugin at index ${index}: cssImport at index ${cssIndex} is empty or whitespace`)\n }\n })\n }\n\n return {\n valid: true,\n errors: [],\n warnings,\n plugin: validatedPlugin,\n }\n } catch (error) {\n return {\n valid: false,\n errors: [`Plugin at index ${index}: unexpected validation error: ${error}`],\n warnings: [],\n }\n }\n}\n\n/**\n * Validates an array of plugin configurations.\n *\n * @param plugins - Array of plugin configurations to validate\n * @param options - Validation options\n * @returns Combined validation result for all plugins\n */\nexport function validatePlugins(plugins: any[], options: { failFast?: boolean } = {}): ValidationResult {\n const allErrors: string[] = []\n const allWarnings: string[] = []\n const validatedPlugins: WorkbenchPlugin[] = []\n\n if (!Array.isArray(plugins)) {\n return {\n valid: false,\n errors: [`Expected plugins to be an array, got ${typeof plugins}`],\n warnings: [],\n }\n }\n\n if (plugins.length === 0) {\n console.warn('[motia-plugins] No plugins provided to validate')\n return {\n valid: true,\n errors: [],\n warnings: ['No plugins configured'],\n }\n }\n\n for (let i = 0; i < plugins.length; i++) {\n const result = validatePluginConfig(plugins[i], i)\n\n allErrors.push(...result.errors)\n allWarnings.push(...result.warnings)\n\n if (result.valid && result.plugin) {\n validatedPlugins.push(result.plugin)\n }\n\n if (options.failFast && result.errors.length > 0) {\n break\n }\n }\n\n const packageNames = validatedPlugins.map((p) => p.packageName)\n const duplicates = packageNames.filter((name, index) => packageNames.indexOf(name) !== index)\n\n if (duplicates.length > 0) {\n const uniqueDuplicates = [...new Set(duplicates)]\n uniqueDuplicates.forEach((dup) => {\n allWarnings.push(`Duplicate package name found: \"${dup}\". This may cause conflicts.`)\n })\n }\n\n const valid = allErrors.length === 0\n\n if (valid) {\n console.log(`[motia-plugins] ✓ Validated ${validatedPlugins.length} plugin(s) successfully`)\n if (allWarnings.length > 0) {\n console.warn(`[motia-plugins] Found ${allWarnings.length} warning(s)`)\n }\n } else {\n console.error(`[motia-plugins] ✗ Validation failed with ${allErrors.length} error(s)`)\n }\n\n return {\n valid,\n errors: allErrors,\n warnings: allWarnings,\n }\n}\n","import { Printer } from '@motiadev/core'\nimport path from 'path'\nimport type { Plugin, ViteDevServer } from 'vite'\nimport { generateCssImports, generatePluginCode, isValidCode } from './generator'\nimport { handlePluginHotUpdate } from './hmr'\nimport { createAliasConfig, resolvePluginPackage } from './resolver'\nimport type { WorkbenchPlugin } from './types'\nimport { CONSTANTS } from './types'\nimport { isLocalPlugin, normalizePath } from './utils'\nimport { validatePlugins } from './validator'\n\n/**\n * Vite plugin for loading and managing Motia workbench plugins.\n *\n * Features:\n * - Hot Module Replacement (HMR) support\n * - Runtime validation with detailed error messages\n * - Verbose logging for debugging\n * - CSS injection for plugin styles\n *\n * @param plugins - Array of plugin configurations\n * @param options - Optional loader configuration\n * @returns Vite plugin instance\n *\n * @example\n * ```ts\n * export default defineConfig({\n * plugins: [\n * motiaPluginsPlugin([\n * { packageName: '@my-org/plugin', label: 'My Plugin' }\n * ])\n * ]\n * })\n * ```\n */\nconst printer = new Printer(process.cwd())\n\nexport default function motiaPluginsPlugin(plugins: WorkbenchPlugin[]): Plugin {\n let devServer: ViteDevServer | null = null\n\n try {\n const validationResult = validatePlugins(plugins, {\n failFast: false,\n })\n\n if (!validationResult.valid) {\n printer.printPluginError('Plugin configuration validation failed:')\n for (const err of validationResult.errors) {\n printer.printPluginError(` ${err}`)\n }\n throw new Error('Invalid plugin configuration. See errors above.')\n }\n\n if (validationResult.warnings.length > 0) {\n for (const warning of validationResult.warnings) {\n printer.printPluginWarn(warning)\n }\n }\n } catch (error) {\n printer.printPluginError(`Failed to validate plugins: ${error}`)\n throw error\n }\n\n const alias = createAliasConfig(plugins)\n\n printer.printPluginLog(`Initialized with ${plugins.length} plugin(s)`)\n\n return {\n name: 'vite-plugin-motia-plugins',\n enforce: 'pre',\n\n buildStart() {\n printer.printPluginLog('Build started')\n },\n\n config: () => ({\n resolve: {\n alias,\n },\n }),\n\n configureServer(server) {\n devServer = server\n printer.printPluginLog('Dev server configured, HMR enabled')\n\n const configPaths = [path.join(process.cwd(), 'motia.config.ts'), path.join(process.cwd(), 'motia.config.js')]\n\n for (const configPath of configPaths) {\n server.watcher.add(configPath)\n }\n printer.printPluginLog('Watching for config file changes')\n\n const localPlugins = plugins.filter((p) => isLocalPlugin(p.packageName))\n if (localPlugins.length > 0) {\n printer.printPluginLog(`Watching ${localPlugins.length} local plugin(s)`)\n\n for (const plugin of localPlugins) {\n const resolved = resolvePluginPackage(plugin)\n const watchPath = resolved.resolvedPath\n\n server.watcher.add(watchPath)\n printer.printPluginLog(`Watching: ${watchPath}`)\n }\n\n server.watcher.on('change', (file) => {\n const normalizedFile = normalizePath(file)\n printer.printPluginLog(`File watcher detected change: ${normalizedFile}`)\n })\n\n server.watcher.on('add', (file) => {\n const normalizedFile = normalizePath(file)\n printer.printPluginLog(`File watcher detected new file: ${normalizedFile}`)\n })\n }\n },\n\n resolveId(id) {\n if (id === CONSTANTS.VIRTUAL_MODULE_ID) {\n return CONSTANTS.RESOLVED_VIRTUAL_MODULE_ID\n }\n },\n\n load(id) {\n if (id !== CONSTANTS.RESOLVED_VIRTUAL_MODULE_ID) {\n return null\n }\n\n printer.printPluginLog('Loading plugins virtual module')\n printer.printPluginLog('Generating plugin code...')\n\n const code = generatePluginCode(plugins)\n\n if (!isValidCode(code)) {\n printer.printPluginError('Generated code is invalid or empty')\n return 'export const plugins = []'\n }\n\n printer.printPluginLog('Plugin code generated successfully')\n\n return code\n },\n\n async transform(code, id) {\n const normalizedId = normalizePath(id)\n\n if (!normalizedId.endsWith('src/index.css')) {\n return null\n }\n\n printer.printPluginLog('Injecting plugin CSS imports')\n\n const cssImports = generateCssImports(plugins)\n\n if (!cssImports) {\n return null\n }\n\n return {\n code: `${cssImports}\\n${code}`,\n map: null,\n }\n },\n\n handleHotUpdate(ctx) {\n if (!devServer) {\n printer.printPluginWarn('HMR: Dev server not available')\n return\n }\n\n const modulesToUpdate = handlePluginHotUpdate(ctx, plugins, printer)\n\n if (modulesToUpdate && modulesToUpdate.length > 0) {\n const merged = Array.from(new Set([...(ctx.modules || []), ...modulesToUpdate]))\n printer.printPluginLog(`HMR: Successfully updated ${merged.length} module(s)`)\n return merged\n }\n },\n\n buildEnd() {\n printer.printPluginLog('Build ended')\n },\n }\n}\n","import react from '@vitejs/plugin-react'\nimport type { Express, NextFunction, Request, Response } from 'express'\nimport fs from 'fs'\nimport path from 'path'\nimport { fileURLToPath } from 'url'\nimport { createServer as createViteServer } from 'vite'\nimport motiaPluginsPlugin from './motia-plugin/index.js'\nimport type { WorkbenchPlugin } from './motia-plugin/types.js'\n\nconst workbenchBasePlugin = (workbenchBase: string) => {\n return {\n name: 'html-transform',\n transformIndexHtml: (html: string) => {\n return html.replace('</head>', `<script>const workbenchBase = ${JSON.stringify(workbenchBase)};</script></head>`)\n },\n }\n}\n\nconst processCwdPlugin = () => {\n return {\n name: 'html-transform',\n transformIndexHtml: (html: string) => {\n // Normalize path for cross-platform compatibility\n const cwd = process.cwd().replace(/\\\\/g, '/')\n return html.replace('</head>', `<script>const processCwd = \"${cwd}\";</script></head>`)\n },\n }\n}\n\nconst reoPlugin = () => {\n return {\n name: 'html-transform',\n transformIndexHtml(html: string) {\n const isAnalyticsEnabled = process.env.MOTIA_ANALYTICS_DISABLED !== 'true'\n\n if (!isAnalyticsEnabled) {\n return html\n }\n\n // inject before </head>\n return html.replace(\n '</head>',\n `\n <script type=\"text/javascript\">\n !function(){var e,t,n;e=\"d8f0ce9cae8ae64\",t=function(){Reo.init({clientID:\"d8f0ce9cae8ae64\", source: \"internal\"})},(n=document.createElement(\"script\")).src=\"https://static.reo.dev/\"+e+\"/reo.js\",n.defer=!0,n.onload=t,document.head.appendChild(n)}();\n </script>\n </head>`,\n )\n },\n }\n}\n\nexport type ApplyMiddlewareParams = {\n app: Express\n port: number\n workbenchBase: string\n plugins: WorkbenchPlugin[]\n}\n\nconst __dirname = path.dirname(fileURLToPath(import.meta.url))\n\nexport const applyMiddleware = async ({ app, port, workbenchBase, plugins }: ApplyMiddlewareParams) => {\n const vite = await createViteServer({\n appType: 'spa',\n root: __dirname,\n base: workbenchBase,\n server: {\n middlewareMode: true,\n allowedHosts: true,\n host: true,\n hmr: { port: 21678 + port },\n fs: {\n allow: [\n __dirname, // workbench root\n path.join(process.cwd(), './steps'), // steps directory\n path.join(process.cwd(), './src'), // src directory\n path.join(process.cwd(), './tutorial'), // tutorial directory\n path.join(process.cwd(), './node_modules'), // node_modules directory\n path.join(__dirname, './node_modules'), // node_modules directory\n ],\n },\n },\n resolve: {\n alias: {\n '@': path.resolve(__dirname, './src'),\n '@/assets': path.resolve(__dirname, './src/assets'),\n 'lucide-react/dynamic': 'lucide-react/dynamic.mjs',\n 'lucide-react': 'lucide-react/dist/cjs/lucide-react.js',\n },\n },\n plugins: [\n react(),\n processCwdPlugin(),\n reoPlugin(),\n motiaPluginsPlugin(plugins),\n workbenchBasePlugin(workbenchBase),\n ],\n assetsInclude: ['**/*.png', '**/*.jpg', '**/*.jpeg', '**/*.gif', '**/*.svg', '**/*.ico', '**/*.webp', '**/*.avif'],\n })\n\n app.use(workbenchBase, vite.middlewares)\n app.use(`${workbenchBase}/*`, async (req: Request, res: Response, next: NextFunction) => {\n const url = req.originalUrl\n\n try {\n const index = fs.readFileSync(path.resolve(__dirname, 'index.html'), 'utf-8')\n const html = await vite.transformIndexHtml(url, index)\n\n res.status(200).set({ 'Content-Type': 'text/html' }).end(html)\n } catch (e) {\n next(e)\n }\n })\n}\n"],"mappings":"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"}
@@ -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
+ }