@mantajs/dashboard 0.1.10 → 0.1.12

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.
@@ -0,0 +1,10 @@
1
+ declare function App(props: {
2
+ plugins?: any[]
3
+ }): JSX.Element
4
+
5
+ export default App
6
+
7
+ import type enTranslation from "./en.json"
8
+ export type Resources = {
9
+ translation: typeof enTranslation
10
+ }
@@ -15,7 +15,7 @@ import {
15
15
  import {
16
16
  FormExtensionZone,
17
17
  useExtendableForm
18
- } from "./chunk-6AOXP3RP.mjs";
18
+ } from "./chunk-VHUQFKRO.mjs";
19
19
  import "./chunk-2SSUH2HJ.mjs";
20
20
  import "./chunk-ONB3JEHR.mjs";
21
21
  import "./chunk-YCDDT44O.mjs";
@@ -57,7 +57,7 @@ import {
57
57
  import {
58
58
  FormExtensionZone,
59
59
  useExtendableForm
60
- } from "./chunk-6AOXP3RP.mjs";
60
+ } from "./chunk-VHUQFKRO.mjs";
61
61
  import "./chunk-2SSUH2HJ.mjs";
62
62
  import "./chunk-ONB3JEHR.mjs";
63
63
  import "./chunk-YCDDT44O.mjs";
@@ -17,7 +17,7 @@ import {
17
17
  } from "./chunk-GIZFNLKK.mjs";
18
18
  import {
19
19
  getLinkedFields
20
- } from "./chunk-6AOXP3RP.mjs";
20
+ } from "./chunk-VHUQFKRO.mjs";
21
21
  import "./chunk-2SSUH2HJ.mjs";
22
22
  import "./chunk-ONB3JEHR.mjs";
23
23
  import "./chunk-YCDDT44O.mjs";
@@ -18,7 +18,7 @@ import {
18
18
  import {
19
19
  FormExtensionZone,
20
20
  useExtendableForm
21
- } from "./chunk-6AOXP3RP.mjs";
21
+ } from "./chunk-VHUQFKRO.mjs";
22
22
  import "./chunk-2SSUH2HJ.mjs";
23
23
  import "./chunk-ONB3JEHR.mjs";
24
24
  import "./chunk-YCDDT44O.mjs";
@@ -20,7 +20,7 @@ import {
20
20
  import {
21
21
  FormExtensionZone,
22
22
  useExtendableForm
23
- } from "./chunk-6AOXP3RP.mjs";
23
+ } from "./chunk-VHUQFKRO.mjs";
24
24
  import "./chunk-2SSUH2HJ.mjs";
25
25
  import "./chunk-ONB3JEHR.mjs";
26
26
  import "./chunk-YCDDT44O.mjs";
@@ -0,0 +1,31 @@
1
+ import { Plugin } from 'vite';
2
+ import { ReactNode } from 'react';
3
+
4
+ type MenuNestedItem = {
5
+ label: string;
6
+ to: string;
7
+ useTranslation?: boolean;
8
+ };
9
+ type MenuItem = {
10
+ icon: ReactNode;
11
+ label: string;
12
+ to: string;
13
+ useTranslation?: boolean;
14
+ items?: MenuNestedItem[];
15
+ };
16
+ type MenuConfig = {
17
+ items: MenuItem[];
18
+ };
19
+
20
+ /**
21
+ * Unified Vite plugin for @mantajs/dashboard.
22
+ *
23
+ * Handles:
24
+ * 1. Menu config virtual module (virtual:dashboard/menu-config)
25
+ * 2. Component overrides — any file in src/admin/components/ overrides the
26
+ * dashboard component with the same name.
27
+ */
28
+ declare function customDashboardPlugin(): Plugin;
29
+ declare const menuConfigPlugin: typeof customDashboardPlugin;
30
+
31
+ export { type MenuConfig, type MenuItem, type MenuNestedItem, customDashboardPlugin, menuConfigPlugin };
@@ -0,0 +1,31 @@
1
+ import { Plugin } from 'vite';
2
+ import { ReactNode } from 'react';
3
+
4
+ type MenuNestedItem = {
5
+ label: string;
6
+ to: string;
7
+ useTranslation?: boolean;
8
+ };
9
+ type MenuItem = {
10
+ icon: ReactNode;
11
+ label: string;
12
+ to: string;
13
+ useTranslation?: boolean;
14
+ items?: MenuNestedItem[];
15
+ };
16
+ type MenuConfig = {
17
+ items: MenuItem[];
18
+ };
19
+
20
+ /**
21
+ * Unified Vite plugin for @mantajs/dashboard.
22
+ *
23
+ * Handles:
24
+ * 1. Menu config virtual module (virtual:dashboard/menu-config)
25
+ * 2. Component overrides — any file in src/admin/components/ overrides the
26
+ * dashboard component with the same name.
27
+ */
28
+ declare function customDashboardPlugin(): Plugin;
29
+ declare const menuConfigPlugin: typeof customDashboardPlugin;
30
+
31
+ export { type MenuConfig, type MenuItem, type MenuNestedItem, customDashboardPlugin, menuConfigPlugin };
@@ -30,30 +30,104 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
30
30
  // src/vite-plugin/index.ts
