@mantajs/dashboard 0.1.11 → 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.
package/dist/app.css CHANGED
@@ -1574,6 +1574,9 @@ video {
1574
1574
  .grid {
1575
1575
  display: grid;
1576
1576
  }
1577
+ .contents {
1578
+ display: contents;
1579
+ }
1577
1580
  .hidden {
1578
1581
  display: none;
1579
1582
  }
package/dist/app.js CHANGED
@@ -100539,7 +100539,7 @@ var init_main_layout = __esm({
100539
100539
  items: item.items?.map((sub2) => ({
100540
100540
  label: sub2.useTranslation ? t5(sub2.label) : sub2.label,
100541
100541
  to: sub2.to
100542
- }))
100542
+ })) ?? []
100543
100543
  }));
100544
100544
  };
100545
100545
  Searchbar = () => {
@@ -100569,7 +100569,7 @@ var init_main_layout = __esm({
100569
100569
  menuItems.forEach((item) => {
100570
100570
  if (item.nested) {
100571
100571
  const route = coreRoutes.find((route2) => route2.to === item.nested);
100572
- if (route) {
100572
+ if (route && !route.items?.some((existing) => existing.to === item.to)) {
100573
100573
  route.items?.push(item);
100574
100574
  }
100575
100575
  }
@@ -102790,6 +102790,42 @@ var init_loader = __esm({
102790
102790
  }
102791
102791
  });
102792
102792
 
102793
+ // src/dashboard-app/routes/merge-extension-routes.ts
102794
+ function mergeExtensionRoutes(staticRoutes, extensionRoutes) {
102795
+ if (!extensionRoutes.length) return staticRoutes;
102796
+ const result = [...staticRoutes];
102797
+ for (const ext of extensionRoutes) {
102798
+ const extPath = (ext.path ?? "").replace(/^\/+/, "");
102799
+ const idx = result.findIndex(
102800
+ (r) => (r.path ?? "").replace(/^\/+/, "") === extPath
102801
+ );
102802
+ if (idx === -1) {
102803
+ result.push(ext);
102804
+ continue;
102805
+ }
102806
+ const existing = result[idx];
102807
+ const merged = { ...existing };
102808
+ if (ext.lazy) {
102809
+ merged.lazy = ext.lazy;
102810
+ delete merged.Component;
102811
+ delete merged.loader;
102812
+ }
102813
+ if (ext.children || existing.children) {
102814
+ merged.children = mergeExtensionRoutes(
102815
+ existing.children || [],
102816
+ ext.children || []
102817
+ );
102818
+ }
102819
+ result[idx] = merged;
102820
+ }
102821
+ return result;
102822
+ }
102823
+ var init_merge_extension_routes = __esm({
102824
+ "src/dashboard-app/routes/merge-extension-routes.ts"() {
102825
+ "use strict";
102826
+ }
102827
+ });
102828
+
102793
102829
  // src/routes/home/home.tsx
102794
102830
  var import_react28, import_react_router_dom13, import_jsx_runtime27, Home;
102795
102831
  var init_home = __esm({
@@ -193924,7 +193960,7 @@ function getRouteMap({
193924
193960
  children: [
193925
193961
  {
193926
193962
  element: /* @__PURE__ */ (0, import_jsx_runtime706.jsx)(MainLayout, {}),
193927
- children: [
193963
+ children: mergeExtensionRoutes([
193928
193964
  {
193929
193965
  path: "/",
193930
193966
  errorElement: /* @__PURE__ */ (0, import_jsx_runtime706.jsx)(ErrorBoundary, {}),
@@ -194610,9 +194646,8 @@ function getRouteMap({
194610
194646
  ]
194611
194647
  }
194612
194648
  ]
194613
- },
194614
- ...coreRoutes
194615
- ]
194649
+ }
194650
+ ], coreRoutes)
194616
194651
  }
194617
194652
  ]
194618
194653
  },
@@ -194626,7 +194661,7 @@ function getRouteMap({
194626
194661
  breadcrumb: () => (0, import_i18next9.t)("app.nav.settings.header")
194627
194662
  },
194628
194663
  element: /* @__PURE__ */ (0, import_jsx_runtime706.jsx)(SettingsLayout, {}),
194629
- children: [
194664
+ children: mergeExtensionRoutes([
194630
194665
  {
194631
194666
  index: true,
194632
194667
  errorElement: /* @__PURE__ */ (0, import_jsx_runtime706.jsx)(ErrorBoundary, {}),
@@ -195357,9 +195392,8 @@ function getRouteMap({
195357
195392
  lazy: () => Promise.resolve().then(() => (init_add_locales2(), add_locales_exports))
195358
195393
  }
195359
195394
  ]
195360
- },
195361
- ...settingsRoutes?.[0]?.children || []
195362
- ]
195395
+ }
195396
+ ], settingsRoutes?.[0]?.children || [])
195363
195397
  }
195364
195398
  ]
195365
195399
  },
@@ -195404,6 +195438,7 @@ var init_get_route_map = __esm({
195404
195438
  init_error_boundary2();
195405
195439
  init_breadcrumb();
195406
195440
  init_loader();
195441
+ init_merge_extension_routes();
195407
195442
  import_jsx_runtime706 = require("react/jsx-runtime");
195408
195443
  }
195409
195444
  });
package/dist/app.mjs CHANGED
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  DashboardApp
3
- } from "./chunk-6AOXP3RP.mjs";
3
+ } from "./chunk-VHUQFKRO.mjs";
4
4
  import "./chunk-2SSUH2HJ.mjs";
