@mercurjs/dashboard-sdk 2.0.0-canary.9 → 2.0.0-canary.90

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/index.cjs CHANGED
@@ -30,24 +30,49 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
30
30
  // src/index.ts
31
31
  var index_exports = {};
32
32
  __export(index_exports, {
33
- dashboardPlugin: () => dashboardPlugin,
34
- defineConfig: () => defineConfig
33
+ generatePluginEntryModule: () => generatePluginEntryModule,
34
+ mercurDashboardPlugin: () => mercurDashboardPlugin
35
35
  });
36
36
  module.exports = __toCommonJS(index_exports);
37
37
 
38
38
  // src/plugin.ts
39
39
  var import_path5 = __toESM(require("path"), 1);
40
+ var import_fs4 = __toESM(require("fs"), 1);
41
+
42
+ // src/babel.ts
43
+ var import_parser = require("@babel/parser");
44
+ var import_traverse = __toESM(require("@babel/traverse"), 1);
45
+ var import_types = require("@babel/types");
46
+ var traverse;
47
+ if (typeof import_traverse.default === "function") {
48
+ traverse = import_traverse.default;
49
+ } else {
50
+ traverse = import_traverse.default.default;
51
+ }
40
52
 
41
53
  // src/utils.ts
54
+ function normalizePath(filePath) {
55
+ return filePath.replace(/\\/g, "/");
56
+ }
57
+ function getParserOptions(file) {
58
+ const options = {
59
+ sourceType: "module",
60
+ plugins: ["jsx"]
61
+ };
62
+ if (file.endsWith(".ts") || file.endsWith(".tsx")) {
63
+ options.plugins.push("typescript");
64
+ }
65
+ return options;
66
+ }
42
67
  function resolveExports(moduleExports) {
43
68
  if ("default" in moduleExports && moduleExports.default && "default" in moduleExports.default) {
44
69
  return resolveExports(moduleExports.default);
45
70
  }
46
71
  return moduleExports;
47
72
  }
48
- async function getFileExports(path6) {
73
+ async function getFileExports(path7) {
49
74
  const { unregister } = await safeRegister();
50
- const module2 = require(path6);
75
+ const module2 = require(path7);
51
76
  unregister();
52
77
  return resolveExports(module2);
53
78
  }
@@ -67,13 +92,31 @@ var safeRegister = async () => {
67
92
  }
68
93
  return res;
69
94
  };
70
- function normalizePath(filePath) {
71
- return filePath.replace(/\\/g, "/");
95
+ function hasDefaultExport(ast) {
96
+ let found = false;
97
+ traverse(ast, {
98
+ ExportDefaultDeclaration() {
99
+ found = true;
100
+ },
101
+ AssignmentExpression(path7) {
102
+ if (path7.node.left.type === "MemberExpression" && path7.node.left.object.type === "Identifier" && path7.node.left.object.name === "exports" && path7.node.left.property.type === "Identifier" && path7.node.left.property.name === "default") {
103
+ found = true;
104
+ }
105
+ },
106
+ ExportNamedDeclaration(path7) {
107
+ const specifiers = path7.node.specifiers;
108
+ if (specifiers?.some(
109
+ (s) => s.type === "ExportSpecifier" && s.exported.type === "Identifier" && s.exported.name === "default"
110
+ )) {
111
+ found = true;
112
+ }
113
+ }
114
+ });
115
+ return found;
72
116
  }
73
117
 
74
118
  // src/constants.ts
75
119
  var VALID_FILE_EXTENSIONS = [".tsx", ".ts", ".jsx", ".js"];
76
- var CONFIG_NAME = "mercur.config.ts";
77
120
  var CONFIG_VIRTUAL_MODULE = "virtual:mercur/config";
78
121
  var ROUTES_VIRTUAL_MODULE = "virtual:mercur/routes";
79
122
  var COMPONENTS_VIRTUAL_MODULE = "virtual:mercur/components";
@@ -98,17 +141,17 @@ var import_path4 = __toESM(require("path"), 1);
98
141
  // src/routes.ts
99
142
  var import_fs = __toESM(require("fs"), 1);
100
143
  var import_path = __toESM(require("path"), 1);
101
- function getRoute(file, pagesDir) {
144
+ function getRoute(file, routesDir) {
102
145
  const importPath = normalizePath(file);
103
- const normalizedPagesDir = normalizePath(pagesDir);
104
- return importPath.replace(normalizedPagesDir, "").replace(/\[\[\*\]\]/g, "*?").replace(/\[\*\]/g, "*").replace(/\(([^\[\]\)]+)\)/g, "$1?").replace(/\[\[([^\]]+)\]\]/g, ":$1?").replace(/\[([^\]]+)\]/g, ":$1").replace(
146
+ const normalizedRoutesDir = normalizePath(routesDir);
147
+ return importPath.replace(normalizedRoutesDir, "").replace(/\[\[\*\]\]/g, "*?").replace(/\[\*\]/g, "*").replace(/\(([^\[\]\)]+)\)/g, "$1?").replace(/\[\[([^\]]+)\]\]/g, ":$1?").replace(/\[([^\]]+)\]/g, ":$1").replace(
105
148
  new RegExp(
106
149
  `/page\\.(${VALID_FILE_EXTENSIONS.map((ext) => ext.slice(1)).join("|")})$`
107
150
  ),
108
151
  ""
109
152
  ) || "/";
