@@ -2,9 +2,8 @@ import { defineConfig, Plugin, PluginOption } from "vite";
22import react from "@vitejs/plugin-react" ;
33import { viteSingleFile } from "vite-plugin-singlefile" ;
44import { nodePolyfills } from "vite-plugin-node-polyfills" ;
5- import { readFileSync , writeFileSync , mkdirSync , existsSync } from "fs" ;
6- import { join , resolve , dirname } from "path" ;
7- import { uiMap } from "./src/ui/registry/uiMap.js" ;
5+ import { readFileSync , writeFileSync , mkdirSync , existsSync , readdirSync , statSync } from "fs" ;
6+ import { join , resolve } from "path" ;
87
98const componentsDir = resolve ( __dirname , "src/ui/components" ) ;
109// Use node_modules/.cache for generated HTML entries - these are build artifacts, not source files
@@ -14,14 +13,37 @@ const mountPath = resolve(__dirname, "src/ui/build/mount.tsx");
1413const generatedDir = resolve ( __dirname , "src/ui/generated" ) ;
1514const uiDistPath = resolve ( __dirname , "dist/ui" ) ;
1615
17- // Unique component names from uiMap - only these will be built
18- const components = [ ...new Set ( Object . values ( uiMap ) ) ] ;
16+ // Converts PascalCase to kebab-case: "ListDatabases" -> "list-databases"
17+ function toKebabCase ( pascalCase : string ) : string {
18+ return pascalCase
19+ . replace ( / ( [ a - z 0 - 9 ] ) ( [ A - Z ] ) / g, "$1-$2" )
20+ . replace ( / ( [ A - Z ] ) ( [ A - Z ] [ a - z ] ) / g, "$1-$2" )
21+ . toLowerCase ( ) ;
22+ }
23+
24+ // Discovers component directories and builds tool name mappings
25+ function discoverComponents ( ) : { components : string [ ] ; toolToComponentMap : Record < string , string > } {
26+ const components : string [ ] = [ ] ;
27+ const toolToComponentMap : Record < string , string > = { } ;
28+
29+ for ( const entry of readdirSync ( componentsDir ) ) {
30+ const entryPath = join ( componentsDir , entry ) ;
31+ const indexPath = join ( entryPath , "index.ts" ) ;
32+
33+ if ( statSync ( entryPath ) . isDirectory ( ) && existsSync ( indexPath ) ) {
34+ components . push ( entry ) ;
35+ toolToComponentMap [ toKebabCase ( entry ) ] = entry ;
36+ }
37+ }
38+
39+ return { components, toolToComponentMap } ;
40+ }
41+
42+ const { components, toolToComponentMap } = discoverComponents ( ) ;
1943
2044/**
21- * Vite plugin that generates HTML entry files for each mapped component
45+ * Vite plugin that generates HTML entry files for each discovered component
2246 * based on the template.html file.
23- *
24- * Only builds components that are referenced in uiMap.
2547 */
2648function generateHtmlEntries ( ) : Plugin {
2749 return {
@@ -34,14 +56,6 @@ function generateHtmlEntries(): Plugin {
3456 }
3557
3658 for ( const componentName of components ) {
37- // Verify the component exists
38- const componentPath = join ( componentsDir , componentName , "index.ts" ) ;
39- if ( ! existsSync ( componentPath ) ) {
40- throw new Error (
41- `Component "${ componentName } " referenced in uiMap but not found at ${ componentPath } `
42- ) ;
43- }
44-
4559 const html = template
4660 . replace ( "{{COMPONENT_NAME}}" , componentName )
4761 . replace ( "{{TITLE}}" , componentName . replace ( / ( [ A - Z ] ) / g, " $1" ) . trim ( ) ) // "ListDatabases" -> "List Databases"
@@ -57,8 +71,6 @@ function generateHtmlEntries(): Plugin {
5771
5872/**
5973 * Vite plugin that generates per-tool UI modules after the build completes.
60- *
61- * Uses the uiMap to map tool names to component HTML files.
6274 */
6375function generateUIModule ( ) : Plugin {
6476 return {
@@ -79,7 +91,7 @@ function generateUIModule(): Plugin {
7991
8092 const generatedTools : string [ ] = [ ] ;
8193
82- for ( const [ toolName , componentName ] of Object . entries ( uiMap ) ) {
94+ for ( const [ toolName , componentName ] of Object . entries ( toolToComponentMap ) ) {
8395 const htmlFile = join ( uiDistPath , `${ componentName } .html` ) ;
8496 if ( ! existsSync ( htmlFile ) ) {
8597 console . warn (
0 commit comments