@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.
- package/dist/app.css +3 -0
- package/dist/app.js +45 -10
- package/dist/app.mjs +1 -1
- package/dist/{chunk-6AOXP3RP.mjs → chunk-VHUQFKRO.mjs} +45 -16
- package/dist/en.json +3311 -0
- package/dist/index.d.ts +10 -0
- package/dist/{product-attributes-WOKHQSU3.mjs → product-attributes-NT25DQN6.mjs} +1 -1
- package/dist/{product-create-T4PVLEL7.mjs → product-create-GZANVK54.mjs} +1 -1
- package/dist/{product-detail-77R3VVI5.mjs → product-detail-P3LEJNTC.mjs} +1 -1
- package/dist/{product-edit-CPKK4QSD.mjs → product-edit-ZWEDI4DK.mjs} +1 -1
- package/dist/{product-organization-BH5TZRYS.mjs → product-organization-3PJP3Y43.mjs} +1 -1
- package/dist/vite-plugin/index.d.mts +31 -0
- package/dist/vite-plugin/index.d.ts +31 -0
- package/dist/vite-plugin/index.js +90 -14
- package/dist/vite-plugin/index.mjs +89 -14
- package/package.json +2 -1
- package/src/components/layout/main-layout/main-layout.tsx +2 -2
- package/src/dashboard-app/routes/get-route.map.tsx +5 -6
- package/src/dashboard-app/routes/merge-extension-routes.ts +51 -0
- package/src/vite-plugin/index.ts +131 -13
- package/src/vite-plugin/types.ts +3 -1
package/dist/index.d.ts
ADDED
|
@@ -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
|
|
39
|
-
var
|
|
40
|
-
|
|
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
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
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(
|
|
51
|
-
if (
|
|
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 !==
|
|
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
|
|
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
|
|
5
|
-
var
|
|
6
|
-
|
|
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
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
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(
|
|
17
|
-
if (
|
|
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 !==
|
|
93
|
+
if (id !== MENU_RESOLVED_ID) return;
|
|
21
94
|
const basePath = path.resolve(process.cwd(), "src/admin/menu.config");
|
|
22
|
-
for (const ext of
|
|
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.
|
|
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
|
-
|
|
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
|
-
|
|
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
|
+
}
|
package/src/vite-plugin/index.ts
CHANGED
|
@@ -2,26 +2,141 @@ import { Plugin } from "vite"
|
|
|
2
2
|
import path from "path"
|
|
3
3
|
import fs from "fs"
|
|
4
4
|
|
|
5
|
-
const
|
|
6
|
-
const
|
|
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
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
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
|
-
|
|
19
|
-
|
|
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 !==
|
|
137
|
+
if (id !== MENU_RESOLVED_ID) return
|
|
23
138
|
const basePath = path.resolve(process.cwd(), "src/admin/menu.config")
|
|
24
|
-
for (const ext of
|
|
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"
|