110
153
  }
111
- function crawlPages(dir, pattern = "page") {
154
+ function crawlRoutes(dir, pattern = "page") {
112
155
  const files = [];
113
156
  if (!import_fs.default.existsSync(dir)) {
114
157
  return files;
@@ -117,7 +160,7 @@ function crawlPages(dir, pattern = "page") {
117
160
  for (const entry of entries) {
118
161
  const fullPath = import_path.default.join(dir, entry.name);
119
162
  if (entry.isDirectory()) {
120
- files.push(...crawlPages(fullPath, pattern));
163
+ files.push(...crawlRoutes(fullPath, pattern));
121
164
  } else if (entry.isFile()) {
122
165
  const ext = import_path.default.extname(entry.name);
123
166
  const baseName = import_path.default.basename(entry.name, ext);
@@ -128,31 +171,51 @@ function crawlPages(dir, pattern = "page") {
128
171
  }
129
172
  return files;
130
173
  }
131
- function hasDefaultExport(filePath) {
132
- try {
133
- const content = import_fs.default.readFileSync(filePath, "utf-8");
134
- return /export\s+default\s+/.test(content) || /export\s*\{\s*[^}]*\s+as\s+default\s*[,}]/.test(content);
135
- } catch {
136
- return false;
137
- }
138
- }
139
- function hasConfigPublic(filePath) {
140
- try {
141
- const content = import_fs.default.readFileSync(filePath, "utf-8");
142
- return /export\s+const\s+config\s*=\s*\{[^}]*public\s*:\s*true/.test(content);
143
- } catch {
144
- return false;
145
- }
146
- }
147
- function getNamedExports(filePath) {
148
- try {
149
- const content = import_fs.default.readFileSync(filePath, "utf-8");
150
- const hasHandle = /export\s+(const|function|async\s+function)\s+handle\b/.test(content) || /export\s*\{[^}]*\bhandle\b[^}]*\}/.test(content);
151
- const hasLoader = /export\s+(const|function|async\s+function)\s+loader\b/.test(content) || /export\s*\{[^}]*\bloader\b[^}]*\}/.test(content);
152
- return { hasHandle, hasLoader };
153
- } catch {
154
- return { hasHandle: false, hasLoader: false };
155
- }
174
+ function hasConfigPublic(ast) {
175
+ let found = false;
176
+ traverse(ast, {
177
+ ExportNamedDeclaration(path7) {
178
+ const declaration = path7.node.declaration;
179
+ if (!(0, import_types.isVariableDeclaration)(declaration)) return;
180
+ for (const decl of declaration.declarations) {
181
+ if ((0, import_types.isVariableDeclarator)(decl) && (0, import_types.isIdentifier)(decl.id, { name: "config" }) && decl.init?.type === "ObjectExpression") {
182
+ const publicProp = decl.init.properties.find(
183
+ (prop) => (0, import_types.isObjectProperty)(prop) && (0, import_types.isIdentifier)(prop.key, { name: "public" }) && (0, import_types.isBooleanLiteral)(prop.value, { value: true })
184
+ );
185
+ if (publicProp) {
186
+ found = true;
187
+ }
188
+ }
189
+ }
190
+ }
191
+ });
192
+ return found;
193
+ }
194
+ function getNamedExports(ast) {
195
+ let hasHandle = false;
196
+ let hasLoader = false;
197
+ traverse(ast, {
198
+ ExportNamedDeclaration(path7) {
199
+ const declaration = path7.node.declaration;
200
+ if (declaration?.type === "VariableDeclaration") {
201
+ declaration.declarations.forEach((decl) => {
202
+ if (decl.id.type === "Identifier" && decl.id.name === "handle") {
203
+ hasHandle = true;
204
+ }
205
+ if (decl.id.type === "Identifier" && decl.id.name === "loader") {
206
+ hasLoader = true;
207
+ }
208
+ });
209
+ }
210
+ if (declaration?.type === "FunctionDeclaration" && declaration.id?.name === "loader") {
211
+ hasLoader = true;
212
+ }
213
+ if (declaration?.type === "FunctionDeclaration" && declaration.id?.name === "handle") {
214
+ hasHandle = true;
215
+ }
216
+ }
217
+ });
218
+ return { hasHandle, hasLoader };
156
219
  }
157
220
  function generateRouteComponentName(index) {
158
221
  return `RouteComponent${index}`;
@@ -217,19 +280,25 @@ ${indent} ]`;
217
280
  ${indent}}`;
218
281
  return result;
219
282
  }