31
31
  var index_exports = {};
32
32
  __export(index_exports, {
33
+ customDashboardPlugin: () => customDashboardPlugin,
33
34
  menuConfigPlugin: () => menuConfigPlugin
34
35
  });
35
36
  module.exports = __toCommonJS(index_exports);
36
37
  var import_path = __toESM(require("path"));
37
38
  var import_fs = __toESM(require("fs"));
38
- var VIRTUAL_MODULE_ID = "virtual:dashboard/menu-config";
39
- var RESOLVED_ID = "\0" + VIRTUAL_MODULE_ID;
40
- function menuConfigPlugin() {
39
+ var MENU_VIRTUAL_ID = "virtual:dashboard/menu-config";
40
+ var MENU_RESOLVED_ID = "\0" + MENU_VIRTUAL_ID;
41
+ var COMPONENT_EXTENSIONS = [".tsx", ".ts", ".jsx", ".js", ".mts", ".mjs"];
42
+ function getComponentName(filePath) {
43
+ const normalized = filePath.replace(/\\/g, "/");
44
+ const parts = normalized.split("/");
45
+ const fileName = parts[parts.length - 1];
46
+ const baseName = fileName.replace(/\.(tsx?|jsx?|mts|mjs)$/, "");
47
+ if (baseName === "index") {
48
+ return parts.length >= 2 ? parts[parts.length - 2] : null;
49
+ }
50
+ if (parts.length >= 2 && baseName === parts[parts.length - 2]) {
51
+ return baseName;
52
+ }
53
+ return baseName;
54
+ }
55
+ function customDashboardPlugin() {
56
+ const componentsDir = import_path.default.resolve(process.cwd(), "src/admin/components");
57
+ const overridesByName = /* @__PURE__ */ new Map();
58
+ if (import_fs.default.existsSync(componentsDir)) {
59
+ for (const file of import_fs.default.readdirSync(componentsDir)) {
60
+ const fullPath = import_path.default.resolve(componentsDir, file);
61
+ if (!import_fs.default.statSync(fullPath).isFile()) continue;
62
+ const name = file.replace(/\.(tsx?|jsx?|mts|mjs)$/, "");
63
+ overridesByName.set(name, fullPath);
64
+ }
65
+ }
66
+ const hasOverrides = overridesByName.size > 0;
67
+ if (hasOverrides) {
68
+ console.log("[custom-dashboard] overrides:", [...overridesByName.keys()]);
69
+ }
41
70
  return {
42
- name: "dashboard-menu-config",
43
- config() {
44
- return {
45
- optimizeDeps: {
46
- exclude: [VIRTUAL_MODULE_ID]
47
- }
48
- };
71
+ name: "custom-dashboard",
72
+ enforce: "pre",
73
+ config(config) {
74
+ config.optimizeDeps = config.optimizeDeps || {};
75
+ config.optimizeDeps.exclude = config.optimizeDeps.exclude || [];
76
+ config.optimizeDeps.exclude.push(MENU_VIRTUAL_ID);
77
+ if (hasOverrides) {
78
+ config.optimizeDeps.esbuildOptions = config.optimizeDeps.esbuildOptions || {};
79
+ config.optimizeDeps.esbuildOptions.plugins = config.optimizeDeps.esbuildOptions.plugins || [];
80
+ const overrides = overridesByName;
81
+ config.optimizeDeps.esbuildOptions.plugins.push({
82
+ name: "dashboard-component-overrides",
83
+ setup(build) {
84
+ build.onLoad({ filter: /app\.(mjs|js)$/ }, (args) => {
85
+ const normalized = args.path.replace(/\\/g, "/");
86
+ if (!normalized.includes("/dashboard/dist/")) return void 0;
87
+ const srcEntry = args.path.replace(/\/dist\/app\.(mjs|js)$/, "/src/app.tsx");
88
+ if (!import_fs.default.existsSync(srcEntry)) return void 0;
89
+ console.log(
90
+ `[custom-dashboard] Redirecting entry: ${args.path} \u2192 ${srcEntry}`
91
+ );
92
+ return {
93
+ contents: import_fs.default.readFileSync(srcEntry, "utf-8"),
94
+ loader: "tsx",
95
+ resolveDir: import_path.default.dirname(srcEntry)
96
+ };
97
+ });
98
+ build.onLoad({ filter: /\.(tsx?|jsx?)$/ }, (args) => {
99
+ const normalized = args.path.replace(/\\/g, "/");
100
+ if (!normalized.includes("/dashboard/src/")) return void 0;
101
+ const fileName = import_path.default.basename(args.path);
102
+ if (fileName.startsWith("index.")) return void 0;
103
+ const componentName = getComponentName(args.path);
104
+ if (componentName && overrides.has(componentName)) {
105
+ const overridePath = overrides.get(componentName);
106
+ const ext = import_path.default.extname(overridePath).slice(1);
107
+ console.log(
108
+ `[custom-dashboard] Override: ${componentName} \u2192 ${overridePath}`
109
+ );
110
+ return {
111
+ contents: import_fs.default.readFileSync(overridePath, "utf-8"),
112
+ loader: ext,
113
+ resolveDir: import_path.default.dirname(overridePath)
114
+ };
115
+ }
116
+ return void 0;
117
+ });
118
+ }
119
+ });
120
+ config.optimizeDeps.force = true;
121
+ }
49
122
  },
50
- resolveId(id) {
51
- if (id === VIRTUAL_MODULE_ID) return RESOLVED_ID;
123
+ resolveId(source) {
124
+ if (source === MENU_VIRTUAL_ID) return MENU_RESOLVED_ID;
125
+ return null;
52
126
  },
53
127
  load(id) {
54
- if (id !== RESOLVED_ID) return;
128
+ if (id !== MENU_RESOLVED_ID) return;
55
129
  const basePath = import_path.default.resolve(process.cwd(), "src/admin/menu.config");
56
- for (const ext of [".tsx", ".ts", ".jsx", ".js", ".mts", ".mjs"]) {
130
+ for (const ext of COMPONENT_EXTENSIONS) {
57
131
  if (import_fs.default.existsSync(basePath + ext)) {
58
132
  return `export { default } from "${basePath + ext}"`;
59
133
  }
@@ -62,7 +136,9 @@ function menuConfigPlugin() {
62
136
  }
63
137
  };
64
138
  }
139
+ var menuConfigPlugin = customDashboardPlugin;
65
140
  // Annotate the CommonJS export names for ESM import in node:
66
141
  0 && (module.exports = {
142
+ customDashboardPlugin,
67
143
  menuConfigPlugin
68
144
  });
@@ -1,25 +1,98 @@
1
1
  // src/vite-plugin/index.ts
2
2
  import path from "path";
3
3
  import fs from "fs";
4
- var VIRTUAL_MODULE_ID = "virtual:dashboard/menu-config";
5
- var RESOLVED_ID = "\0" + VIRTUAL_MODULE_ID;
6
- function menuConfigPlugin() {
4
+ var MENU_VIRTUAL_ID = "virtual:dashboard/menu-config";
5
+ var MENU_RESOLVED_ID = "\0" + MENU_VIRTUAL_ID;
6
+ var COMPONENT_EXTENSIONS = [".tsx", ".ts", ".jsx", ".js", ".mts", ".mjs"];
7
+ function getComponentName(filePath) {
8
+ const normalized = filePath.replace(/\\/g, "/");
9
+ const parts = normalized.split("/");
10
+ const fileName = parts[parts.length - 1];
11
+ const baseName = fileName.replace(/\.(tsx?|jsx?|mts|mjs)$/, "");
12
+ if (baseName === "index") {
13
+ return parts.length >= 2 ? parts[parts.length - 2] : null;
14
+ }
15
+ if (parts.length >= 2 && baseName === parts[parts.length - 2]) {
16
+ return baseName;
17
+ }
18
+ return baseName;
19
+ }
20
+ function customDashboardPlugin() {
21
+ const componentsDir = path.resolve(process.cwd(), "src/admin/components");
22
+ const overridesByName = /* @__PURE__ */ new Map();
23
+ if (fs.existsSync(componentsDir)) {
24
+ for (const file of fs.readdirSync(componentsDir)) {
25
+ const fullPath = path.resolve(componentsDir, file);
26
+ if (!fs.statSync(fullPath).isFile()) continue;
27
+ const name = file.replace(/\.(tsx?|jsx?|mts|mjs)$/, "");
28
+ overridesByName.set(name, fullPath);
29
+ }
30
+ }
31
+ const hasOverrides = overridesByName.size > 0;
32
+ if (hasOverrides) {
33
+ console.log("[custom-dashboard] overrides:", [...overridesByName.keys()]);
34
+ }
7
35
  return {
8
- name: "dashboard-menu-config",
9
- config() {
10
- return {
11
- optimizeDeps: {
12
- exclude: [VIRTUAL_MODULE_ID]
13
- }
14
- };
36
+ name: "custom-dashboard",
37
+ enforce: "pre",
38
+ config(config) {
39
+ config.optimizeDeps = config.optimizeDeps || {};
40
+ config.optimizeDeps.exclude = config.optimizeDeps.exclude || [];
41
+ config.optimizeDeps.exclude.push(MENU_VIRTUAL_ID);
42
+ if (hasOverrides) {
43
+ config.optimizeDeps.esbuildOptions = config.optimizeDeps.esbuildOptions || {};
44
+ config.optimizeDeps.esbuildOptions.plugins = config.optimizeDeps.esbuildOptions.plugins || [];
45
+ const overrides = overridesByName;
46
+ config.optimizeDeps.esbuildOptions.plugins.push({
47
+ name: "dashboard-component-overrides",
48
+ setup(build) {
49
+ build.onLoad({ filter: /app\.(mjs|js)$/ }, (args) => {
50
+ const normalized = args.path.replace(/\\/g, "/");
51
+ if (!normalized.includes("/dashboard/dist/")) return void 0;
52
+ const srcEntry = args.path.replace(/\/dist\/app\.(mjs|js)$/, "/src/app.tsx");
53
+ if (!fs.existsSync(srcEntry)) return void 0;
54
+ console.log(
55
+ `[custom-dashboard] Redirecting entry: ${args.path} \u2192 ${srcEntry}`
56
+ );
57
+ return {
58
+ contents: fs.readFileSync(srcEntry, "utf-8"),
59
+ loader: "tsx",
60
+ resolveDir: path.dirname(srcEntry)
61
+ };
62
+ });
63
+ build.onLoad({ filter: /\.(tsx?|jsx?)$/ }, (args) => {
64
+ const normalized = args.path.replace(/\\/g, "/");
65
+ if (!normalized.includes("/dashboard/src/")) return void 0;
66
+ const fileName = path.basename(args.path);
67
+ if (fileName.startsWith("index.")) return void 0;
68
+ const componentName = getComponentName(args.path);
69
+ if (componentName && overrides.has(componentName)) {
70
+ const overridePath = overrides.get(componentName);
71
+ const ext = path.extname(overridePath).slice(1);
72
+ console.log(
73
+ `[custom-dashboard] Override: ${componentName} \u2192 ${overridePath}`
74
+ );
75
+ return {
76
+ contents: fs.readFileSync(overridePath, "utf-8"),
77
+ loader: ext,
78
+ resolveDir: path.dirname(overridePath)
79
+ };
80
+ }
81
+ return void 0;
82
+ });
83
+ }
84
+ });
85
+ config.optimizeDeps.force = true;
86
+ }
15
87
  },
16
- resolveId(id) {
17
- if (id === VIRTUAL_MODULE_ID) return RESOLVED_ID;
88
+ resolveId(source) {
89
+ if (source === MENU_VIRTUAL_ID) return MENU_RESOLVED_ID;
90
+ return null;
18
91
  },
19
92
  load(id) {
20
- if (id !== RESOLVED_ID) return;
93
+ if (id !== MENU_RESOLVED_ID) return;
21
94
  const basePath = path.resolve(process.cwd(), "src/admin/menu.config");
22
- for (const ext of [".tsx", ".ts", ".jsx", ".js", ".mts", ".mjs"]) {
95
+ for (const ext of COMPONENT_EXTENSIONS) {
23
96
  if (fs.existsSync(basePath + ext)) {
24
97
  return `export { default } from "${basePath + ext}"`;
25
98
  }
@@ -28,6 +101,8 @@ function menuConfigPlugin() {
28
101
  }
29
102
  };
30
103
  }
104
+ var menuConfigPlugin = customDashboardPlugin;
31
105
  export {
106
+ customDashboardPlugin,
32
107
  menuConfigPlugin
33
108
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mantajs/dashboard",
3
- "version": "0.1.10",
3
+ "version": "0.1.12",
4
4
  "description": "B2B Admin Dashboard for Medusa - Fork of @medusajs/dashboard",
5
5
  "scripts": {
6
6
  "dev": "vite",
@@ -84,6 +84,7 @@
84
84
  "@medusajs/types": "2.13.1",
85
85
  "@medusajs/ui-preset": "2.13.1",
86
86
  "@types/node": "^20.0.0",
87
+ "@types/react": "^19.2.13",
87
88
  "@vitejs/plugin-react": "^4.0.0",
88
89
  "autoprefixer": "^10.4.16",
89
90
  "postcss": "^8.4.31",
@@ -211,7 +211,7 @@ const useCoreRoutes = (): Omit<INavItem, "pathname">[] => {
211
211
  items: item.items?.map((sub) => ({
212
212
  label: sub.useTranslation ? t(sub.label) : sub.label,
213
213
  to: sub.to,
214
- })),
214
+ })) ?? [],
215
215
  }))
216
216
  }
217
217
 
@@ -253,7 +253,7 @@ const CoreRouteSection = () => {
253
253
  menuItems.forEach((item) => {
254
254
  if (item.nested) {
255
255
  const route = coreRoutes.find((route) => route.to === item.nested)
256
- if (route) {
256
+ if (route && !route.items?.some((existing) => existing.to === item.to)) {
257
257
  route.items?.push(item)
258
258
  }
259
259
  }
@@ -8,6 +8,7 @@ import { SettingsLayout } from "../../components/layout/settings-layout"
8
8
  import { ErrorBoundary } from "../../components/utilities/error-boundary"
9
9
  import { TaxRegionDetailBreadcrumb } from "../../routes/tax-regions/tax-region-detail/breadcrumb"
10
10
  import { taxRegionLoader } from "../../routes/tax-regions/tax-region-detail/loader"
11
+ import { mergeExtensionRoutes } from "./merge-extension-routes"
11
12
 
12
13
  export function getRouteMap({
13
14
  settingsRoutes,
@@ -23,7 +24,7 @@ export function getRouteMap({
23
24
  children: [
24
25
  {
25
26
  element: <MainLayout />,
26
- children: [
27
+ children: mergeExtensionRoutes([
27
28
  {
28
29
  path: "/",
29
30
  errorElement: <ErrorBoundary />,
@@ -918,8 +919,7 @@ export function getRouteMap({
918
919
  },
919
920
  ],
920
921
  },
921
- ...coreRoutes,
922
- ],
922
+ ], coreRoutes),
923
923
  },
924
924
  ],
925
925
  },
@@ -933,7 +933,7 @@ export function getRouteMap({
933
933
  breadcrumb: () => t("app.nav.settings.header"),
934
934
  },
935
935
  element: <SettingsLayout />,
936
- children: [
936
+ children: mergeExtensionRoutes([
937
937
  {
938
938
  index: true,
939
939
  errorElement: <ErrorBoundary />,
@@ -1877,8 +1877,7 @@ export function getRouteMap({
1877
1877
  },
1878
1878
  ],
1879
1879
  },
1880
- ...(settingsRoutes?.[0]?.children || []),
1881
- ],
1880
+ ], settingsRoutes?.[0]?.children || []),
1882
1881
  },
1883
1882
  ],
1884
1883
  },
@@ -0,0 +1,51 @@
1
+ import { RouteObject } from "react-router-dom"
2
+
3
+ /**
4
+ * Merges extension routes into static routes. When an extension route has the
5
+ * same path as a static route, the extension's component/lazy wins while
6
+ * static children that are not redefined by the extension are preserved.
7
+ */
8
+ export function mergeExtensionRoutes(
9
+ staticRoutes: RouteObject[],
10
+ extensionRoutes: RouteObject[]
11
+ ): RouteObject[] {
12
+ if (!extensionRoutes.length) return staticRoutes
13
+
14
+ const result = [...staticRoutes]
15
+
16
+ for (const ext of extensionRoutes) {
17
+ const extPath = (ext.path ?? "").replace(/^\/+/, "")
18
+ const idx = result.findIndex(
19
+ (r) => (r.path ?? "").replace(/^\/+/, "") === extPath
20
+ )
21
+
22
+ if (idx === -1) {
23
+ result.push(ext)
24
+ continue
25
+ }
26
+
27
+ const existing = result[idx]
28
+ const merged = { ...existing }
29
+
30
+ // Extension component takes priority
31
+ if (ext.lazy) {
32
+ merged.lazy = ext.lazy
33
+ delete (merged as any).Component
34
+ delete (merged as any).loader
35
+ }
36
+
37
+ // Recursively merge children — static children not redefined by the
38
+ // extension are preserved (e.g. /orders/:id stays when only /orders ""
39
+ // is overridden).
40
+ if (ext.children || existing.children) {
41
+ merged.children = mergeExtensionRoutes(
42
+ existing.children || [],
43
+ ext.children || []
44
+ )
45
+ }
46
+
47
+ result[idx] = merged
48
+ }
49
+
50
+ return result
51
+ }
@@ -2,26 +2,141 @@ import { Plugin } from "vite"
2
2
  import path from "path"
3
3
  import fs from "fs"
4
4
 
5
- const VIRTUAL_MODULE_ID = "virtual:dashboard/menu-config"
6
- const RESOLVED_ID = "\0" + VIRTUAL_MODULE_ID
5
+ const MENU_VIRTUAL_ID = "virtual:dashboard/menu-config"
6
+ const MENU_RESOLVED_ID = "\0" + MENU_VIRTUAL_ID
7
+
8
+ const COMPONENT_EXTENSIONS = [".tsx", ".ts", ".jsx", ".js", ".mts", ".mjs"]
9
+
10
+ /**
11
+ * Extract a component name from a resolved file path.
12
+ */
13
+ function getComponentName(filePath: string): string | null {
14
+ const normalized = filePath.replace(/\\/g, "/")
15
+ const parts = normalized.split("/")
16
+ const fileName = parts[parts.length - 1]
17
+ const baseName = fileName.replace(/\.(tsx?|jsx?|mts|mjs)$/, "")
18
+
19
+ if (baseName === "index") {
20
+ return parts.length >= 2 ? parts[parts.length - 2] : null
21
+ }
22
+ if (parts.length >= 2 && baseName === parts[parts.length - 2]) {
23
+ return baseName
24
+ }
25
+ return baseName
26
+ }
27
+
28
+ /**
29
+ * Unified Vite plugin for @mantajs/dashboard.
30
+ *
31
+ * Handles:
32
+ * 1. Menu config virtual module (virtual:dashboard/menu-config)
33
+ * 2. Component overrides — any file in src/admin/components/ overrides the
34
+ * dashboard component with the same name.
35
+ */
36
+ export function customDashboardPlugin(): Plugin {
37
+ const componentsDir = path.resolve(process.cwd(), "src/admin/components")
38
+ const overridesByName = new Map<string, string>()
39
+
40
+ if (fs.existsSync(componentsDir)) {
41
+ for (const file of fs.readdirSync(componentsDir)) {
42
+ const fullPath = path.resolve(componentsDir, file)
43
+ if (!fs.statSync(fullPath).isFile()) continue
44
+ const name = file.replace(/\.(tsx?|jsx?|mts|mjs)$/, "")
45
+ overridesByName.set(name, fullPath)
46
+ }
47
+ }
48
+
49
+ const hasOverrides = overridesByName.size > 0
50
+
51
+ if (hasOverrides) {
52
+ console.log("[custom-dashboard] overrides:", [...overridesByName.keys()])
53
+ }
7
54
 
8
- export function menuConfigPlugin(): Plugin {
9
55
  return {
10
- name: "dashboard-menu-config",
11
- config() {
12
- return {
13
- optimizeDeps: {
14
- exclude: [VIRTUAL_MODULE_ID],
15
- },
56
+ name: "custom-dashboard",
57
+ enforce: "pre",
58
+
59
+ config(config) {
60
+ // Always exclude the menu virtual module
61
+ config.optimizeDeps = config.optimizeDeps || {}
62
+ config.optimizeDeps.exclude = config.optimizeDeps.exclude || []
63
+ config.optimizeDeps.exclude.push(MENU_VIRTUAL_ID)
64
+
65
+ if (hasOverrides) {
66
+ // Strategy: the package.json points to dist/app.mjs (so the browser
67
+ // gets a working pre-bundled chunk — no blank page). But during
68
+ // esbuild pre-bundling we redirect the dist entry to the source TSX
69
+ // via onLoad, so esbuild follows individual imports and we can swap
70
+ // component files with the user's overrides.
71
+ config.optimizeDeps.esbuildOptions = config.optimizeDeps.esbuildOptions || {}
72
+ config.optimizeDeps.esbuildOptions.plugins =
73
+ config.optimizeDeps.esbuildOptions.plugins || []
74
+
75
+ const overrides = overridesByName
76
+ config.optimizeDeps.esbuildOptions.plugins.push({
77
+ name: "dashboard-component-overrides",
78
+ setup(build) {
79
+ // 1. Redirect the dist entry to source so esbuild processes
80
+ // individual TSX files instead of one big pre-built bundle.
81
+ build.onLoad({ filter: /app\.(mjs|js)$/ }, (args) => {
82
+ const normalized = args.path.replace(/\\/g, "/")
83
+ if (!normalized.includes("/dashboard/dist/")) return undefined
84
+
85
+ const srcEntry = args.path
86
+ .replace(/\/dist\/app\.(mjs|js)$/, "/src/app.tsx")
87
+ if (!fs.existsSync(srcEntry)) return undefined
88
+
89
+ console.log(
90
+ `[custom-dashboard] Redirecting entry: ${args.path} → ${srcEntry}`
91
+ )
92
+ return {
93
+ contents: fs.readFileSync(srcEntry, "utf-8"),
94
+ loader: "tsx",
95
+ resolveDir: path.dirname(srcEntry),
96
+ }
97
+ })
98
+
99
+ // 2. Intercept individual source files to swap with overrides.
100
+ build.onLoad({ filter: /\.(tsx?|jsx?)$/ }, (args) => {
101
+ const normalized = args.path.replace(/\\/g, "/")
102
+ if (!normalized.includes("/dashboard/src/")) return undefined
103
+
104
+ // Skip index/barrel files to preserve re-exports
105
+ const fileName = path.basename(args.path)
106
+ if (fileName.startsWith("index.")) return undefined
107
+
108
+ const componentName = getComponentName(args.path)
109
+ if (componentName && overrides.has(componentName)) {
110
+ const overridePath = overrides.get(componentName)!
111
+ const ext = path.extname(overridePath).slice(1)
112
+ console.log(
113
+ `[custom-dashboard] Override: ${componentName} → ${overridePath}`
114
+ )
115
+ return {
116
+ contents: fs.readFileSync(overridePath, "utf-8"),
117
+ loader: ext as any,
118
+ resolveDir: path.dirname(overridePath),
119
+ }
120
+ }
121
+ return undefined
122
+ })
123
+ },
124
+ })
125
+
126
+ // Force re-optimisation so overrides are always applied
127
+ config.optimizeDeps.force = true
16
128
  }
17
129
  },
18
- resolveId(id) {
19
- if (id === VIRTUAL_MODULE_ID) return RESOLVED_ID
130
+
131
+ resolveId(source) {
132
+ if (source === MENU_VIRTUAL_ID) return MENU_RESOLVED_ID
133
+ return null
20
134
  },
135
+
21
136
  load(id) {
22
- if (id !== RESOLVED_ID) return
137
+ if (id !== MENU_RESOLVED_ID) return
23
138
  const basePath = path.resolve(process.cwd(), "src/admin/menu.config")
24
- for (const ext of [".tsx", ".ts", ".jsx", ".js", ".mts", ".mjs"]) {
139
+ for (const ext of COMPONENT_EXTENSIONS) {
25
140
  if (fs.existsSync(basePath + ext)) {
26
141
  return `export { default } from "${basePath + ext}"`
27
142
  }
@@ -31,4 +146,7 @@ export function menuConfigPlugin(): Plugin {
31
146
  }
32
147
  }
33
148
 
149
+ // Keep backward-compatible alias
150
+ export const menuConfigPlugin = customDashboardPlugin
151
+
34
152
  export type { MenuConfig, MenuItem, MenuNestedItem } from "./types"