5
5
  import "./chunk-ONB3JEHR.mjs";
6
6
  import "./chunk-YCDDT44O.mjs";
@@ -95537,7 +95537,7 @@ var useCoreRoutes = () => {
95537
95537
  items: item.items?.map((sub) => ({
95538
95538
  label: sub.useTranslation ? t2(sub.label) : sub.label,
95539
95539
  to: sub.to
95540
- }))
95540
+ })) ?? []
95541
95541
  }));
95542
95542
  };
95543
95543
  var Searchbar = () => {
@@ -95567,7 +95567,7 @@ var CoreRouteSection = () => {
95567
95567
  menuItems.forEach((item) => {
95568
95568
  if (item.nested) {
95569
95569
  const route = coreRoutes.find((route2) => route2.to === item.nested);
95570
- if (route) {
95570
+ if (route && !route.items?.some((existing) => existing.to === item.to)) {
95571
95571
  route.items?.push(item);
95572
95572
  }
95573
95573
  }
@@ -95910,6 +95910,37 @@ var ErrorBoundary = () => {
95910
95910
  ] }) }) });
95911
95911
  };
95912
95912
 
95913
+ // src/dashboard-app/routes/merge-extension-routes.ts
95914
+ function mergeExtensionRoutes(staticRoutes, extensionRoutes) {
95915
+ if (!extensionRoutes.length) return staticRoutes;
95916
+ const result = [...staticRoutes];
95917
+ for (const ext of extensionRoutes) {
95918
+ const extPath = (ext.path ?? "").replace(/^\/+/, "");
95919
+ const idx = result.findIndex(
95920
+ (r) => (r.path ?? "").replace(/^\/+/, "") === extPath
95921
+ );
95922
+ if (idx === -1) {
95923
+ result.push(ext);
95924
+ continue;
95925
+ }
95926
+ const existing = result[idx];
95927
+ const merged = { ...existing };
95928
+ if (ext.lazy) {
95929
+ merged.lazy = ext.lazy;
95930
+ delete merged.Component;
95931
+ delete merged.loader;
95932
+ }
95933
+ if (ext.children || existing.children) {
95934
+ merged.children = mergeExtensionRoutes(
95935
+ existing.children || [],
95936
+ ext.children || []
95937
+ );
95938
+ }
95939
+ result[idx] = merged;
95940
+ }
95941
+ return result;
95942
+ }
95943
+
95913
95944
  // src/dashboard-app/routes/get-route.map.tsx
95914
95945
  import { jsx as jsx17 } from "react/jsx-runtime";
