@motiadev/workbench 0.8.2-beta.140-628177 → 0.8.2-beta.140-027880

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 (142) hide show
  1. package/dist/components.json +1 -1
  2. package/dist/index.d.ts +7 -7
  3. package/dist/index.js +5 -5
  4. package/dist/middleware.d.ts +1 -1
  5. package/dist/middleware.js +3 -3
  6. package/dist/motia-plugin/__tests__/generator.test.js +97 -0
  7. package/dist/motia-plugin/__tests__/resolver.test.d.ts +1 -0
  8. package/dist/motia-plugin/__tests__/resolver.test.js +64 -0
  9. package/dist/motia-plugin/__tests__/validator.test.d.ts +1 -0
  10. package/dist/motia-plugin/__tests__/validator.test.js +59 -0
  11. package/dist/motia-plugin/generator.d.ts +78 -0
  12. package/dist/motia-plugin/generator.js +128 -0
  13. package/dist/motia-plugin/hmr.d.ts +22 -0
  14. package/dist/motia-plugin/hmr.js +116 -0
  15. package/dist/motia-plugin/index.d.ts +3 -0
  16. package/dist/motia-plugin/index.js +152 -0
  17. package/dist/motia-plugin/resolver.d.ts +63 -0
  18. package/dist/motia-plugin/resolver.js +92 -0
  19. package/dist/motia-plugin/types.d.ts +169 -0
  20. package/dist/motia-plugin/types.js +36 -0
  21. package/dist/motia-plugin/utils.d.ts +57 -0
  22. package/dist/motia-plugin/utils.js +75 -0
  23. package/dist/motia-plugin/validator.d.ts +19 -0
  24. package/dist/motia-plugin/validator.js +163 -0
  25. package/dist/postcss.config.mjs +1 -1
  26. package/dist/src/App.d.ts +1 -1
  27. package/dist/src/App.js +1 -18
  28. package/dist/src/components/flow/base-edge.d.ts +2 -2
  29. package/dist/src/components/flow/base-edge.js +1 -1
  30. package/dist/src/components/flow/flow-page.js +2 -2
  31. package/dist/src/components/flow/flow-tab-menu-item.js +2 -2
  32. package/dist/src/components/flow/flow-view.d.ts +3 -3
  33. package/dist/src/components/flow/hooks/use-get-flow-state.d.ts +2 -2
  34. package/dist/src/components/flow/hooks/use-get-flow-state.js +0 -4
  35. package/dist/src/components/flow/hooks/use-save-workflow-config.d.ts +1 -1
  36. package/dist/src/components/flow/node-organizer.d.ts +3 -3
  37. package/dist/src/components/flow/nodes/api-flow-node.d.ts +1 -1
  38. package/dist/src/components/flow/nodes/cron-flow-node.d.ts +1 -1
  39. package/dist/src/components/flow/nodes/event-flow-node.d.ts +1 -1
  40. package/dist/src/components/flow/nodes/noop-flow-node.d.ts +1 -1
  41. package/dist/src/components/header/deploy-button.js +2 -2
  42. package/dist/src/components/header/header.d.ts +1 -1
  43. package/dist/src/components/header/header.js +2 -2
  44. package/dist/src/components/root-motia.d.ts +2 -1
  45. package/dist/src/components/tutorial/engine/tutorial-engine.d.ts +1 -1
  46. package/dist/src/components/tutorial/hooks/use-tutorial-engine.d.ts +1 -1
  47. package/dist/src/components/tutorial/hooks/use-tutorial.d.ts +1 -1
  48. package/dist/src/components/tutorial/tutorial-button.d.ts +1 -1
  49. package/dist/src/components/tutorial/tutorial-button.js +1 -1
  50. package/dist/src/components/tutorial/tutorial-step.d.ts +2 -2
  51. package/dist/src/components/tutorial/tutorial-step.js +1 -1
  52. package/dist/src/components/tutorial/tutorial.css +8 -8
  53. package/dist/src/components/ui/json-editor.d.ts +1 -1
  54. package/dist/src/components/ui/json-editor.js +1 -1
  55. package/dist/src/components/ui/table.js +1 -1
  56. package/dist/src/components/ui/theme-toggle.d.ts +1 -1
  57. package/dist/src/components/ui/tooltip.d.ts +1 -1
  58. package/dist/src/hooks/use-fetch-flows.js +1 -1
  59. package/dist/src/hooks/use-update-handle-positions.d.ts +1 -1
  60. package/dist/src/index.css +5 -5
  61. package/dist/src/lib/plugins.js +3 -3
  62. package/dist/src/main.js +2 -3
  63. package/dist/src/project-view-mode.js +1 -1
  64. package/dist/src/publicComponents/api-node.d.ts +2 -2
  65. package/dist/src/publicComponents/base-node/base-handle.d.ts +3 -2
  66. package/dist/src/publicComponents/base-node/base-node.d.ts +3 -2
  67. package/dist/src/publicComponents/base-node/base-node.js +1 -1
  68. package/dist/src/publicComponents/base-node/code-display.d.ts +2 -2
  69. package/dist/src/publicComponents/base-node/code-display.js +1 -1
  70. package/dist/src/publicComponents/base-node/emits.d.ts +2 -2
  71. package/dist/src/publicComponents/base-node/feature-card.d.ts +2 -2
  72. package/dist/src/publicComponents/base-node/language-indicator.d.ts +2 -2
  73. package/dist/src/publicComponents/base-node/node-header.d.ts +3 -2
  74. package/dist/src/publicComponents/base-node/node-sidebar.d.ts +2 -2
  75. package/dist/src/publicComponents/base-node/subscribe.d.ts +1 -1
  76. package/dist/src/publicComponents/cron-node.d.ts +3 -2
  77. package/dist/src/publicComponents/event-node.d.ts +3 -2
  78. package/dist/src/publicComponents/node-props.d.ts +1 -1
  79. package/dist/src/publicComponents/noop-node.d.ts +3 -2
  80. package/dist/src/stores/use-global-store.d.ts +0 -6
  81. package/dist/src/stores/use-global-store.js +0 -6
  82. package/dist/src/types/flow.d.ts +1 -1
  83. package/dist/tsconfig.app.tsbuildinfo +1 -1
  84. package/dist/tsconfig.node.tsbuildinfo +1 -1
  85. package/motia-plugin/__tests__/generator.test.ts +129 -0
  86. package/motia-plugin/__tests__/resolver.test.ts +82 -0
  87. package/motia-plugin/__tests__/validator.test.ts +71 -0
  88. package/motia-plugin/generator.ts +130 -0
  89. package/motia-plugin/hmr.ts +140 -0
  90. package/motia-plugin/index.ts +182 -0
  91. package/motia-plugin/resolver.ts +96 -0
  92. package/motia-plugin/types.ts +198 -0
  93. package/motia-plugin/utils.ts +70 -0
  94. package/motia-plugin/validator.ts +197 -0
  95. package/package.json +9 -9
  96. package/postcss.config.mjs +1 -1
  97. package/dist/src/components/observability/events/code/function-call.d.ts +0 -13
  98. package/dist/src/components/observability/events/code/function-call.js +0 -16
  99. package/dist/src/components/observability/events/event-icon.d.ts +0 -7
  100. package/dist/src/components/observability/events/event-icon.js +0 -16
  101. package/dist/src/components/observability/events/trace-emit-event.d.ts +0 -5
  102. package/dist/src/components/observability/events/trace-emit-event.js +0 -5
  103. package/dist/src/components/observability/events/trace-event.d.ts +0 -5
  104. package/dist/src/components/observability/events/trace-event.js +0 -20
  105. package/dist/src/components/observability/events/trace-log-event.d.ts +0 -5
  106. package/dist/src/components/observability/events/trace-log-event.js +0 -5
  107. package/dist/src/components/observability/events/trace-state-event.d.ts +0 -5
  108. package/dist/src/components/observability/events/trace-state-event.js +0 -5
  109. package/dist/src/components/observability/events/trace-stream-event.d.ts +0 -5
  110. package/dist/src/components/observability/events/trace-stream-event.js +0 -5
  111. package/dist/src/components/observability/hooks/use-get-endtime.d.ts +0 -2
  112. package/dist/src/components/observability/hooks/use-get-endtime.js +0 -15
  113. package/dist/src/components/observability/trace-item/trace-item-detail.d.ts +0 -8
  114. package/dist/src/components/observability/trace-item/trace-item-detail.js +0 -10
  115. package/dist/src/components/observability/trace-item/trace-item.d.ts +0 -10
  116. package/dist/src/components/observability/trace-item/trace-item.js +0 -14
  117. package/dist/src/components/observability/trace-status.d.ts +0 -8
  118. package/dist/src/components/observability/trace-status.js +0 -18
  119. package/dist/src/components/observability/trace-tab-label.d.ts +0 -1
  120. package/dist/src/components/observability/trace-tab-label.js +0 -5
  121. package/dist/src/components/observability/trace-timeline.d.ts +0 -6
  122. package/dist/src/components/observability/trace-timeline.js +0 -30
  123. package/dist/src/components/observability/traces-groups.d.ts +0 -9
  124. package/dist/src/components/observability/traces-groups.js +0 -9
  125. package/dist/src/components/observability/traces-page.d.ts +0 -1
  126. package/dist/src/components/observability/traces-page.js +0 -33
  127. package/dist/src/components/states/hooks/states-hooks.d.ts +0 -13
  128. package/dist/src/components/states/hooks/states-hooks.js +0 -26
  129. package/dist/src/components/states/state-details.d.ts +0 -7
  130. package/dist/src/components/states/state-details.js +0 -3
  131. package/dist/src/components/states/state-editor.d.ts +0 -7
  132. package/dist/src/components/states/state-editor.js +0 -71
  133. package/dist/src/components/states/state-sidebar.d.ts +0 -8
  134. package/dist/src/components/states/state-sidebar.js +0 -17
  135. package/dist/src/components/states/state-tab-label.d.ts +0 -1
  136. package/dist/src/components/states/state-tab-label.js +0 -5
  137. package/dist/src/components/states/states-page.d.ts +0 -1
  138. package/dist/src/components/states/states-page.js +0 -56
  139. package/dist/src/types/observability.d.ts +0 -78
  140. package/dist/vite-plugin-motia-plugins.d.ts +0 -9
  141. package/dist/vite-plugin-motia-plugins.js +0 -69
  142. /package/dist/{src/types/observability.js → motia-plugin/__tests__/generator.test.d.ts} +0 -0
