@mercurjs/dashboard-sdk 2.0.0-canary.1 → 2.0.0-canary.100

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,23 +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 getNamedExports(filePath) {
140
- try {
141
- const content = import_fs.default.readFileSync(filePath, "utf-8");
142
- const hasHandle = /export\s+(const|function|async\s+function)\s+handle\b/.test(content) || /export\s*\{[^}]*\bhandle\b[^}]*\}/.test(content);
143
- const hasLoader = /export\s+(const|function|async\s+function)\s+loader\b/.test(content) || /export\s*\{[^}]*\bloader\b[^}]*\}/.test(content);
144
- return { hasHandle, hasLoader };
145
- } catch {
146
- return { hasHandle: false, hasLoader: false };
147
- }
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 };
148
219
  }
149
220
  function generateRouteComponentName(index) {
150
221
  return `RouteComponent${index}`;
@@ -170,12 +241,13 @@ function generateImports(file, index, hasHandle, hasLoader) {
170
241
  }
171
242
  return imports;
172
243
  }
173
- function generateRouteObject(routePath, index, hasHandle, hasLoader) {
244
+ function generateRouteObject(routePath, index, hasHandle, hasLoader, isPublic) {
174
245
  return {
175
246
  Component: generateRouteComponentName(index),
176
247
  path: routePath,
177
248
  handle: hasHandle ? generateHandleName(index) : void 0,
178
- loader: hasLoader ? generateLoaderName(index) : void 0
249
+ loader: hasLoader ? generateLoaderName(index) : void 0,
250
+ isPublic
179
251
  };
180
252
  }
181
253
  function formatRoute(route, indent = " ") {
@@ -191,6 +263,10 @@ ${indent} handle: ${route.handle}`;
191
263
  if (route.loader) {
192
264
  result += `,
193
265
  ${indent} loader: ${route.loader}`;
266
+ }
267
+ if (route.isPublic) {
268
+ result += `,
269
+ ${indent} isPublic: true`;
194
270
  }
195
271
  if (route.children?.length) {
196
272
  result += `,
@@ -204,18 +280,25 @@ ${indent} ]`;
204
280
  ${indent}}`;
205
281
  return result;
206
282
  }
207
- function parseFile(file, pagesDir, index) {
208
- 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 {
209
300
  return null;
210
301
  }
211
- const { hasHandle, hasLoader } = getNamedExports(file);
212
- const routePath = getRoute(file, pagesDir);
213
- const imports = generateImports(file, index, hasHandle, hasLoader);
214
- const route = generateRouteObject(routePath, index, hasHandle, hasLoader);
215
- return {
216
- imports,
217
- route
218
- };
219
302
  }
