1import { existsSync } from "node:fs";
2import path from "path";
3import type { PluginOption } from "vite";
4
5interface LocalFrappeUIDevConfigParams {
6 mode: string;
7 rootDir: string;
8}
9
10interface LocalFrappeUIDevConfig {
11 useLocalFrappeUI: boolean;
12 localFrappeUIPath: string;
13 localFrappeUIAliases: Record<string, string>;
14}
15
16interface ImportFrappeUIPluginParams {
17 useLocalFrappeUI: boolean;
18}
19
20type FrappeUIPluginFactory = (...args: any[]) => PluginOption;
21type FrappeUIPluginModule = { default: FrappeUIPluginFactory };
22
23async function loadFrappeUIPluginModule(
24 modulePath: string
25): Promise<FrappeUIPluginModule> {
26 return (await import(modulePath)) as FrappeUIPluginModule;
27}
28
29export function getLocalFrappeUIDevConfig({
30 mode,
31 rootDir,
32}: LocalFrappeUIDevConfigParams): LocalFrappeUIDevConfig {
33 const isDev = mode === "development";
34 const localFrappeUIPath = path.resolve(rootDir, "../frappe-ui");
35 const useLocalFrappeUI =
36 isDev && existsSync(path.join(localFrappeUIPath, "node_modules"));
37
38 if (isDev && existsSync(localFrappeUIPath) && !useLocalFrappeUI) {
39 console.warn("⚠️ Local frappe-ui found but dependencies not installed.");
40 console.warn(" Run: cd ../frappe-ui && yarn install");
41 }
42
43 const localFrappeUIAliases: Record<string, string> = useLocalFrappeUI
44 ? {
45 "frappe-ui/style.css": path.resolve(
46 localFrappeUIPath,
47 "src",
48 "style.css"
49 ),
50 // The `frappe-ui/editor` subpath maps to src/molecules/editor via the
51 // package's exports map, which the bare `frappe-ui` directory alias
52 // bypasses. Map it explicitly (same pattern as `frappe-ui/style.css`)
53 // so the v1 editor resolves when developing against local frappe-ui.
54 "frappe-ui/editor": path.resolve(
55 localFrappeUIPath,
56 "src",
57 "molecules",
58 "editor",
59 "index.ts"
60 ),
61 "frappe-ui": localFrappeUIPath,
62 }
63 : {};
64
65 return {
66 useLocalFrappeUI,
67 localFrappeUIPath,
68 localFrappeUIAliases,
69 };
70}
71
72export async function importFrappeUIPlugin({
73 useLocalFrappeUI,
74}: ImportFrappeUIPluginParams): Promise<FrappeUIPluginFactory> {
75 const npmModulePath = "frappe-ui/vite";
76 const modulePath = useLocalFrappeUI
77 ? "../frappe-ui/vite/index.js"
78 : npmModulePath;
79
80 try {
81 const module = await loadFrappeUIPluginModule(modulePath);
82 return module.default as FrappeUIPluginFactory;
83 } catch (error) {
84 if (useLocalFrappeUI) {
85 console.warn(
86 "⚠️ Failed to import local frappe-ui plugin, falling back to npm package"
87 );
88 console.warn(
89 " Error:",
90 error instanceof Error ? error.message : String(error)
91 );
92 const fallbackModule = await loadFrappeUIPluginModule(npmModulePath);
93 return fallbackModule.default as FrappeUIPluginFactory;
94 }
95 throw error;
96 }
97}
98