@@ -16,17 +16,18 @@ export function createPluginDiscoveryPlugin(): Plugin {
1616 build . onStart ( async ( ) => {
1717 try {
1818 await generateWorkflowLoaders ( ) ;
19+ await generateResourceLoaders ( ) ;
1920 } catch ( error ) {
20- console . error ( 'Failed to generate workflow loaders:' , error ) ;
21+ console . error ( 'Failed to generate loaders:' , error ) ;
2122 throw error ;
2223 }
2324 } ) ;
2425 } ,
2526 } ;
2627}
2728
28- async function generateWorkflowLoaders ( ) : Promise < void > {
29- const pluginsDir = path . resolve ( process . cwd ( ) , 'src/plugins ' ) ;
29+ export async function generateWorkflowLoaders ( ) : Promise < void > {
30+ const pluginsDir = path . resolve ( process . cwd ( ) , 'src/mcp/tools ' ) ;
3031
3132 if ( ! existsSync ( pluginsDir ) ) {
3233 throw new Error ( `Plugins directory not found: ${ pluginsDir } ` ) ;
@@ -41,7 +42,8 @@ async function generateWorkflowLoaders(): Promise<void> {
4142 const workflowMetadata : Record < string , WorkflowMetadata > = { } ;
4243
4344 for ( const dirName of workflowDirs ) {
44- const indexPath = join ( pluginsDir , dirName , 'index.ts' ) ;
45+ const dirPath = join ( pluginsDir , dirName ) ;
46+ const indexPath = join ( dirPath , 'index.ts' ) ;
4547
4648 // Check if workflow has index.ts file
4749 if ( ! existsSync ( indexPath ) ) {
@@ -55,11 +57,26 @@ async function generateWorkflowLoaders(): Promise<void> {
5557 const metadata = extractWorkflowMetadata ( indexContent ) ;
5658
5759 if ( metadata ) {
58- // Generate dynamic import for this workflow
59- workflowLoaders [ dirName ] = `() => import('../plugins/${ dirName } /index.js')` ;
60+ // Find all tool files in this workflow directory
61+ const toolFiles = readdirSync ( dirPath , { withFileTypes : true } )
62+ . filter ( ( dirent ) => dirent . isFile ( ) )
63+ . map ( ( dirent ) => dirent . name )
64+ . filter (
65+ ( name ) =>
66+ ( name . endsWith ( '.ts' ) || name . endsWith ( '.js' ) ) &&
67+ name !== 'index.ts' &&
68+ name !== 'index.js' &&
69+ ! name . endsWith ( '.test.ts' ) &&
70+ ! name . endsWith ( '.test.js' ) &&
71+ name !== 'active-processes.ts' ,
72+ ) ;
73+
74+ workflowLoaders [ dirName ] = generateWorkflowLoader ( dirName , toolFiles ) ;
6075 workflowMetadata [ dirName ] = metadata ;
6176
62- console . log ( `✅ Discovered workflow: ${ dirName } - ${ metadata . name } ` ) ;
77+ console . log (
78+ `✅ Discovered workflow: ${ dirName } - ${ metadata . name } (${ toolFiles . length } tools)` ,
79+ ) ;
6380 } else {
6481 console . warn ( `⚠️ Skipping ${ dirName } : invalid workflow metadata` ) ;
6582 }
@@ -80,6 +97,31 @@ async function generateWorkflowLoaders(): Promise<void> {
8097 console . log ( `🔧 Generated workflow loaders for ${ Object . keys ( workflowLoaders ) . length } workflows` ) ;
8198}
8299
100+ function generateWorkflowLoader ( workflowName : string , toolFiles : string [ ] ) : string {
101+ const toolImports = toolFiles
102+ . map ( ( file , index ) => {
103+ const toolName = file . replace ( / \. ( t s | j s ) $ / , '' ) ;
104+ return `const tool_${ index } = await import('../mcp/tools/${ workflowName } /${ toolName } .js').then(m => m.default)` ;
105+ } )
106+ . join ( ';\n ' ) ;
107+
108+ const toolExports = toolFiles
109+ . map ( ( file , index ) => {
110+ const toolName = file . replace ( / \. ( t s | j s ) $ / , '' ) ;
111+ return `'${ toolName } ': tool_${ index } ` ;
112+ } )
113+ . join ( ',\n ' ) ;
114+
115+ return `async () => {
116+ const { workflow } = await import('../mcp/tools/${ workflowName } /index.js');
117+ ${ toolImports ? toolImports + ';\n ' : '' }
118+ return {
119+ workflow,
120+ ${ toolExports ? toolExports : '' }
121+ };
122+ }` ;
123+ }
124+
83125function extractWorkflowMetadata ( content : string ) : WorkflowMetadata | null {
84126 try {
85127 // Simple regex to extract workflow export object
@@ -114,7 +156,13 @@ function generatePluginsFileContent(
114156 workflowMetadata : Record < string , WorkflowMetadata > ,
115157) : string {
116158 const loaderEntries = Object . entries ( workflowLoaders )
117- . map ( ( [ key , loader ] ) => ` '${ key } ': ${ loader } ` )
159+ . map ( ( [ key , loader ] ) => {
160+ const indentedLoader = loader
161+ . split ( '\n' )
162+ . map ( ( line , index ) => ( index === 0 ? ` '${ key } ': ${ line } ` : ` ${ line } ` ) )
163+ . join ( '\n' ) ;
164+ return indentedLoader ;
165+ } )
118166 . join ( ',\n' ) ;
119167
120168 const metadataEntries = Object . entries ( workflowMetadata )
@@ -143,3 +191,59 @@ ${metadataEntries}
143191};
144192` ;
145193}
194+
195+ export async function generateResourceLoaders ( ) : Promise < void > {
196+ const resourcesDir = path . resolve ( process . cwd ( ) , 'src/mcp/resources' ) ;
197+
198+ if ( ! existsSync ( resourcesDir ) ) {
199+ console . log ( 'Resources directory not found, skipping resource generation' ) ;
200+ return ;
201+ }
202+
203+ const resourceFiles = readdirSync ( resourcesDir , { withFileTypes : true } )
204+ . filter ( ( dirent ) => dirent . isFile ( ) )
205+ . map ( ( dirent ) => dirent . name )
206+ . filter (
207+ ( name ) =>
208+ ( name . endsWith ( '.ts' ) || name . endsWith ( '.js' ) ) &&
209+ ! name . endsWith ( '.test.ts' ) &&
210+ ! name . endsWith ( '.test.js' ) &&
211+ ! name . startsWith ( '__' ) ,
212+ ) ;
213+
214+ const resourceLoaders : Record < string , string > = { } ;
215+
216+ for ( const fileName of resourceFiles ) {
217+ const resourceName = fileName . replace ( / \. ( t s | j s ) $ / , '' ) ;
218+ resourceLoaders [ resourceName ] = `async () => {
219+ const module = await import('../mcp/resources/${ resourceName } .js');
220+ return module.default;
221+ }` ;
222+
223+ console . log ( `✅ Discovered resource: ${ resourceName } ` ) ;
224+ }
225+
226+ const generatedContent = generateResourcesFileContent ( resourceLoaders ) ;
227+ const outputPath = path . resolve ( process . cwd ( ) , 'src/core/generated-resources.ts' ) ;
228+
229+ const fs = await import ( 'fs' ) ;
230+ await fs . promises . writeFile ( outputPath , generatedContent , 'utf8' ) ;
231+
232+ console . log ( `🔧 Generated resource loaders for ${ Object . keys ( resourceLoaders ) . length } resources` ) ;
233+ }
234+
235+ function generateResourcesFileContent ( resourceLoaders : Record < string , string > ) : string {
236+ const loaderEntries = Object . entries ( resourceLoaders )
237+ . map ( ( [ key , loader ] ) => ` '${ key } ': ${ loader } ` )
238+ . join ( ',\n' ) ;
239+
240+ return `// AUTO-GENERATED - DO NOT EDIT
241+ // This file is generated by the plugin discovery esbuild plugin
242+
243+ export const RESOURCE_LOADERS = {
244+ ${ loaderEntries }
245+ };
246+
247+ export type ResourceName = keyof typeof RESOURCE_LOADERS;
248+ ` ;
249+ }
0 commit comments