220
303
  function buildRouteTree(results) {
221
304
  const routeMap = /* @__PURE__ */ new Map();
@@ -229,7 +312,7 @@ function buildRouteTree(results) {
229
312
  const parentPath = routePath.split("/@")[0];
230
313
  const parent = routeMap.get(parentPath);
231
314
  if (parent) {
232
- parent.route.children = parent.route.children || [];
315
+ parent.route.children = parent.route.children ?? [];
233
316
  parent.route.children.push({
234
317
  ...result.route,
235
318
  path: result.route.path.replace("/@", "/")
@@ -244,38 +327,44 @@ function buildRouteTree(results) {
244
327
  }
245
328
  return Array.from(routeMap.values());
246
329
  }
247
- function generateRoutes({ srcDir }) {
248
- const pagesDir = import_path.default.join(srcDir, "pages");
249
- const files = crawlPages(pagesDir);
250
- if (files.length === 0) {
251
- return `export const customRoutes = []`;
252
- }
330
+ function generateRoutes({ srcDir, pluginExtensions }) {
331
+ const routesDir = import_path.default.join(srcDir, "routes");
253
332
  let index = 0;
254
333
  const results = [];
255
- for (const file of files) {
256
- const result = parseFile(file, pagesDir, index);
334
+ for (const file of crawlRoutes(routesDir)) {
335
+ const result = parseFile(file, routesDir, index);
257
336
  if (result) {
258
337
  results.push(result);
259
338
  index++;
260
339
  }
261
340
  }
262
- 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) {
263
353
  return `export const customRoutes = []`;
264
354
  }
265
- const routeTree = buildRouteTree(results);
266
- const imports = routeTree.flatMap((result) => result.imports);
267
- const routes = routeTree.map((result) => formatRoute(result.route));
268
- return `${imports.join("\n")}
355
+ return `${allImports.join("\n")}
356
+
357
+ ${pluginDeclarations.join("\n")}
269
358
 
270
359
  export const customRoutes = [
271
- ${routes.join(",\n")}
360
+ ${allRoutes.join(",\n")}
272
361
  ]`;
273
362
  }
274
363
 
275
364
  // src/menu-items.ts
276
365
  var import_fs2 = __toESM(require("fs"), 1);
277
366
  var import_path2 = __toESM(require("path"), 1);
278
- function crawlPages2(dir, pattern = "page") {
367
+ function crawlRoutes2(dir, pattern = "page") {
279
368
  const files = [];
280
369
  if (!import_fs2.default.existsSync(dir)) {
281
370
  return files;
@@ -284,7 +373,7 @@ function crawlPages2(dir, pattern = "page") {
284
373
  for (const entry of entries) {
285
374
  const fullPath = import_path2.default.join(dir, entry.name);
286
375
  if (entry.isDirectory()) {
287
- files.push(...crawlPages2(fullPath, pattern));
376
+ files.push(...crawlRoutes2(fullPath, pattern));
288
377
  } else if (entry.isFile()) {
289
378
  const ext = import_path2.default.extname(entry.name);
290
379
  const baseName = import_path2.default.basename(entry.name, ext);
@@ -295,39 +384,100 @@ function crawlPages2(dir, pattern = "page") {
295
384
  }
296
385
  return files;
297
386
  }
298
- function getRoute2(file, pagesDir) {
387
+ function getRoute2(file, routesDir) {
299
388
  const importPath = normalizePath(file);
300
- const normalizedPagesDir = normalizePath(pagesDir);
301
- 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(
302
391
  new RegExp(
303
392
  `/page\\.(${VALID_FILE_EXTENSIONS.map((ext) => ext.slice(1)).join("|")})$`
304
393
  ),
305
394
  ""
306
395
  ) || "/";
307
396
  }
308
- function hasConfigExport(filePath) {
309
- try {
310
- const content = import_fs2.default.readFileSync(filePath, "utf-8");
311
- return /export\s+(const|let|var)\s+config\b/.test(content) || /export\s*\{[^}]*\bconfig\b[^}]*\}/.test(content);
312
- } catch {
313
- 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;
314
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;
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 };
315
457
  }
316
- function getConfigProperties(filePath) {
458
+ function getRouteConfig(file) {
317
459
  try {
318
- const content = import_fs2.default.readFileSync(filePath, "utf-8");
319
- const hasLabel = /\blabel\s*[:=]/.test(content);
320
- if (!hasLabel) {
321
- return null;
322
- }
323
- const hasIcon = /\bicon\s*[:=]/.test(content);
324
- const rankMatch = content.match(/\brank\s*:\s*(\d+)/);
325
- const rank = rankMatch ? parseInt(rankMatch[1], 10) : void 0;
326
- const nestedMatch = content.match(/\bnested\s*:\s*["']([^"']+)["']/);
327
- const nested = nestedMatch ? nestedMatch[1] : void 0;
328
- const translationNsMatch = content.match(/\btranslationNs\s*:\s*["']([^"']+)["']/);
329
- const translationNs = translationNsMatch ? translationNsMatch[1] : void 0;
330
- 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;
331
481
  } catch {
332
482
  return null;
333
483
  }
@@ -339,12 +489,12 @@ function generateImport(file, index) {
339
489
  const importPath = normalizePath(file);
340
490
  return `import { config as ${generateRouteConfigName(index)} } from "${importPath}"`;
341
491
  }
342
- function generateMenuItem(config, file, pagesDir, index) {
492
+ function generateMenuItem(config, file, routesDir, index) {
343
493
  const configName = generateRouteConfigName(index);
344
494
  return {
345
495
  label: `${configName}.label`,
346
496
  icon: config.icon ? `${configName}.icon` : void 0,
347
- path: getRoute2(file, pagesDir),
497
+ path: getRoute2(file, routesDir),
348
498
  rank: config.rank,
349
499
  nested: config.nested,
350
500
  translationNs: config.translationNs ? `${configName}.translationNs` : void 0
@@ -363,44 +513,47 @@ function formatMenuItem(menuItem) {
363
513
  ${parts.join(",\n")}
364
514
  }`;
365
515
  }
366
- function parseFile2(file, pagesDir, index) {
367
- if (!hasConfigExport(file)) {
368
- return null;
369
- }
370
- const config = getConfigProperties(file);
516
+ function parseMenuItemFile(file, routesDir, index) {
517
+ const config = getRouteConfig(file);
371
518
  if (!config) {
372
519
  return null;
373
520
  }
374
521
  return {
375
522
  import: generateImport(file, index),
376
- menuItem: generateMenuItem(config, file, pagesDir, index)
523
+ menuItem: generateMenuItem(config, file, routesDir, index)
377
524
  };
378
525
  }
379
- function generateMenuItems({ srcDir }) {
380
- const pagesDir = import_path2.default.join(srcDir, "pages");
381
- const files = crawlPages2(pagesDir);
382
- if (files.length === 0) {
383
- return `export default { menuItems: [] }`;
384
- }
526
+ function generateMenuItems({ srcDir, pluginExtensions }) {
527
+ const routesDir = import_path2.default.join(srcDir, "routes");
385
528
  let index = 0;
386
529
  const results = [];
387
- for (const file of files) {
388
- const result = parseFile2(file, pagesDir, index);
530
+ for (const file of crawlRoutes2(routesDir)) {
531
+ const result = parseMenuItemFile(file, routesDir, index);
389
532
  if (result) {
390
533
  results.push(result);
391
534
  index++;
392
535
  }
393
536
  }
394
- 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) {
395
548
  return `export default { menuItems: [] }`;
396
549
  }
397
- const imports = results.map((r) => r.import);
398
- const menuItems = results.map((r) => formatMenuItem(r.menuItem));
399
- return `${imports.join("\n")}
550
+ return `${allImports.join("\n")}
551
+
552
+ ${pluginDeclarations.join("\n")}
400
553
 
401
554
  export default {
402
555
  menuItems: [
403
- ${menuItems.join(",\n")}
556
+ ${allMenuItems.join(",\n")}
404
557
  ]
405
558
  }`;
406
559
  }
@@ -492,44 +645,138 @@ function loadI18nModule(mercurConfig) {
492
645
  }
493
646
 
494
647
  // src/plugin.ts
495
- function buildConfig(config, root) {
496
- const srcDir = import_path5.default.join(root, "src");
497
- return {
498
- ...config,
499
- backendUrl: config.backendUrl ?? "http://localhost:9000",
500
- root,
501
- srcDir,
502
- configPath: import_path5.default.resolve(root, CONFIG_NAME)
503
- };
648
+ function isRouteFile(file) {
649
+ const basename = import_path5.default.basename(file, import_path5.default.extname(file));
650
+ return basename === "page";
651
+ }
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");
504
663
  }
505
- async function loadMercurConfig(root) {
506
- const configPath = import_path5.default.resolve(root, CONFIG_NAME);
664
+ function resolvePluginRoot(resolve, configDir, nodeModulesRoot) {
507
665
  try {
508
- const mod = await getFileExports(configPath);
509
- const content = mod.default ?? mod;
510
- return buildConfig(content, root);
511
- } catch (error) {
512
- console.error(error);
513
- throw new Error(
514
- `[@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"
515
695
  );
696
+ if (import_fs4.default.existsSync(extFile)) {
697
+ extensions.push(extFile);
698
+ }
516
699
  }
700
+ return extensions;
517
701
  }
518
- function isPageFile(file) {
519
- const basename = import_path5.default.basename(file, import_path5.default.extname(file));
520
- 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
+ }
521
729
  }
522
- function dashboardPlugin() {
730
+ function mercurDashboardPlugin(pluginConfig) {
523
731
  let root;
524
732
  let config;
525
733
  return {
526
734
  name: "@mercurjs/dashboard-sdk",
527
735
  async config(viteConfig) {
528
736
  root = viteConfig.root || process.cwd();
529
- 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
+ };
530
755
  return {
756
+ base: config.base,
531
757
  define: {
532
- "__BACKEND_URL__": JSON.stringify(config.backendUrl)
758
+ __BACKEND_URL__: JSON.stringify(config.backendUrl),
759
+ __BASE__: JSON.stringify(config.base || "/")
760
+ },
761
+ optimizeDeps: {
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
+ ]
533
780
  }
534
781
  };
535
782
  },
@@ -546,24 +793,19 @@ function dashboardPlugin() {
546
793
  return loadVirtualModule({ cwd: root, id, mercurConfig: config });
547
794
  },
548
795
  configureServer(server) {
549
- const handlePageChange = (file) => {
550
- if (!isPageFile(file)) return;
796
+ const handleRouteChange = (file) => {
797
+ if (!isRouteFile(file)) return;
551
798
  const mod = server.moduleGraph.getModuleById(RESOLVED_ROUTES_MODULE);
552
799
  if (mod) {
553
800
  server.moduleGraph.invalidateModule(mod);
554
801
  server.ws.send({ type: "full-reload" });
555
802
  }
556
803
  };
557
- server.watcher.on("add", handlePageChange);
558
- server.watcher.on("unlink", handlePageChange);
804
+ server.watcher.on("add", handleRouteChange);
805
+ server.watcher.on("unlink", handleRouteChange);
559
806
  },
560
807
  handleHotUpdate({ file, server }) {
561
- const configPath = import_path5.default.resolve(root, CONFIG_NAME);
562
- if (file === configPath) {
563
- server.restart();
564
- return;
565
- }
566
- if (isPageFile(file)) {
808
+ if (isRouteFile(file)) {
567
809
  const mod = server.moduleGraph.getModuleById(RESOLVED_ROUTES_MODULE);
568
810
  if (mod) {
569
811
  server.moduleGraph.invalidateModule(mod);
@@ -573,12 +815,71 @@ function dashboardPlugin() {
573
815
  };
574
816
  }
575
817
 
576
- // src/define-config.ts
577
- function defineConfig(config) {
578
- 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
+ `;
579
880
  }
580
881
  // Annotate the CommonJS export names for ESM import in node:
581
882
  0 && (module.exports = {
582
- dashboardPlugin,
583
- defineConfig
883
+ generatePluginEntryModule,
884
+ mercurDashboardPlugin
584
885
  });
package/dist/index.d.cts CHANGED
@@ -1,25 +1,27 @@
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?: {
10
10
  MainSidebar?: string;
11
11
  SettingsSidebar?: string;
12
+ TopbarActions?: string;
12
13
  };
13
14
  i18n?: {
14
15
  defaultLanguage: string;
15
16
  };
16
- backendUrl?: string;
17
+ enableSellerRegistration?: boolean;
17
18
  }
18
19
  interface BuiltMercurConfig extends MercurConfig {
19
20
  backendUrl: string;
21
+ base?: string;
20
22
  root: string;
21
23
  srcDir: string;
22
- configPath: string;
24
+ pluginExtensions: string[];
23
25
  }
24
26
  type RouteConfig = {
25
27
  label: string;
@@ -27,8 +29,16 @@ type RouteConfig = {
27
29
  rank?: number;
28
30
  nested?: string;
29
31
  translationNs?: string;
32
+ public?: boolean;
30
33
  };
31
34
 
32
- 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;
33
43
 
34
- 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.1",
3
+ "version": "2.0.0-canary.100",
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": {