@@ -2,57 +2,46 @@ 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 { readdirSync , statSync , readFileSync , writeFileSync , mkdirSync , existsSync } from "fs" ;
5+ import { readFileSync , writeFileSync , mkdirSync , existsSync } from "fs" ;
66import { join , resolve } from "path" ;
7+ import { uiMap } from "./src/ui/registry/uiMap.js" ;
78
89const componentsDir = resolve ( __dirname , "src/ui/components" ) ;
910// Use node_modules/.cache for generated HTML entries - these are build artifacts, not source files
1011const entriesDir = resolve ( __dirname , "node_modules/.cache/mongodb-mcp-server/ui-entries" ) ;
1112const templatePath = resolve ( __dirname , "src/ui/build/template.html" ) ;
1213const mountPath = resolve ( __dirname , "src/ui/build/mount.tsx" ) ;
14+ const generatedModulePath = resolve ( __dirname , "src/ui/generated/uiHtml.ts" ) ;
15+ const uiDistPath = resolve ( __dirname , "dist/ui" ) ;
1316
14- /**
15- * Discover all component directories in src/ui/components/
16- * Each directory should have an index.ts that exports the component
17- */
18- function discoverComponents ( ) : string [ ] {
19- const components : string [ ] = [ ] ;
20-
21- try {
22- const dirs = readdirSync ( componentsDir ) ;
23- for ( const dir of dirs ) {
24- const dirPath = join ( componentsDir , dir ) ;
25- if ( statSync ( dirPath ) . isDirectory ( ) ) {
26- // Check if index.ts exists
27- const indexPath = join ( dirPath , "index.ts" ) ;
28- if ( existsSync ( indexPath ) ) {
29- components . push ( dir ) ;
30- }
31- }
32- }
33- } catch {
34- console . warn ( "No components directory found or error reading it" ) ;
35- }
36-
37- return components ;
38- }
17+ // Unique component names from uiMap - only these will be built
18+ const components = [ ...new Set ( Object . values ( uiMap ) ) ] ;
3919
4020/**
41- * Vite plugin that generates HTML entry files for each component
42- * based on the template.html file
21+ * Vite plugin that generates HTML entry files for each mapped component
22+ * based on the template.html file.
23+ *
24+ * Only builds components that are referenced in uiMap.
4325 */
4426function generateHtmlEntries ( ) : Plugin {
4527 return {
4628 name : "generate-html-entries" ,
4729 buildStart ( ) {
48- const components = discoverComponents ( ) ;
4930 const template = readFileSync ( templatePath , "utf-8" ) ;
5031
5132 if ( ! existsSync ( entriesDir ) ) {
5233 mkdirSync ( entriesDir , { recursive : true } ) ;
5334 }
5435
5536 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+
5645 const html = template
5746 . replace ( "{{COMPONENT_NAME}}" , componentName )
5847 . replace ( "{{TITLE}}" , componentName . replace ( / ( [ A - Z ] ) / g, " $1" ) . trim ( ) ) // "ListDatabases" -> "List Databases"
@@ -66,7 +55,58 @@ function generateHtmlEntries(): Plugin {
6655 } ;
6756}
6857
69- const components = discoverComponents ( ) ;
58+ /**
59+ * Vite plugin that generates the uiHtml.ts module after the build completes.
60+ * This embeds all built HTML strings into a TypeScript module so they can be
61+ * imported at runtime.
62+ *
63+ * Uses the uiMap from src/ui/registry/uiMap.ts to map tool names to component HTML files.
64+ */
65+ function generateUIModule ( ) : Plugin {
66+ return {
67+ name : "generate-ui-module" ,
68+ closeBundle ( ) {
69+ if ( ! existsSync ( uiDistPath ) ) {
70+ console . warn ( "[generate-ui-module] dist/ui not found, skipping module generation" ) ;
71+ return ;
72+ }
73+
74+ const entries : Record < string , string > = { } ;
75+
76+ // Use uiMap to determine which tools get which UI
77+ for ( const [ toolName , componentName ] of Object . entries ( uiMap ) ) {
78+ const htmlFile = join ( uiDistPath , `${ componentName } .html` ) ;
79+ if ( ! existsSync ( htmlFile ) ) {
80+ console . warn (
81+ `[generate-ui-module] HTML file not found for component "${ componentName } " (tool: "${ toolName } ")`
82+ ) ;
83+ continue ;
84+ }
85+ const html = readFileSync ( htmlFile , "utf-8" ) ;
86+ entries [ toolName ] = html ;
87+ }
88+
89+ const moduleContent = `/**
90+ * AUTO-GENERATED FILE - DO NOT EDIT MANUALLY
91+ *
92+ * This file is generated by the UI build process (vite build --config vite.ui.config.ts).
93+ * It contains the bundled HTML strings for each UI component, keyed by tool name.
94+ *
95+ * To add a new UI:
96+ * 1. Create a component in src/ui/components/YourComponent/
97+ * 2. Add the mapping in src/ui/registry/uiMap.ts
98+ * 3. Run \`pnpm build:ui\` to regenerate this file
99+ */
100+ export const uiHtml: Record<string, string> = ${ JSON . stringify ( entries , null , 4 ) } ;
101+ ` ;
102+
103+ writeFileSync ( generatedModulePath , moduleContent ) ;
104+ console . log (
105+ `[generate-ui-module] Generated uiHtml.ts with ${ Object . keys ( entries ) . length } UI(s): ${ Object . keys ( entries ) . join ( ", " ) } `
106+ ) ;
107+ } ,
108+ } ;
109+ }
70110
71111export default defineConfig ( {
72112 root : entriesDir ,
@@ -82,6 +122,7 @@ export default defineConfig({
82122 viteSingleFile ( {
83123 removeViteModuleLoader : true ,
84124 } ) ,
125+ generateUIModule ( ) ,
85126 ] ,
86127 build : {
87128 outDir : resolve ( __dirname , "dist/ui" ) ,
0 commit comments