95915
95946
  function getRouteMap({
@@ -95923,7 +95954,7 @@ function getRouteMap({
95923
95954
  children: [
95924
95955
  {
95925
95956
  element: /* @__PURE__ */ jsx17(MainLayout, {}),
95926
- children: [
95957
+ children: mergeExtensionRoutes([
95927
95958
  {
95928
95959
  path: "/",
95929
95960
  errorElement: /* @__PURE__ */ jsx17(ErrorBoundary, {}),
@@ -95950,7 +95981,7 @@ function getRouteMap({
95950
95981
  children: [
95951
95982
  {
95952
95983
  path: "create",
95953
- lazy: () => import("./product-create-T4PVLEL7.mjs")
95984
+ lazy: () => import("./product-create-GZANVK54.mjs")
95954
95985
  },
95955
95986
  {
95956
95987
  path: "import",
@@ -95966,7 +95997,7 @@ function getRouteMap({
95966
95997
  path: ":id",
95967
95998
  errorElement: /* @__PURE__ */ jsx17(ErrorBoundary, {}),
95968
95999
  lazy: async () => {
95969
- const { Breadcrumb, loader } = await import("./product-detail-77R3VVI5.mjs");
96000
+ const { Breadcrumb, loader } = await import("./product-detail-P3LEJNTC.mjs");
95970
96001
  return {
95971
96002
  Component: Outlet4,
95972
96003
  loader,
@@ -95978,11 +96009,11 @@ function getRouteMap({
95978
96009
  children: [
95979
96010
  {
95980
96011
  path: "",
95981
- lazy: () => import("./product-detail-77R3VVI5.mjs"),
96012
+ lazy: () => import("./product-detail-P3LEJNTC.mjs"),
95982
96013
  children: [
95983
96014
  {
95984
96015
  path: "edit",
95985
- lazy: () => import("./product-edit-CPKK4QSD.mjs")
96016
+ lazy: () => import("./product-edit-ZWEDI4DK.mjs")
95986
96017
  },
95987
96018
  {
95988
96019
  path: "edit-variant",
@@ -95994,11 +96025,11 @@ function getRouteMap({
95994
96025
  },
95995
96026
  {
95996
96027
  path: "attributes",
95997
- lazy: () => import("./product-attributes-WOKHQSU3.mjs")
96028
+ lazy: () => import("./product-attributes-NT25DQN6.mjs")
95998
96029
  },
95999
96030
  {
96000
96031
  path: "organization",
96001
- lazy: () => import("./product-organization-BH5TZRYS.mjs")
96032
+ lazy: () => import("./product-organization-3PJP3Y43.mjs")
96002
96033
  },
96003
96034
  {
96004
96035
  path: "shipping-profile",
@@ -96609,9 +96640,8 @@ function getRouteMap({
96609
96640
  ]
96610
96641
  }
96611
96642
  ]
96612
- },
96613
- ...coreRoutes
96614
- ]
96643
+ }
96644
+ ], coreRoutes)
96615
96645
  }
96616
96646
  ]
96617
96647
  },
@@ -96625,7 +96655,7 @@ function getRouteMap({
96625
96655
  breadcrumb: () => t("app.nav.settings.header")
96626
96656
  },
96627
96657
  element: /* @__PURE__ */ jsx17(SettingsLayout, {}),
96628
- children: [
96658
+ children: mergeExtensionRoutes([
96629
96659
  {
96630
96660
  index: true,
96631
96661
  errorElement: /* @__PURE__ */ jsx17(ErrorBoundary, {}),
@@ -97356,9 +97386,8 @@ function getRouteMap({
97356
97386
  lazy: () => import("./add-locales-47HF5EWF.mjs")
97357
97387
  }
97358
97388
  ]
97359
- },
97360
- ...settingsRoutes?.[0]?.children || []
97361
- ]
97389
+ }
97390
+ ], settingsRoutes?.[0]?.children || [])
97362
97391
  }
97363
97392
  ]
97364
97393
  },
@@ -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";
@@ -17,6 +17,15 @@ type MenuConfig = {
17
17
  items: MenuItem[];
18
18
  };
19
19
 
20
- declare function menuConfigPlugin(): Plugin;
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;
21
30
 
22
- export { type MenuConfig, type MenuItem, type MenuNestedItem, menuConfigPlugin };
31
+ export { type MenuConfig, type MenuItem, type MenuNestedItem, customDashboardPlugin, menuConfigPlugin };
@@ -17,6 +17,15 @@ type MenuConfig = {
17
17
  items: MenuItem[];
18
18
  };
19
19
 
20
- declare function menuConfigPlugin(): Plugin;
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;
21
30
 
22
- export { type MenuConfig, type MenuItem, type MenuNestedItem, menuConfigPlugin };
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.11",
3
+ "version": "0.1.12",
4
4
  "description": "B2B Admin Dashboard for Medusa - Fork of @medusajs/dashboard",
5
5
  "scripts": {
6
6
  "dev": "vite",
@@ -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"