@@ -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
+ })
@@ -0,0 +1,130 @@
1
+ import { getUniquePackageNames } from './resolver'
2
+ import type { WorkbenchPlugin } from './types'
3
+
4
+ /**
5
+ * Generates import statements for all unique plugin packages.
6
+ *
7
+ * @param packages - Array of unique package names
8
+ * @returns JavaScript code string with import statements
9
+ *
10
+ * @example
11
+ * ```ts
12
+ * generateImports(['@org/plugin-1', '~/plugins/local'])
13
+ * // Returns:
14
+ * // import * as plugin_0 from '@org/plugin-1'
15
+ * // import * as plugin_1 from '~/plugins/local'
16
+ * ```
17
+ */
18
+ export function generateImports(packages: string[]): string {
19
+ return packages.map((packageName, index) => `import * as plugin_${index} from '${packageName}'`).join('\n')
20
+ }
21
+
22
+ /**
23
+ * Generates the package map that links package names to their imported modules.
24
+ *
25
+ * @param packages - Array of unique package names
26
+ * @returns JavaScript code string defining the package map
27
+ *
28
+ * @example
29
+ * ```ts
30
+ * generatePackageMap(['@org/plugin-1', '~/plugins/local'])
31
+ * // Returns: const packageMap = {'@org/plugin-1': plugin_0,'~/plugins/local': plugin_1}
32
+ * ```
33
+ */
34
+ export function generatePackageMap(packages: string[]): string {
35
+ const entries = packages.map((packageName, index) => `'${packageName}': plugin_${index}`)
36
+ return `const packageMap = {${entries.join(',')}}`
37
+ }
38
+
39
+ /**
40
+ * Generates the plugin transformation logic that processes plugin configurations.
41
+ *
42
+ * @param plugins - Array of plugin configurations
43
+ * @returns JavaScript code string with plugin processing logic
44
+ */
45
+ export function generatePluginLogic(plugins: WorkbenchPlugin[]): string {
46
+ return `
47
+ const motiaPlugins = ${JSON.stringify(plugins)}
48
+
49
+ export const plugins = motiaPlugins.map((plugin) => {
50
+ const component = packageMap[plugin.packageName]
51
+ const config = component.config || {}
52
+ const componentName = config.componentName || plugin.componentName
53
+
54
+ return {
55
+ label: plugin.label || config.label || 'Plugin label',
56
+ labelIcon: plugin.labelIcon || config.labelIcon || 'toy-brick',
57
+ position: plugin.position || config.position || 'top',
58
+ props: plugin.props || config.props || {},
59
+ component: componentName ? component[componentName] : component.default,
60
+ }
61
+ })
62
+ `
63
+ }
64
+
65
+ /**
66
+ * Generates the complete virtual module code for all plugins.
67
+ * This is the main code generation function that combines all parts.
68
+ *
69
+ * @param plugins - Array of plugin configurations
70
+ * @returns Complete JavaScript code string for the virtual module
71
+ *
72
+ * @example
73
+ * ```ts
74
+ * const plugins = [
75
+ * { packageName: '@test/plugin', label: 'Test' }
76
+ * ]
77
+ * const code = generatePluginCode(plugins)
78
+ * // Returns complete module code with imports, map, and logic
79
+ * ```
80
+ */
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}
92
+ ${packageMap}
93
+ ${logic}`
94
+ }
95
+
96
+ /**
97
+ * Generates CSS imports for plugins that specify cssImports.
98
+ *
99
+ * @param plugins - Array of plugin configurations
100
+ * @returns CSS import statements as a string
101
+ *
102
+ * @example
103
+ * ```ts
104
+ * const plugins = [
105
+ * { packageName: '@test/plugin', cssImports: ['styles.css', 'theme.css'] }
106
+ * ]
107
+ * generateCssImports(plugins)
108
+ * // Returns:
109
+ * // @import 'styles.css';
110
+ * // @import 'theme.css';
111
+ * ```
112
+ */
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')
120
+ }
121
+
122
+ /**
123
+ * Checks if the generated code is valid (non-empty and has content).
124
+ *
125
+ * @param code - The generated code to check
126
+ * @returns True if code is valid
127
+ */
128
+ export function isValidCode(code: string): boolean {
129
+ return typeof code === 'string' && code.trim().length > 0
130
+ }
@@ -0,0 +1,140 @@
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
+ // Check if this change affects plugins
75
+ if (!shouldInvalidatePlugins(file, plugins)) {
76
+ return // Let Vite handle it normally
77
+ }
78
+
79
+ if (isConfigFile(file)) {
80
+ printer.printPluginLog('HMR: Config file changed, triggering full page reload')
81
+ printer.printPluginWarn(
82
+ 'Configuration changes require a server restart for full effect. Please restart the dev server to apply all changes.',
83
+ )
84
+ server.ws.send({
85
+ type: 'full-reload',
86
+ path: '*',
87
+ })
88
+ return
89
+ }
90
+
91
+ printer.printPluginLog('HMR: Plugin change detected, invalidating virtual module')
92
+
93
+ // Find the virtual module
94
+ const virtualModule = server.moduleGraph.getModuleById(CONSTANTS.RESOLVED_VIRTUAL_MODULE_ID)
95
+
96
+ if (!virtualModule) {
97
+ printer.printPluginWarn('HMR: Virtual module not found, triggering full reload')
98
+ server.ws.send({
99
+ type: 'full-reload',
100
+ path: '*',
101
+ })
102
+ return
103
+ }
104
+
105
+ server.moduleGraph.invalidateModule(virtualModule, new Set(), timestamp)
106
+ printer.printPluginLog('HMR: Virtual module invalidated')
107
+
108
+ const modulesToUpdate: ModuleNode[] = [virtualModule]
109
+ const processedModules = new Set<ModuleNode>([virtualModule])
110
+
111
+ // Recursively add all importers
112
+ const addImporters = (module: ModuleNode) => {
113
+ for (const importer of module.importers) {
114
+ if (!processedModules.has(importer)) {
115
+ processedModules.add(importer)
116
+ modulesToUpdate.push(importer)
117
+ server.moduleGraph.invalidateModule(importer, new Set(), timestamp)
118
+ addImporters(importer)
119
+ }
120
+ }
121
+ }
122
+
123
+ addImporters(virtualModule)
124
+
125
+ server.ws.send({
126
+ type: 'update',
127
+ updates: modulesToUpdate
128
+ .filter((m) => m.url)
129
+ .map((m) => ({
130
+ type: m.type === 'css' ? ('css-update' as const) : ('js-update' as const),
131
+ path: m.url,
132
+ acceptedPath: m.url,
133
+ timestamp,
134
+ })),
135
+ })
136
+
137
+ printer.printPluginLog(`HMR: Updated ${modulesToUpdate.length} module(s)`)
138
+
139
+ return modulesToUpdate
140
+ }
@@ -0,0 +1,182 @@
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
+ printer.printPluginLog(`HMR: Successfully updated ${modulesToUpdate.length} module(s)`)
174
+ return modulesToUpdate
175
+ }
176
+ },
177
+
178
+ buildEnd() {
179
+ printer.printPluginLog('Build ended')
180
+ },
181
+ }
182
+ }
@@ -0,0 +1,96 @@
1
+ import type { ResolvedPackage, WorkbenchPlugin } from './types'
2
+ import { isLocalPlugin, normalizePath, resolveLocalPath, resolveNpmPath } from './utils'
3
+
4
+ /**
5
+ * Resolves a plugin package to its absolute path and creates an alias.
6
+ *
7
+ * @param plugin - The plugin configuration to resolve
8
+ * @returns Resolved package information including path and alias
9
+ *
10
+ * @example
11
+ * ```ts
12
+ * // Local plugin
13
+ * resolvePluginPackage({ packageName: '~/plugins/my-plugin' })
14
+ * // Returns: {
15
+ * // packageName: '~/plugins/my-plugin',
16
+ * // resolvedPath: '/Users/project/plugins/my-plugin',
17
+ * // isLocal: true,
18
+ * // alias: '~/plugins/my-plugin'
19
+ * // }
20
+ *
21
+ * // NPM package
22
+ * resolvePluginPackage({ packageName: '@org/plugin' })
23
+ * // Returns: {
24
+ * // packageName: '@org/plugin',
25
+ * // resolvedPath: '/Users/project/node_modules/@org/plugin',
26
+ * // isLocal: false,
27
+ * // alias: '@org/plugin'
28
+ * // }
29
+ * ```
30
+ */
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
+
45
+ /**
46
+ * Resolves all plugin packages and creates a Vite alias configuration.
47
+ *
48
+ * @param plugins - Array of plugin configurations
49
+ * @returns Vite alias configuration object
50
+ *
51
+ * @example
52
+ * ```ts
53
+ * const plugins = [
54
+ * { packageName: '~/plugins/local' },
55
+ * { packageName: '@org/npm-plugin' }
56
+ * ]
57
+ * const aliases = createAliasConfig(plugins)
58
+ * // Returns: {
59
+ * // '~/plugins/local': '/Users/project/plugins/local',
60
+ * // '@org/npm-plugin': '/Users/project/node_modules/@org/npm-plugin'
61
+ * // }
62
+ * ```
63
+ */
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
+
78
+ /**
79
+ * Resolves all plugins and returns their resolved package information.
80
+ *
81
+ * @param plugins - Array of plugin configurations
82
+ * @returns Array of resolved package information
83
+ */
84
+ export function resolveAllPlugins(plugins: WorkbenchPlugin[]): ResolvedPackage[] {
85
+ return plugins.map((plugin) => resolvePluginPackage(plugin))
86
+ }
87
+
88
+ /**
89
+ * Gets the unique set of package names from plugins.
90
+ *
91
+ * @param plugins - Array of plugin configurations
92
+ * @returns Array of unique package names
93
+ */
94
+ export function getUniquePackageNames(plugins: WorkbenchPlugin[]): string[] {
95
+ return Array.from(new Set(plugins.map((p) => p.packageName)))
96
+ }