220
- function parseFile(file, pagesDir, index) {
221
- if (!hasDefaultExport(file)) {
283
+ function parseFile(file, routesDir, index) {
284
+ try {
285
+ const code = import_fs.default.readFileSync(file, "utf-8");
286
+ const ast = (0, import_parser.parse)(code, getParserOptions(file));
287
+ if (!hasDefaultExport(ast)) {
288
+ return null;
289
+ }
290
+ const { hasHandle, hasLoader } = getNamedExports(ast);
291
+ const isPublic = hasConfigPublic(ast);
292
+ const routePath = getRoute(file, routesDir);
293
+ const imports = generateImports(file, index, hasHandle, hasLoader);
294
+ const route = generateRouteObject(routePath, index, hasHandle, hasLoader, isPublic);
295
+ return {
296
+ imports,
297
+ route
298
+ };
299
+ } catch {
222
300
  return null;
223
301
  }
224
- const { hasHandle, hasLoader } = getNamedExports(file);
225
- const isPublic = hasConfigPublic(file);
226
- const routePath = getRoute(file, pagesDir);
227
- const imports = generateImports(file, index, hasHandle, hasLoader);
228
- const route = generateRouteObject(routePath, index, hasHandle, hasLoader, isPublic);
229
- return {
230
- imports,
231
- route
232
- };
233
302
  }
234
303
  function buildRouteTree(results) {
235
304
  const routeMap = /* @__PURE__ */ new Map();
@@ -243,7 +312,7 @@ function buildRouteTree(results) {
243
312
  const parentPath = routePath.split("/@")[0];
244
313
  const parent = routeMap.get(parentPath);
245
314
  if (parent) {
246
- parent.route.children = parent.route.children || [];
315
+ parent.route.children = parent.route.children ?? [];
247
316
  parent.route.children.push({
248
317
  ...result.route,
249
318
  path: result.route.path.replace("/@", "/")
@@ -258,38 +327,44 @@ function buildRouteTree(results) {
258
327
  }
259
328
  return Array.from(routeMap.values());
260
329
  }
261
- function generateRoutes({ srcDir }) {
262
- const pagesDir = import_path.default.join(srcDir, "pages");
263
- const files = crawlPages(pagesDir);
264
- if (files.length === 0) {
265
- return `export const customRoutes = []`;
266
- }
330
+ function generateRoutes({ srcDir, pluginExtensions }) {
331
+ const routesDir = import_path.default.join(srcDir, "routes");
267
332
  let index = 0;
268
333
  const results = [];
269
- for (const file of files) {
270
- const result = parseFile(file, pagesDir, index);
334
+ for (const file of crawlRoutes(routesDir)) {
335
+ const result = parseFile(file, routesDir, index);
271
336
  if (result) {
272
337
  results.push(result);
273
338
  index++;
274
339
  }
275
340
  }
276
- if (results.length === 0) {
341
+ const pluginDeclarations = pluginExtensions.map(
342
+ (ext, i) => `const __plugin${i} = (await import("${normalizePath(ext)}")).default`
343
+ );
344
+ const pluginSpreads = pluginExtensions.map(
345
+ (_, i) => ` ...(__plugin${i}.routeModule?.routes ?? [])`
346
+ );
347
+ const routeTree = buildRouteTree(results);
348
+ const appImports = routeTree.flatMap((r) => r.imports);
349
+ const appRoutes = routeTree.map((r) => formatRoute(r.route));
350
+ const allImports = [...appImports];
351
+ const allRoutes = [...appRoutes, ...pluginSpreads];
352
+ if (allImports.length === 0 && pluginDeclarations.length === 0 && allRoutes.length === 0) {
277
353
  return `export const customRoutes = []`;
278
354
  }
279
- const routeTree = buildRouteTree(results);
280
- const imports = routeTree.flatMap((result) => result.imports);
281
- const routes = routeTree.map((result) => formatRoute(result.route));
282
- return `${imports.join("\n")}
355
+ return `${allImports.join("\n")}
356
+
357
+ ${pluginDeclarations.join("\n")}
283
358
 
284
359
  export const customRoutes = [
285
- ${routes.join(",\n")}
360
+ ${allRoutes.join(",\n")}
286
361
  ]`;
287
362
  }
288
363
 
289
364
  // src/menu-items.ts
290
365
  var import_fs2 = __toESM(require("fs"), 1);
291
366
  var import_path2 = __toESM(require("path"), 1);
292
- function crawlPages2(dir, pattern = "page") {
367
+ function crawlRoutes2(dir, pattern = "page") {
293
368
  const files = [];
294
369
  if (!import_fs2.default.existsSync(dir)) {
295
370
  return files;
@@ -298,7 +373,7 @@ function crawlPages2(dir, pattern = "page") {
298
373
  for (const entry of entries) {
299
374
  const fullPath = import_path2.default.join(dir, entry.name);
300
375
  if (entry.isDirectory()) {
301
- files.push(...crawlPages2(fullPath, pattern));
376
+ files.push(...crawlRoutes2(fullPath, pattern));
302
377
  } else if (entry.isFile()) {
303
378
  const ext = import_path2.default.extname(entry.name);
304
379
  const baseName = import_path2.default.basename(entry.name, ext);
@@ -309,39 +384,100 @@ function crawlPages2(dir, pattern = "page") {
309
384
  }
310
385
  return files;
311
386
  }
312
- function getRoute2(file, pagesDir) {
387
+ function getRoute2(file, routesDir) {
313
388
  const importPath = normalizePath(file);
314
- const normalizedPagesDir = normalizePath(pagesDir);
315
- return importPath.replace(normalizedPagesDir, "").replace(/\[\[\*\]\]/g, "*?").replace(/\[\*\]/g, "*").replace(/\(([^\[\]\)]+)\)/g, "$1?").replace(/\[\[([^\]]+)\]\]/g, ":$1?").replace(/\[([^\]]+)\]/g, ":$1").replace(
389
+ const normalizedRoutesDir = normalizePath(routesDir);
390
+ return importPath.replace(normalizedRoutesDir, "").replace(/\[\[\*\]\]/g, "*?").replace(/\[\*\]/g, "*").replace(/\(([^\[\]\)]+)\)/g, "$1?").replace(/\[\[([^\]]+)\]\]/g, ":$1?").replace(/\[([^\]]+)\]/g, ":$1").replace(
316
391
  new RegExp(
317
392
  `/page\\.(${VALID_FILE_EXTENSIONS.map((ext) => ext.slice(1)).join("|")})$`
318
393
  ),
319
394
  ""
320
395
  ) || "/";
321
396
  }
322
- function hasConfigExport(filePath) {
323
- try {
324
- const content = import_fs2.default.readFileSync(filePath, "utf-8");
325
- return /export\s+(const|let|var)\s+config\b/.test(content) || /export\s*\{[^}]*\bconfig\b[^}]*\}/.test(content);
326
- } catch {
327
- return false;
397
+ function getConfigObjectProperties(path7) {
398
+ if ((0, import_types.isVariableDeclarator)(path7.node)) {
399
+ const decl = (0, import_types.isIdentifier)(path7.node.id, { name: "config" }) ? path7.node : null;
400
+ if (!decl) return null;
401
+ if ((0, import_types.isCallExpression)(decl.init) && decl.init.arguments.length > 0 && (0, import_types.isObjectExpression)(decl.init.arguments[0])) {
402
+ return decl.init.arguments[0].properties;
403
+ }
404
+ if ((0, import_types.isObjectExpression)(decl.init)) {
405
+ return decl.init.properties;
406
+ }
407
+ return null;
408
+ }
409
+ const declaration = path7.node.declaration;
410
+ if ((0, import_types.isVariableDeclaration)(declaration)) {
411
+ const configDecl = declaration.declarations.find(
412
+ (d) => (0, import_types.isVariableDeclarator)(d) && (0, import_types.isIdentifier)(d.id, { name: "config" })
413
+ );
414
+ if (configDecl && (0, import_types.isCallExpression)(configDecl.init) && configDecl.init.arguments.length > 0 && (0, import_types.isObjectExpression)(configDecl.init.arguments[0])) {
415
+ return configDecl.init.arguments[0].properties;
416
+ }
417
+ const directDecl = declaration.declarations.find(
418
+ (d) => (0, import_types.isVariableDeclarator)(d) && (0, import_types.isIdentifier)(d.id, { name: "config" })
419
+ );
420
+ if (directDecl && (0, import_types.isObjectExpression)(directDecl.init)) {
421
+ return directDecl.init.properties;
422
+ }
423
+ }
424
+ return null;
425
+ }
426
+ function processConfigProperties(properties) {
427
+ const hasProperty = (name) => properties.some(
428
+ (prop) => (0, import_types.isObjectProperty)(prop) && (0, import_types.isIdentifier)(prop.key, { name })
429
+ );
430
+ const hasLabel = hasProperty("label");
431
+ if (!hasLabel) {
432
+ return null;
433
+ }
434
+ const hasIcon = hasProperty("icon");
435
+ const nested = properties.find(
436
+ (prop) => (0, import_types.isObjectProperty)(prop) && (0, import_types.isIdentifier)(prop.key, { name: "nested" })
437
+ );
438
+ let nestedValue;
439
+ if (nested && (0, import_types.isObjectProperty)(nested) && (0, import_types.isStringLiteral)(nested.value)) {
440
+ nestedValue = nested.value.value;
441
+ }
442
+ const translationNs = properties.find(
443
+ (prop) => (0, import_types.isObjectProperty)(prop) && (0, import_types.isIdentifier)(prop.key, { name: "translationNs" })
444
+ );
445
+ let translationNsValue;
446
+ if (translationNs && (0, import_types.isObjectProperty)(translationNs) && (0, import_types.isStringLiteral)(translationNs.value)) {
447
+ translationNsValue = translationNs.value.value;
328
448
  }
449
+ const rank = properties.find(
450
+ (prop) => (0, import_types.isObjectProperty)(prop) && (0, import_types.isIdentifier)(prop.key, { name: "rank" })
451
+ );
452
+ let rankValue;
453
+ if (rank && (0, import_types.isObjectProperty)(rank) && (0, import_types.isNumericLiteral)(rank.value)) {
454
+ rankValue = rank.value.value;
455
+ }
456
+ return { label: hasLabel, icon: hasIcon, rank: rankValue, nested: nestedValue, translationNs: translationNsValue };
329
457
  }
330
- function getConfigProperties(filePath) {
458
+ function getRouteConfig(file) {
331
459
  try {
332
- const content = import_fs2.default.readFileSync(filePath, "utf-8");
333
- const hasLabel = /\blabel\s*[:=]/.test(content);
334
- if (!hasLabel) {
335
- return null;
336
- }
337
- const hasIcon = /\bicon\s*[:=]/.test(content);
338
- const rankMatch = content.match(/\brank\s*:\s*(\d+)/);
339
- const rank = rankMatch ? parseInt(rankMatch[1], 10) : void 0;
340
- const nestedMatch = content.match(/\bnested\s*:\s*["']([^"']+)["']/);
341
- const nested = nestedMatch ? nestedMatch[1] : void 0;
342
- const translationNsMatch = content.match(/\btranslationNs\s*:\s*["']([^"']+)["']/);
343
- const translationNs = translationNsMatch ? translationNsMatch[1] : void 0;
344
- return { label: hasLabel, icon: hasIcon, rank, nested, translationNs };
460
+ const code = import_fs2.default.readFileSync(file, "utf-8");
461
+ const ast = (0, import_parser.parse)(code, getParserOptions(file));
462
+ let config = null;
463
+ let configFound = false;
464
+ traverse(ast, {
465
+ VariableDeclarator(path7) {
466
+ if (configFound) return;
467
+ const properties = getConfigObjectProperties(path7);
468
+ if (!properties) return;
469
+ config = processConfigProperties(properties);
470
+ if (config) configFound = true;
471
+ },
472
+ ExportNamedDeclaration(path7) {
473
+ if (configFound) return;
474
+ const properties = getConfigObjectProperties(path7);
475
+ if (!properties) return;
476
+ config = processConfigProperties(properties);
477
+ if (config) configFound = true;
478
+ }
479
+ });
480
+ return config;
345
481
  } catch {
346
482
  return null;
347
483
  }
@@ -353,12 +489,12 @@ function generateImport(file, index) {
353
489
  const importPath = normalizePath(file);
354
490
  return `import { config as ${generateRouteConfigName(index)} } from "${importPath}"`;
355
491
  }
356
- function generateMenuItem(config, file, pagesDir, index) {
492
+ function generateMenuItem(config, file, routesDir, index) {
357
493
  const configName = generateRouteConfigName(index);
358
494
  return {
359
495
  label: `${configName}.label`,
360
496
  icon: config.icon ? `${configName}.icon` : void 0,
361
- path: getRoute2(file, pagesDir),
497
+ path: getRoute2(file, routesDir),
362
498
  rank: config.rank,
363
499
  nested: config.nested,
364
500
  translationNs: config.translationNs ? `${configName}.translationNs` : void 0
@@ -377,44 +513,47 @@ function formatMenuItem(menuItem) {
377
513
  ${parts.join(",\n")}
378
514
  }`;
379
515
  }
380
- function parseFile2(file, pagesDir, index) {
381
- if (!hasConfigExport(file)) {
382
- return null;
383
- }
384
- const config = getConfigProperties(file);
516
+ function parseMenuItemFile(file, routesDir, index) {
517
+ const config = getRouteConfig(file);
385
518
  if (!config) {
386
519
  return null;
387
520
  }
388
521
  return {
389
522
  import: generateImport(file, index),
390
- menuItem: generateMenuItem(config, file, pagesDir, index)
523
+ menuItem: generateMenuItem(config, file, routesDir, index)
391
524
  };
392
525
  }
393
- function generateMenuItems({ srcDir }) {
394
- const pagesDir = import_path2.default.join(srcDir, "pages");
395
- const files = crawlPages2(pagesDir);
396
- if (files.length === 0) {
397
- return `export default { menuItems: [] }`;
398
- }
526
+ function generateMenuItems({ srcDir, pluginExtensions }) {
527
+ const routesDir = import_path2.default.join(srcDir, "routes");
399
528
  let index = 0;
400
529
  const results = [];
401
- for (const file of files) {
402
- const result = parseFile2(file, pagesDir, index);
530
+ for (const file of crawlRoutes2(routesDir)) {
531
+ const result = parseMenuItemFile(file, routesDir, index);
403
532
  if (result) {
404
533
  results.push(result);
405
534
  index++;
406
535
  }
407
536
  }
408
- if (results.length === 0) {
537
+ const pluginDeclarations = pluginExtensions.map(
538
+ (ext, i) => `const __plugin${i} = (await import("${normalizePath(ext)}")).default`
539
+ );
540
+ const pluginSpreads = pluginExtensions.map(
541
+ (_, i) => ` ...(__plugin${i}.menuItemModule?.menuItems ?? [])`
542
+ );
543
+ const appImports = results.map((r) => r.import);
544
+ const appMenuItems = results.map((r) => formatMenuItem(r.menuItem));
545
+ const allImports = [...appImports];
546
+ const allMenuItems = [...appMenuItems, ...pluginSpreads];
547
+ if (allImports.length === 0 && pluginDeclarations.length === 0 && allMenuItems.length === 0) {
409
548
  return `export default { menuItems: [] }`;
410
549
  }
411
- const imports = results.map((r) => r.import);
412
- const menuItems = results.map((r) => formatMenuItem(r.menuItem));
413
- return `${imports.join("\n")}
550
+ return `${allImports.join("\n")}
551
+
552
+ ${pluginDeclarations.join("\n")}
414
553
 
415
554
  export default {
416
555
  menuItems: [
417
- ${menuItems.join(",\n")}
556
+ ${allMenuItems.join(",\n")}
418
557
  ]
419
558
  }`;
420
559
  }
@@ -506,47 +645,138 @@ function loadI18nModule(mercurConfig) {
506
645
  }
507
646
 
508
647
  // src/plugin.ts
509
- function buildConfig(config, root) {
510
- const srcDir = import_path5.default.join(root, "src");
511
- return {
512
- ...config,
513
- backendUrl: config.backendUrl ?? "http://localhost:9000",
514
- root,
515
- srcDir,
516
- configPath: import_path5.default.resolve(root, CONFIG_NAME)
517
- };
648
+ function isRouteFile(file) {
649
+ const basename = import_path5.default.basename(file, import_path5.default.extname(file));
650
+ return basename === "page";
518
651
  }
519
- async function loadMercurConfig(root) {
520
- const configPath = import_path5.default.resolve(root, CONFIG_NAME);
652
+ var UI_MODULE_KEYS = ["admin_ui", "vendor_ui"];
653
+ function findNodeModulesRoot(configDir) {
654
+ let dir = configDir;
655
+ while (dir !== import_path5.default.dirname(dir)) {
656
+ const candidate = import_path5.default.join(dir, "node_modules");
657
+ if (import_fs4.default.existsSync(candidate) && import_fs4.default.statSync(candidate).isDirectory()) {
658
+ return candidate;
659
+ }
660
+ dir = import_path5.default.dirname(dir);
661
+ }
662
+ return import_path5.default.join(configDir, "node_modules");
663
+ }
664
+ function resolvePluginRoot(resolve, configDir, nodeModulesRoot) {
521
665
  try {
522
- const mod = await getFileExports(configPath);
523
- const content = mod.default ?? mod;
524
- return buildConfig(content, root);
525
- } catch (error) {
526
- console.error(error);
527
- throw new Error(
528
- `[@mercurjs/dashboard-sdk] Could not find or load ${CONFIG_NAME} in ${root}`
666
+ if (resolve.startsWith(".")) {
667
+ const resolved = import_path5.default.resolve(configDir, resolve);
668
+ if (import_fs4.default.existsSync(resolved)) {
669
+ return import_fs4.default.realpathSync(resolved);
670
+ }
671
+ return null;
672
+ }
673
+ const packagePath = import_path5.default.join(nodeModulesRoot, resolve);
674
+ if (!import_fs4.default.existsSync(packagePath)) {
675
+ return null;
676
+ }
677
+ return import_fs4.default.realpathSync(packagePath);
678
+ } catch {
679
+ return null;
680
+ }
681
+ }
682
+ function resolvePluginExtensions(plugins, configDir, appType) {
683
+ const nodeModulesRoot = findNodeModulesRoot(configDir);
684
+ const extensions = [];
685
+ for (const plugin of plugins) {
686
+ const resolve = typeof plugin === "string" ? plugin : plugin?.resolve;
687
+ if (!resolve || typeof resolve !== "string") continue;
688
+ const pluginRoot = resolvePluginRoot(resolve, configDir, nodeModulesRoot);
689
+ if (!pluginRoot) continue;
690
+ const extFile = import_path5.default.join(
691
+ pluginRoot,
692
+ ".medusa/server/src",
693
+ appType,
694
+ "index.mjs"
529
695
  );
696
+ if (import_fs4.default.existsSync(extFile)) {
697
+ extensions.push(extFile);
698
+ }
530
699
  }
700
+ return extensions;
531
701
  }
532
- function isPageFile(file) {
533
- const basename = import_path5.default.basename(file, import_path5.default.extname(file));
534
- return basename === "page";
702
+ async function loadMedusaConfig(medusaConfigPath, root) {
703
+ try {
704
+ const mod = await getFileExports(medusaConfigPath);
705
+ const medusaConfig = mod.default ?? mod;
706
+ const modules = medusaConfig?.modules ?? {};
707
+ const configDir = import_path5.default.dirname(medusaConfigPath);
708
+ let base;
709
+ let appType = "admin";
710
+ for (const key of UI_MODULE_KEYS) {
711
+ const value = modules[key];
712
+ if (!value || typeof value !== "object" || !value.options?.appDir)
713
+ continue;
714
+ const appDir = import_path5.default.resolve(configDir, value.options.appDir);
715
+ if (appDir === root) {
716
+ base = value.options.path;
717
+ appType = key === "vendor_ui" ? "vendor" : "admin";
718
+ break;
719
+ }
720
+ }
721
+ const plugins = medusaConfig?.plugins?.filter(
722
+ (plugin) => plugin.resolve !== "@medusajs/draft-order"
723
+ ) ?? [];
724
+ const pluginExtensions = resolvePluginExtensions(plugins, configDir, appType);
725
+ return { base, pluginExtensions };
726
+ } catch {
727
+ return { pluginExtensions: [] };
728
+ }
535
729
  }
536
- function dashboardPlugin() {
730
+ function mercurDashboardPlugin(pluginConfig) {
537
731
  let root;
538
732
  let config;
539
733
  return {
540
734
  name: "@mercurjs/dashboard-sdk",
541
735
  async config(viteConfig) {
542
736
  root = viteConfig.root || process.cwd();
543
- config = await loadMercurConfig(root);
737
+ const medusaConfigPath = import_path5.default.resolve(
738
+ root,
739
+ pluginConfig.medusaConfigPath
740
+ );
741
+ const { base, pluginExtensions } = await loadMedusaConfig(
742
+ medusaConfigPath,
743
+ root
744
+ );
745
+ const srcDir = import_path5.default.join(root, "src");
746
+ const backendUrl = pluginConfig.backendUrl ?? "http://localhost:9000";
747
+ config = {
748
+ ...pluginConfig,
749
+ backendUrl,
750
+ base,
751
+ root,
752
+ srcDir,
753
+ pluginExtensions
754
+ };
544
755
  return {
756
+ base: config.base,
545
757
  define: {
546
- "__BACKEND_URL__": JSON.stringify(config.backendUrl)
758
+ __BACKEND_URL__: JSON.stringify(config.backendUrl),
759
+ __BASE__: JSON.stringify(config.base || "/")
547
760
  },
548
761
  optimizeDeps: {
549
- exclude: ["@mercurjs/vendor", "virtual:mercur/config"]
762
+ exclude: [
763
+ "virtual:mercur/config",
764
+ "virtual:mercur/routes",
765
+ "virtual:mercur/components",
766
+ "virtual:mercur/menu-items",
767
+ "virtual:mercur/i18n"
768
+ ],
769
+ include: [
770
+ "react",
771
+ "react/jsx-runtime",
772
+ "react-dom/client",
773
+ "react-router-dom",
774
+ "react-i18next",
775
+ "@medusajs/ui",
776
+ "@medusajs/dashboard",
777
+ "@medusajs/js-sdk",
778
+ "@tanstack/react-query"
779
+ ]
550
780
  }
551
781
  };
552
782
  },
@@ -563,24 +793,19 @@ function dashboardPlugin() {
563
793
  return loadVirtualModule({ cwd: root, id, mercurConfig: config });
564
794
  },
565
795
  configureServer(server) {
566
- const handlePageChange = (file) => {
567
- if (!isPageFile(file)) return;
796
+ const handleRouteChange = (file) => {
797
+ if (!isRouteFile(file)) return;
568
798
  const mod = server.moduleGraph.getModuleById(RESOLVED_ROUTES_MODULE);
569
799
  if (mod) {
570
800
  server.moduleGraph.invalidateModule(mod);
571
801
  server.ws.send({ type: "full-reload" });
572
802
  }
573
803
  };
574
- server.watcher.on("add", handlePageChange);
575
- server.watcher.on("unlink", handlePageChange);
804
+ server.watcher.on("add", handleRouteChange);
805
+ server.watcher.on("unlink", handleRouteChange);
576
806
  },
577
807
  handleHotUpdate({ file, server }) {
578
- const configPath = import_path5.default.resolve(root, CONFIG_NAME);
579
- if (file === configPath) {
580
- server.restart();
581
- return;
582
- }
583
- if (isPageFile(file)) {
808
+ if (isRouteFile(file)) {
584
809
  const mod = server.moduleGraph.getModuleById(RESOLVED_ROUTES_MODULE);
585
810
  if (mod) {
586
811
  server.moduleGraph.invalidateModule(mod);
@@ -590,12 +815,71 @@ function dashboardPlugin() {
590
815
  };
591
816
  }
592
817
 
593
- // src/define-config.ts
594
- function defineConfig(config) {
595
- return config;
818
+ // src/generate-plugin-entry.ts
819
+ var import_path6 = __toESM(require("path"), 1);
820
+ var import_fs5 = __toESM(require("fs"), 1);
821
+ function findI18nIndex2(srcDir) {
822
+ const i18nDir = import_path6.default.join(srcDir, "i18n");
823
+ if (!import_fs5.default.existsSync(i18nDir)) return null;
824
+ for (const ext of VALID_FILE_EXTENSIONS) {
825
+ const filePath = import_path6.default.join(i18nDir, `index${ext}`);
826
+ if (import_fs5.default.existsSync(filePath)) return filePath;
827
+ }
828
+ return null;
829
+ }
830
+ function generatePluginEntryModule(srcDir) {
831
+ const routesDir = import_path6.default.join(srcDir, "routes");
832
+ const files = crawlRoutes(routesDir);
833
+ let index = 0;
834
+ const routeResults = [];
835
+ const menuItemResults = [];
836
+ for (const file of files) {
837
+ const route = parseFile(file, routesDir, index);
838
+ if (route) {
839
+ routeResults.push(route);
840
+ }
841
+ const menuItem = parseMenuItemFile(file, routesDir, index);
842
+ if (menuItem) {
843
+ menuItemResults.push(menuItem);
844
+ }
845
+ index++;
846
+ }
847
+ const routeTree = buildRouteTree(routeResults.filter(Boolean));
848
+ const routeImports = routeTree.flatMap((r) => r.imports);
849
+ const routes = routeTree.map((r) => formatRoute(r.route));
850
+ const menuItemImports = menuItemResults.filter(Boolean).map((r) => r.import);
851
+ const menuItems = menuItemResults.filter(Boolean).map((r) => formatMenuItem(r.menuItem));
852
+ const i18nFile = findI18nIndex2(srcDir);
853
+ const i18nImport = i18nFile ? `import i18nResources from "${normalizePath(i18nFile)}"` : "";
854
+ const i18nValue = i18nFile ? "i18nResources" : "{}";
855
+ const allImports = [...routeImports, ...menuItemImports];
856
+ if (i18nImport) allImports.push(i18nImport);
857
+ return `// Auto-generated plugin extensions entry
858
+ ${allImports.join("\n")}
859
+
860
+ const routeModule = {
861
+ routes: [
862
+ ${routes.join(",\n")}
863
+ ]
864
+ }
865
+
866
+ const menuItemModule = {
867
+ menuItems: [
868
+ ${menuItems.join(",\n")}
869
+ ]
870
+ }
871
+
872
+ const plugin = {
873
+ routeModule,
874
+ menuItemModule,
875
+ i18nModule: ${i18nValue},
876
+ }
877
+
878
+ export default plugin
879
+ `;
596
880
  }
597
881
  // Annotate the CommonJS export names for ESM import in node:
598
882
  0 && (module.exports = {
599
- dashboardPlugin,
600
- defineConfig
883
+ generatePluginEntryModule,
884
+ mercurDashboardPlugin
601
885
  });
package/dist/index.d.cts CHANGED
@@ -1,9 +1,9 @@
1
1
  import * as Vite from 'vite';
2
2
  import { ComponentType } from 'react';
3
3
 
4
- declare function dashboardPlugin(): Vite.Plugin;
5
-
6
4
  interface MercurConfig {
5
+ medusaConfigPath: string;
6
+ backendUrl?: string;
7
7
  name?: string;
8
8
  logo?: string;
9
9
  components?: {
@@ -14,13 +14,14 @@ interface MercurConfig {
14
14
  i18n?: {
15
15
  defaultLanguage: string;
16
16
  };
17
- backendUrl?: string;
17
+ enableSellerRegistration?: boolean;
18
18
  }
19
19
  interface BuiltMercurConfig extends MercurConfig {
20
20
  backendUrl: string;
21
+ base?: string;
21
22
  root: string;
22
23
  srcDir: string;
23
- configPath: string;
24
+ pluginExtensions: string[];
24
25
  }
25
26
  type RouteConfig = {
26
27
  label: string;
@@ -31,6 +32,13 @@ type RouteConfig = {
31
32
  public?: boolean;
32
33
  };
33
34
 
34
- declare function defineConfig(config: MercurConfig): MercurConfig;
35
+ declare function mercurDashboardPlugin(pluginConfig: MercurConfig): Vite.Plugin;
36
+
37
+ /**
38
+ * Generates a plugin entry module for a given source directory (e.g. src/vendor).
39
+ * Scans routes, menu items, and i18n — outputs a single module string
40
+ * that exports default `{ routeModule, menuItemModule, i18nModule }`.
41
+ */
42
+ declare function generatePluginEntryModule(srcDir: string): string;
35
43
 
36
- export { type BuiltMercurConfig, type MercurConfig, type RouteConfig, dashboardPlugin, defineConfig };
44
+ export { type BuiltMercurConfig, type MercurConfig, type RouteConfig, generatePluginEntryModule, mercurDashboardPlugin };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mercurjs/dashboard-sdk",
3
- "version": "2.0.0-canary.9",
3
+ "version": "2.0.0-canary.90",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "https://github.com/mercurjs/mercur",
@@ -25,6 +25,9 @@
25
25
  "lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0"
26
26
  },
27
27
  "dependencies": {
28
+ "@babel/parser": "7.25.6",
29
+ "@babel/traverse": "7.25.6",
30
+ "@babel/types": "7.25.6",
28
31
  "esbuild-register": "^3.6.0"
29
32
  },
30
33
  "devDependencies": {