@mercurjs/dashboard-sdk 2.3.8-canary.2 → 2.3.8-canary.4

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/vite.cjs CHANGED
@@ -36,8 +36,8 @@ __export(vite_exports, {
36
36
  module.exports = __toCommonJS(vite_exports);
37
37
 
38
38
  // src/plugin.ts
39
- var import_path8 = __toESM(require("path"), 1);
40
- var import_fs7 = __toESM(require("fs"), 1);
39
+ var import_path5 = __toESM(require("path"), 1);
40
+ var import_fs6 = __toESM(require("fs"), 1);
41
41
  var import_module = require("module");
42
42
 
43
43
  // src/utils.ts
@@ -64,6 +64,8 @@ var I18N_VIRTUAL_MODULE = "virtual:mercur/i18n";
64
64
  var WIDGETS_VIRTUAL_MODULE = "virtual:mercur/widgets";
65
65
  var NAVIGATION_VIRTUAL_MODULE = "virtual:mercur/navigation";
66
66
  var CUSTOM_FIELDS_VIRTUAL_MODULE = "virtual:mercur/custom-fields";
67
+ var LAYOUTS_VIRTUAL_MODULE = "virtual:mercur/layouts";
68
+ var PREFERENCES_VIRTUAL_MODULE = "virtual:mercur/preferences";
67
69
  var RESOLVED_CONFIG_MODULE = "\0" + CONFIG_VIRTUAL_MODULE;
68
70
  var RESOLVED_ROUTES_MODULE = "\0" + ROUTES_VIRTUAL_MODULE;
69
71
  var RESOLVED_MENU_ITEMS_MODULE = "\0" + MENU_ITEMS_VIRTUAL_MODULE;
@@ -71,6 +73,8 @@ var RESOLVED_I18N_MODULE = "\0" + I18N_VIRTUAL_MODULE;
71
73
  var RESOLVED_WIDGETS_MODULE = "\0" + WIDGETS_VIRTUAL_MODULE;
72
74
  var RESOLVED_NAVIGATION_MODULE = "\0" + NAVIGATION_VIRTUAL_MODULE;
73
75
  var RESOLVED_CUSTOM_FIELDS_MODULE = "\0" + CUSTOM_FIELDS_VIRTUAL_MODULE;
76
+ var RESOLVED_LAYOUTS_MODULE = "\0" + LAYOUTS_VIRTUAL_MODULE;
77
+ var RESOLVED_PREFERENCES_MODULE = "\0" + PREFERENCES_VIRTUAL_MODULE;
74
78
  var VIRTUAL_MODULES = [
75
79
  CONFIG_VIRTUAL_MODULE,
76
80
  ROUTES_VIRTUAL_MODULE,
@@ -78,8 +82,12 @@ var VIRTUAL_MODULES = [
78
82
  I18N_VIRTUAL_MODULE,
79
83
  WIDGETS_VIRTUAL_MODULE,
80
84
  NAVIGATION_VIRTUAL_MODULE,
81
- CUSTOM_FIELDS_VIRTUAL_MODULE
85
+ CUSTOM_FIELDS_VIRTUAL_MODULE,
86
+ LAYOUTS_VIRTUAL_MODULE,
87
+ PREFERENCES_VIRTUAL_MODULE
82
88
  ];
89
+ var LAYOUT_PREFERENCES_FILE = "_layouts";
90
+ var NAVIGATION_FILE = "_navigation";
83
91
 
84
92
  // src/utils.ts
85
93
  function normalizePath(filePath) {
@@ -118,9 +126,9 @@ function resolveExports(moduleExports) {
118
126
  }
119
127
  return moduleExports;
120
128
  }
121
- async function getFileExports(path10) {
129
+ async function getFileExports(path6) {
122
130
  const { unregister } = await safeRegister();
123
- const module2 = require(path10);
131
+ const module2 = require(path6);
124
132
  unregister();
125
133
  return resolveExports(module2);
126
134
  }
@@ -146,13 +154,13 @@ function hasDefaultExport(ast) {
146
154
  ExportDefaultDeclaration() {
147
155
  found = true;
148
156
  },
149
- AssignmentExpression(path10) {
150
- if (path10.node.left.type === "MemberExpression" && path10.node.left.object.type === "Identifier" && path10.node.left.object.name === "exports" && path10.node.left.property.type === "Identifier" && path10.node.left.property.name === "default") {
157
+ AssignmentExpression(path6) {
158
+ if (path6.node.left.type === "MemberExpression" && path6.node.left.object.type === "Identifier" && path6.node.left.object.name === "exports" && path6.node.left.property.type === "Identifier" && path6.node.left.property.name === "default") {
151
159
  found = true;
152
160
  }
153
161
  },
154
- ExportNamedDeclaration(path10) {
155
- const specifiers = path10.node.specifiers;
162
+ ExportNamedDeclaration(path6) {
163
+ const specifiers = path6.node.specifiers;
156
164
  if (specifiers?.some(
157
165
  (s) => s.type === "ExportSpecifier" && s.exported.type === "Identifier" && s.exported.name === "default"
158
166
  )) {
@@ -164,252 +172,16 @@ function hasDefaultExport(ast) {
164
172
  }
165
173
 
166
174
  // src/routes.ts
167
- var import_fs2 = __toESM(require("fs"), 1);
168
- var import_path2 = __toESM(require("path"), 1);
169
- function getRoute(file, routesDir) {
170
- const importPath = normalizePath(file);
171
- const normalizedRoutesDir = normalizePath(routesDir);
172
- return importPath.replace(normalizedRoutesDir, "").replace(/\[\[\*\]\]/g, "*?").replace(/\[\*\]/g, "*").replace(/\(([^[\])]+)\)/g, "$1?").replace(/\[\[([^\]]+)\]\]/g, ":$1?").replace(/\[([^\]]+)\]/g, ":$1").replace(
173
- new RegExp(
174
- `/page\\.(${VALID_FILE_EXTENSIONS.map((ext) => ext.slice(1)).join("|")})$`
175
- ),
176
- ""
177
- ) || "/";
178
- }
179
- function crawlRoutes(dir, pattern = "page") {
180
- const files = [];
181
- if (!import_fs2.default.existsSync(dir)) {
182
- return files;
183
- }
184
- const entries = import_fs2.default.readdirSync(dir, { withFileTypes: true });
185
- for (const entry of entries) {
186
- const fullPath = import_path2.default.join(dir, entry.name);
187
- if (entry.isDirectory()) {
188
- files.push(...crawlRoutes(fullPath, pattern));
189
- } else if (entry.isFile()) {
190
- const ext = import_path2.default.extname(entry.name);
191
- const baseName = import_path2.default.basename(entry.name, ext);
192
- if (baseName === pattern && VALID_FILE_EXTENSIONS.includes(ext)) {
193
- files.push(fullPath);
194
- }
195
- }
196
- }
197
- return files;
198
- }
199
- function hasConfigPublic(ast) {
200
- let found = false;
201
- traverse(ast, {
202
- ExportNamedDeclaration(path10) {
203
- const declaration = path10.node.declaration;
204
- if (!(0, import_types.isVariableDeclaration)(declaration)) return;
205
- for (const decl of declaration.declarations) {
206
- if ((0, import_types.isVariableDeclarator)(decl) && (0, import_types.isIdentifier)(decl.id, { name: "config" }) && decl.init?.type === "ObjectExpression") {
207
- const publicProp = decl.init.properties.find(
208
- (prop) => (0, import_types.isObjectProperty)(prop) && (0, import_types.isIdentifier)(prop.key, { name: "public" }) && (0, import_types.isBooleanLiteral)(prop.value, { value: true })
209
- );
210
- if (publicProp) {
211
- found = true;
212
- }
213
- }
214
- }
215
- }
216
- });
217
- return found;
218
- }
219
- function getNamedExports(ast) {
220
- let hasHandle = false;
221
- let hasLoader = false;
222
- traverse(ast, {
223
- ExportNamedDeclaration(path10) {
224
- const declaration = path10.node.declaration;
225
- if (declaration?.type === "VariableDeclaration") {
226
- declaration.declarations.forEach((decl) => {
227
- if (decl.id.type === "Identifier" && decl.id.name === "handle") {
228
- hasHandle = true;
229
- }
230
- if (decl.id.type === "Identifier" && decl.id.name === "loader") {
231
- hasLoader = true;
232
- }
233
- });
234
- }
235
- if (declaration?.type === "FunctionDeclaration" && declaration.id?.name === "loader") {
236
- hasLoader = true;
237
- }
238
- if (declaration?.type === "FunctionDeclaration" && declaration.id?.name === "handle") {
239
- hasHandle = true;
240
- }
241
- }
242
- });
243
- return { hasHandle, hasLoader };
244
- }
245
- function generateRouteComponentName(index) {
246
- return `RouteComponent${index}`;
247
- }
248
- function generateHandleName(index) {
249
- return `RouteHandle${index}`;
250
- }
251
- function generateLoaderName(index) {
252
- return `RouteLoader${index}`;
253
- }
254
- function generateImports(file, index, hasHandle, hasLoader) {
255
- const imports = [];
256
- const componentName = generateRouteComponentName(index);
257
- const importPath = normalizePath(file);
258
- if (!hasHandle && !hasLoader) {
259
- imports.push(`import ${componentName} from "${importPath}"`);
260
- } else {
261
- const namedImports = [
262
- hasHandle && `handle as ${generateHandleName(index)}`,
263
- hasLoader && `loader as ${generateLoaderName(index)}`
264
- ].filter(Boolean).join(", ");
265
- imports.push(`import ${componentName}, { ${namedImports} } from "${importPath}"`);
266
- }
267
- return imports;
268
- }
269
- function generateRouteObject(routePath, index, hasHandle, hasLoader, isPublic) {
270
- return {
271
- Component: generateRouteComponentName(index),
272
- path: routePath,
273
- handle: hasHandle ? generateHandleName(index) : void 0,
274
- loader: hasLoader ? generateLoaderName(index) : void 0,
275
- isPublic
276
- };
277
- }
278
- function formatRoute(route, indent = " ") {
279
- let result = `${indent}{
280
- `;
281
- result += `${indent} Component: ${route.Component},
282
- `;
283
- result += `${indent} path: "${route.path}"`;
284
- if (route.handle) {
285
- result += `,
286
- ${indent} handle: ${route.handle}`;
287
- }
288
- if (route.loader) {
289
- result += `,
290
- ${indent} loader: ${route.loader}`;
291
- }
292
- if (route.isPublic) {
293
- result += `,
294
- ${indent} isPublic: true`;
295
- }
296
- if (route.children?.length) {
297
- result += `,
298
- ${indent} children: [
299
- `;
300
- result += route.children.map((child) => formatRoute(child, indent + " ")).join(",\n");
301
- result += `
302
- ${indent} ]`;
303
- }
304
- result += `
305
- ${indent}}`;
306
- return result;
307
- }
308
- function parseFile(file, routesDir, index) {
309
- try {
310
- const code = import_fs2.default.readFileSync(file, "utf-8");
311
- const ast = (0, import_parser.parse)(code, getParserOptions(file));
312
- if (!hasDefaultExport(ast)) {
313
- return null;
314
- }
315
- const { hasHandle, hasLoader } = getNamedExports(ast);
316
- const isPublic = hasConfigPublic(ast);
317
- const routePath = getRoute(file, routesDir);
318
- const imports = generateImports(file, index, hasHandle, hasLoader);
319
- const route = generateRouteObject(routePath, index, hasHandle, hasLoader, isPublic);
320
- return {
321
- imports,
322
- route
323
- };
324
- } catch {
325
- return null;
326
- }
327
- }
328
- function buildRouteTree(results) {
329
- const routeMap = /* @__PURE__ */ new Map();
330
- const sortedResults = [...results].sort(
331
- (a, b) => a.route.path.split("/").length - b.route.path.split("/").length
332
- );
333
- for (const result of sortedResults) {
334
- const routePath = result.route.path;
335
- const isParallel = routePath.includes("/@");
336
- if (isParallel) {
337
- const parentPath = routePath.split("/@")[0];
338
- const parent = routeMap.get(parentPath);
339
- if (parent) {
340
- parent.route.children = parent.route.children ?? [];
341
- parent.route.children.push({
342
- ...result.route,
343
- path: result.route.path.replace("/@", "/")
344
- });
345
- parent.imports.push(...result.imports);
346
- } else {
347
- routeMap.set(routePath, result);
348
- }
349
- } else {
350
- routeMap.set(routePath, result);
351
- }
352
- }
353
- return Array.from(routeMap.values());
354
- }
355
- function generateRoutes({ srcDir, pluginExtensions }) {
356
- const routesDir = import_path2.default.join(srcDir, "routes");
357
- let index = 0;
358
- const results = [];
359
- for (const file of crawlRoutes(routesDir)) {
360
- const result = parseFile(file, routesDir, index);
361
- if (result) {
362
- results.push(result);
363
- index++;
364
- }
365
- }
366
- const pluginDeclarations = pluginExtensions.map(
367
- (ext, i) => `import __plugin${i} from "${normalizePath(ext)}"`
368
- );
369
- const pluginSpreads = pluginExtensions.map(
370
- (_, i) => ` ...(__plugin${i}.routeModule?.routes ?? [])`
371
- );
372
- const routeTree = buildRouteTree(results);
373
- const appImports = routeTree.flatMap((r) => r.imports);
374
- const appRoutes = routeTree.map((r) => formatRoute(r.route));
375
- const allImports = [...appImports];
376
- const allRoutes = [...appRoutes, ...pluginSpreads];
377
- if (allImports.length === 0 && pluginDeclarations.length === 0 && allRoutes.length === 0) {
378
- return `export const customRoutes = []`;
379
- }
380
- return `${allImports.join("\n")}
381
-
382
- ${pluginDeclarations.join("\n")}
175
+ var import_fs5 = __toESM(require("fs"), 1);
176
+ var import_path4 = __toESM(require("path"), 1);
383
177
 
384
- export const customRoutes = [
385
- ${allRoutes.join(",\n")}
386
- ]`;
387
- }
178
+ // src/layers.ts
179
+ var import_fs4 = __toESM(require("fs"), 1);
180
+ var import_path3 = __toESM(require("path"), 1);
388
181
 
389
182
  // src/menu-items.ts
390
- var import_fs3 = __toESM(require("fs"), 1);
391
- var import_path3 = __toESM(require("path"), 1);
392
- function crawlRoutes2(dir, pattern = "page") {
393
- const files = [];
394
- if (!import_fs3.default.existsSync(dir)) {
395
- return files;
396
- }
397
- const entries = import_fs3.default.readdirSync(dir, { withFileTypes: true });
398
- for (const entry of entries) {
399
- const fullPath = import_path3.default.join(dir, entry.name);
400
- if (entry.isDirectory()) {
401
- files.push(...crawlRoutes2(fullPath, pattern));
402
- } else if (entry.isFile()) {
403
- const ext = import_path3.default.extname(entry.name);
404
- const baseName = import_path3.default.basename(entry.name, ext);
405
- if (baseName === pattern && VALID_FILE_EXTENSIONS.includes(ext)) {
406
- files.push(fullPath);
407
- }
408
- }
409
- }
410
- return files;
411
- }
412
- function getRoute2(file, routesDir) {
183
+ var import_fs2 = __toESM(require("fs"), 1);
184
+ function getRoute(file, routesDir) {
413
185
  const importPath = normalizePath(file);
414
186
  const normalizedRoutesDir = normalizePath(routesDir);
415
187
  return importPath.replace(normalizedRoutesDir, "").replace(/\[\[\*\]\]/g, "*?").replace(/\[\*\]/g, "*").replace(/\(([^[\])]+)\)/g, "$1?").replace(/\[\[([^\]]+)\]\]/g, ":$1?").replace(/\[([^\]]+)\]/g, ":$1").replace(
@@ -419,9 +191,9 @@ function getRoute2(file, routesDir) {
419
191
  ""
420
192
  ) || "/";
421
193
  }
422
- function getConfigObjectProperties(path10) {
423
- if ((0, import_types.isVariableDeclarator)(path10.node)) {
424
- const decl = (0, import_types.isIdentifier)(path10.node.id, { name: "config" }) ? path10.node : null;
194
+ function getConfigObjectProperties(path6) {
195
+ if ((0, import_types.isVariableDeclarator)(path6.node)) {
196
+ const decl = (0, import_types.isIdentifier)(path6.node.id, { name: "config" }) ? path6.node : null;
425
197
  if (!decl) return null;
426
198
  if ((0, import_types.isCallExpression)(decl.init) && decl.init.arguments.length > 0 && (0, import_types.isObjectExpression)(decl.init.arguments[0])) {
427
199
  return decl.init.arguments[0].properties;
@@ -431,7 +203,7 @@ function getConfigObjectProperties(path10) {
431
203
  }
432
204
  return null;
433
205
  }
434
- const declaration = path10.node.declaration;
206
+ const declaration = path6.node.declaration;
435
207
  if ((0, import_types.isVariableDeclaration)(declaration)) {
436
208
  const configDecl = declaration.declarations.find(
437
209
  (d) => (0, import_types.isVariableDeclarator)(d) && (0, import_types.isIdentifier)(d.id, { name: "config" })
@@ -507,21 +279,21 @@ function processConfigProperties(properties) {
507
279
  }
508
280
  function getRouteConfig(file) {
509
281
  try {
510
- const code = import_fs3.default.readFileSync(file, "utf-8");
282
+ const code = import_fs2.default.readFileSync(file, "utf-8");
511
283
  const ast = (0, import_parser.parse)(code, getParserOptions(file));
512
284
  let config = null;
513
285
  let configFound = false;
514
286
  traverse(ast, {
515
- VariableDeclarator(path10) {
287
+ VariableDeclarator(path6) {
516
288
  if (configFound) return;
517
- const properties = getConfigObjectProperties(path10);
289
+ const properties = getConfigObjectProperties(path6);
518
290
  if (!properties) return;
519
291
  config = processConfigProperties(properties);
520
292
  if (config) configFound = true;
521
293
  },
522
- ExportNamedDeclaration(path10) {
294
+ ExportNamedDeclaration(path6) {
523
295
  if (configFound) return;
524
- const properties = getConfigObjectProperties(path10);
296
+ const properties = getConfigObjectProperties(path6);
525
297
  if (!properties) return;
526
298
  config = processConfigProperties(properties);
527
299
  if (config) configFound = true;
@@ -544,7 +316,7 @@ function generateMenuItem(config, file, routesDir, index) {
544
316
  return {
545
317
  label: `${configName}.label`,
546
318
  icon: config.icon ? `${configName}.icon` : void 0,
547
- path: getRoute2(file, routesDir),
319
+ path: getRoute(file, routesDir),
548
320
  rank: config.rank,
549
321
  nested: config.nested,
550
322
  group: config.group,
@@ -574,75 +346,485 @@ function parseMenuItemFile(file, routesDir, index) {
574
346
  if (!config) {
575
347
  return null;
576
348
  }
577
- return {
578
- import: generateImport(file, index),
579
- menuItem: generateMenuItem(config, file, routesDir, index)
580
- };
349
+ return {
350
+ import: generateImport(file, index),
351
+ menuItem: generateMenuItem(config, file, routesDir, index)
352
+ };
353
+ }
354
+ function generateMenuItems(config) {
355
+ const { header, names } = emitLayers(config);
356
+ return `${header}
357
+
358
+ const byPath = new Map()
359
+ for (const item of [
360
+ ${spreadLayers(names, "menuItemModule", "menuItems")}
361
+ ]) {
362
+ byPath.set(item.path, item)
363
+ }
364
+
365
+ export default { menuItems: [...byPath.values()] }`;
366
+ }
367
+
368
+ // src/widgets.ts
369
+ var import_fs3 = __toESM(require("fs"), 1);
370
+ var import_path2 = __toESM(require("path"), 1);
371
+ function readNumber(value) {
372
+ if ((0, import_types.isNumericLiteral)(value)) return value.value;
373
+ if ((0, import_types.isUnaryExpression)(value) && value.operator === "-" && (0, import_types.isNumericLiteral)(value.argument)) {
374
+ return -value.argument.value;
375
+ }
376
+ return void 0;
377
+ }
378
+ function extractStrings(value, zones) {
379
+ if ((0, import_types.isStringLiteral)(value)) {
380
+ zones.push(value.value);
381
+ } else if ((0, import_types.isArrayExpression)(value)) {
382
+ for (const el of value.elements) {
383
+ if ((0, import_types.isStringLiteral)(el)) zones.push(el.value);
384
+ }
385
+ }
386
+ }
387
+ function readWidgetConfig(properties) {
388
+ const zones = [];
389
+ const permissions = [];
390
+ let id;
391
+ let label;
392
+ let section;
393
+ let order;
394
+ let replace;
395
+ for (const prop of properties) {
396
+ if (!(0, import_types.isObjectProperty)(prop)) continue;
397
+ if ((0, import_types.isIdentifier)(prop.key, { name: "zone" })) {
398
+ extractStrings(prop.value, zones);
399
+ }
400
+ if ((0, import_types.isIdentifier)(prop.key, { name: "permission" })) {
401
+ extractStrings(prop.value, permissions);
402
+ }
403
+ if ((0, import_types.isIdentifier)(prop.key, { name: "id" }) && (0, import_types.isStringLiteral)(prop.value)) {
404
+ id = prop.value.value;
405
+ }
406
+ if ((0, import_types.isIdentifier)(prop.key, { name: "label" }) && (0, import_types.isStringLiteral)(prop.value)) {
407
+ label = prop.value.value;
408
+ }
409
+ if ((0, import_types.isIdentifier)(prop.key, { name: "section" }) && (0, import_types.isStringLiteral)(prop.value)) {
410
+ section = prop.value.value;
411
+ }
412
+ if ((0, import_types.isIdentifier)(prop.key, { name: "order" })) {
413
+ order = readNumber(prop.value);
414
+ }
415
+ if ((0, import_types.isIdentifier)(prop.key, { name: "replace" }) && (0, import_types.isStringLiteral)(prop.value)) {
416
+ replace = prop.value.value;
417
+ }
418
+ }
419
+ if (zones.length === 0) return null;
420
+ return { zones, id, label, permissions, section, order, replace };
421
+ }
422
+ function getConfigProperties(node) {
423
+ if ((0, import_types.isCallExpression)(node) && node.arguments.length > 0 && (0, import_types.isObjectExpression)(node.arguments[0])) {
424
+ return node.arguments[0].properties;
425
+ }
426
+ if ((0, import_types.isObjectExpression)(node)) {
427
+ return node.properties;
428
+ }
429
+ return null;
430
+ }
431
+ function getWidgetConfig(file) {
432
+ try {
433
+ const code = import_fs3.default.readFileSync(file, "utf-8");
434
+ const ast = (0, import_parser.parse)(code, getParserOptions(file));
435
+ if (!hasDefaultExport(ast)) return null;
436
+ let info = null;
437
+ const visit = (declaration) => {
438
+ if (info) return;
439
+ if (!(0, import_types.isVariableDeclaration)(declaration)) return;
440
+ for (const decl of declaration.declarations) {
441
+ if ((0, import_types.isVariableDeclarator)(decl) && (0, import_types.isIdentifier)(decl.id, { name: "config" })) {
442
+ const props = getConfigProperties(decl.init);
443
+ if (props) info = readWidgetConfig(props);
444
+ }
445
+ }
446
+ };
447
+ traverse(ast, {
448
+ ExportNamedDeclaration(p) {
449
+ visit(p.node.declaration);
450
+ },
451
+ VariableDeclaration(p) {
452
+ visit(p.node);
453
+ }
454
+ });
455
+ return info;
456
+ } catch {
457
+ return null;
458
+ }
459
+ }
460
+ function hashPath(relative) {
461
+ let hash = 0;
462
+ for (let i = 0; i < relative.length; i++) {
463
+ hash = (hash << 5) - hash + relative.charCodeAt(i);
464
+ hash |= 0;
465
+ }
466
+ return Math.abs(hash).toString(36).slice(0, 6);
467
+ }
468
+ function widgetId(info, file, widgetsDir) {
469
+ if (info.id) return info.id;
470
+ const relative = normalizePath(import_path2.default.relative(widgetsDir, file));
471
+ return `Widget-${hashPath(relative)}`;
472
+ }
473
+ function parseWidgetFile(file, widgetsDir, index) {
474
+ const info = getWidgetConfig(file);
475
+ if (!info) return null;
476
+ const name = `Widget${index}`;
477
+ const importPath = normalizePath(file);
478
+ const zonesLiteral = JSON.stringify(info.zones);
479
+ const id = widgetId(info, file, widgetsDir);
480
+ return {
481
+ import: `import ${name} from "${importPath}"`,
482
+ entry: ` { Component: ${name}, zone: ${zonesLiteral}, widgetId: ${JSON.stringify(
483
+ id
484
+ )}${info.label ? `, label: ${JSON.stringify(info.label)}` : ""}${info.permissions.length ? `, permission: ${JSON.stringify(info.permissions)}` : ""}${info.section ? `, section: ${JSON.stringify(info.section)}` : ""}${info.order !== void 0 ? `, order: ${info.order}` : ""}${info.replace ? `, replace: ${JSON.stringify(info.replace)}` : ""} }`
485
+ };
486
+ }
487
+ function collectWidgets(srcDir, startIndex = 0) {
488
+ const widgetsDir = import_path2.default.join(srcDir, "widgets");
489
+ let index = startIndex;
490
+ const results = [];
491
+ for (const file of crawlModuleFiles(widgetsDir)) {
492
+ const result = parseWidgetFile(file, widgetsDir, index);
493
+ if (result) {
494
+ results.push(result);
495
+ index++;
496
+ }
497
+ }
498
+ return {
499
+ imports: results.map((r) => r.import),
500
+ entries: results.map((r) => r.entry)
501
+ };
502
+ }
503
+ function generateWidgets(config) {
504
+ const { header, names } = emitLayers(config);
505
+ return `${header}
506
+
507
+ const byId = new Map()
508
+ for (const widget of [
509
+ ${spreadLayers(names, "widgetModule", "widgets")}
510
+ ]) {
511
+ byId.set(widget.widgetId, widget)
512
+ }
513
+
514
+ export default { widgets: [...byId.values()] }`;
515
+ }
516
+
517
+ // src/layers.ts
518
+ function layerSources({
519
+ pluginExtensions,
520
+ projectDir,
521
+ srcDir
522
+ }) {
523
+ return [
524
+ ...pluginExtensions.map((entry) => ({ entry })),
525
+ ...projectDir ? [{ srcDir: projectDir }] : [],
526
+ { srcDir }
527
+ ];
528
+ }
529
+ function findFile(dir, stem) {
530
+ for (const ext of VALID_FILE_EXTENSIONS) {
531
+ const file = import_path3.default.join(dir, `${stem}${ext}`);
532
+ if (import_fs4.default.existsSync(file)) return file;
533
+ }
534
+ return null;
535
+ }
536
+ function collectLayer(srcDir, layer, startIndex = 0) {
537
+ const name = `__layer${layer}`;
538
+ const routesDir = import_path3.default.join(srcDir, "routes");
539
+ let index = startIndex;
540
+ const routeResults = [];
541
+ const menuItemResults = [];
542
+ for (const file of crawlRoutes(routesDir)) {
543
+ const route = parseFile(file, routesDir, index);
544
+ if (route) routeResults.push(route);
545
+ const menuItem = parseMenuItemFile(file, routesDir, index);
546
+ if (menuItem) menuItemResults.push(menuItem);
547
+ index++;
548
+ }
549
+ const routeTree = buildRouteTree(routeResults);
550
+ const routeImports = routeTree.flatMap((r) => r.imports);
551
+ const routes = routeTree.map((r) => formatRoute(r.route));
552
+ const menuItemImports = menuItemResults.map((r) => r.import);
553
+ const menuItems = menuItemResults.map((r) => formatMenuItem(r.menuItem));
554
+ const widgets = collectWidgets(srcDir, index);
555
+ index += widgets.entries.length;
556
+ const cfFiles = crawlModuleFiles(import_path3.default.join(srcDir, "custom-fields"));
557
+ const cfImports = cfFiles.map(
558
+ (file, i) => `import __cf${layer}_${i} from "${normalizePath(file)}"`
559
+ );
560
+ const cfRefs = cfFiles.map((_, i) => ` __cf${layer}_${i}`);
561
+ const layoutFiles = crawlModuleFiles(import_path3.default.join(srcDir, "layouts"));
562
+ const layoutImports = layoutFiles.map(
563
+ (file, i) => `import __layout${layer}_${i} from "${normalizePath(file)}"`
564
+ );
565
+ const layoutRefs = layoutFiles.map((_, i) => ` __layout${layer}_${i}`);
566
+ const i18nFile = findFile(import_path3.default.join(srcDir, "i18n"), "index");
567
+ const i18nName = `__i18n${layer}`;
568
+ const navigationFile = findFile(srcDir, NAVIGATION_FILE);
569
+ const navigationName = `__navigation${layer}`;
570
+ const preferencesFile = findFile(srcDir, LAYOUT_PREFERENCES_FILE);
571
+ const preferencesName = `__preferences${layer}`;
572
+ const imports = [
573
+ ...routeImports,
574
+ ...menuItemImports,
575
+ ...widgets.imports,
576
+ ...cfImports,
577
+ ...layoutImports,
578
+ ...i18nFile ? [`import ${i18nName} from "${normalizePath(i18nFile)}"`] : [],
579
+ ...navigationFile ? [`import ${navigationName} from "${normalizePath(navigationFile)}"`] : [],
580
+ ...preferencesFile ? [`import ${preferencesName} from "${normalizePath(preferencesFile)}"`] : []
581
+ ];
582
+ const declaration = `const ${name} = {
583
+ routeModule: { routes: [
584
+ ${routes.join(",\n")}
585
+ ] },
586
+ menuItemModule: { menuItems: [
587
+ ${menuItems.join(",\n")}
588
+ ] },
589
+ widgetModule: { widgets: [
590
+ ${widgets.entries.join(",\n")}
591
+ ] },
592
+ customFieldsModule: { configs: [
593
+ ${cfRefs.join(",\n")}
594
+ ] },
595
+ layoutModule: { layouts: [
596
+ ${layoutRefs.join(",\n")}
597
+ ] },
598
+ i18nModule: ${i18nFile ? i18nName : "{}"},
599
+ navigationModule: ${navigationFile ? navigationName : "{}"},
600
+ preferenceModule: ${preferencesFile ? preferencesName : "{}"},
601
+ }`;
602
+ return { imports, declaration, name, nextIndex: index };
603
+ }
604
+ function emitLayers(config) {
605
+ const lines = [];
606
+ const declarations = [];
607
+ const names = [];
608
+ let index = 0;
609
+ layerSources(config).forEach((source, layer) => {
610
+ if ("entry" in source) {
611
+ const name = `__layer${layer}`;
612
+ lines.push(`import ${name} from "${normalizePath(source.entry)}"`);
613
+ names.push(name);
614
+ return;
615
+ }
616
+ const collected = collectLayer(source.srcDir, layer, index);
617
+ index = collected.nextIndex;
618
+ lines.push(...collected.imports);
619
+ declarations.push(collected.declaration);
620
+ names.push(collected.name);
621
+ });
622
+ return { header: [...lines, ...declarations].join("\n"), names };
623
+ }
624
+ function spreadLayers(names, key, list) {
625
+ return names.map((name) => ` ...(${name}.${key}?.${list} ?? [])`).join(",\n");
626
+ }
627
+
628
+ // src/routes.ts
629
+ function getRoute2(file, routesDir) {
630
+ const importPath = normalizePath(file);
631
+ const normalizedRoutesDir = normalizePath(routesDir);
632
+ return importPath.replace(normalizedRoutesDir, "").replace(/\[\[\*\]\]/g, "*?").replace(/\[\*\]/g, "*").replace(/\(([^[\])]+)\)/g, "$1?").replace(/\[\[([^\]]+)\]\]/g, ":$1?").replace(/\[([^\]]+)\]/g, ":$1").replace(
633
+ new RegExp(
634
+ `/page\\.(${VALID_FILE_EXTENSIONS.map((ext) => ext.slice(1)).join("|")})$`
635
+ ),
636
+ ""
637
+ ) || "/";
638
+ }
639
+ function crawlRoutes(dir, pattern = "page") {
640
+ const files = [];
641
+ if (!import_fs5.default.existsSync(dir)) {
642
+ return files;
643
+ }
644
+ const entries = import_fs5.default.readdirSync(dir, { withFileTypes: true });
645
+ for (const entry of entries) {
646
+ const fullPath = import_path4.default.join(dir, entry.name);
647
+ if (entry.isDirectory()) {
648
+ files.push(...crawlRoutes(fullPath, pattern));
649
+ } else if (entry.isFile()) {
650
+ const ext = import_path4.default.extname(entry.name);
651
+ const baseName = import_path4.default.basename(entry.name, ext);
652
+ if (baseName === pattern && VALID_FILE_EXTENSIONS.includes(ext)) {
653
+ files.push(fullPath);
654
+ }
655
+ }
656
+ }
657
+ return files;
658
+ }
659
+ function hasConfigPublic(ast) {
660
+ let found = false;
661
+ traverse(ast, {
662
+ ExportNamedDeclaration(path6) {
663
+ const declaration = path6.node.declaration;
664
+ if (!(0, import_types.isVariableDeclaration)(declaration)) return;
665
+ for (const decl of declaration.declarations) {
666
+ if ((0, import_types.isVariableDeclarator)(decl) && (0, import_types.isIdentifier)(decl.id, { name: "config" }) && decl.init?.type === "ObjectExpression") {
667
+ const publicProp = decl.init.properties.find(
668
+ (prop) => (0, import_types.isObjectProperty)(prop) && (0, import_types.isIdentifier)(prop.key, { name: "public" }) && (0, import_types.isBooleanLiteral)(prop.value, { value: true })
669
+ );
670
+ if (publicProp) {
671
+ found = true;
672
+ }
673
+ }
674
+ }
675
+ }
676
+ });
677
+ return found;
678
+ }
679
+ function getNamedExports(ast) {
680
+ let hasHandle = false;
681
+ let hasLoader = false;
682
+ traverse(ast, {
683
+ ExportNamedDeclaration(path6) {
684
+ const declaration = path6.node.declaration;
685
+ if (declaration?.type === "VariableDeclaration") {
686
+ declaration.declarations.forEach((decl) => {
687
+ if (decl.id.type === "Identifier" && decl.id.name === "handle") {
688
+ hasHandle = true;
689
+ }
690
+ if (decl.id.type === "Identifier" && decl.id.name === "loader") {
691
+ hasLoader = true;
692
+ }
693
+ });
694
+ }
695
+ if (declaration?.type === "FunctionDeclaration" && declaration.id?.name === "loader") {
696
+ hasLoader = true;
697
+ }
698
+ if (declaration?.type === "FunctionDeclaration" && declaration.id?.name === "handle") {
699
+ hasHandle = true;
700
+ }
701
+ }
702
+ });
703
+ return { hasHandle, hasLoader };
704
+ }
705
+ function generateRouteComponentName(index) {
706
+ return `RouteComponent${index}`;
707
+ }
708
+ function generateHandleName(index) {
709
+ return `RouteHandle${index}`;
710
+ }
711
+ function generateLoaderName(index) {
712
+ return `RouteLoader${index}`;
713
+ }
714
+ function generateImports(file, index, hasHandle, hasLoader) {
715
+ const imports = [];
716
+ const componentName = generateRouteComponentName(index);
717
+ const importPath = normalizePath(file);
718
+ if (!hasHandle && !hasLoader) {
719
+ imports.push(`import ${componentName} from "${importPath}"`);
720
+ } else {
721
+ const namedImports = [
722
+ hasHandle && `handle as ${generateHandleName(index)}`,
723
+ hasLoader && `loader as ${generateLoaderName(index)}`
724
+ ].filter(Boolean).join(", ");
725
+ imports.push(`import ${componentName}, { ${namedImports} } from "${importPath}"`);
726
+ }
727
+ return imports;
728
+ }
729
+ function generateRouteObject(routePath, index, hasHandle, hasLoader, isPublic) {
730
+ return {
731
+ Component: generateRouteComponentName(index),
732
+ path: routePath,
733
+ handle: hasHandle ? generateHandleName(index) : void 0,
734
+ loader: hasLoader ? generateLoaderName(index) : void 0,
735
+ isPublic
736
+ };
737
+ }
738
+ function formatRoute(route, indent = " ") {
739
+ let result = `${indent}{
740
+ `;
741
+ result += `${indent} Component: ${route.Component},
742
+ `;
743
+ result += `${indent} path: "${route.path}"`;
744
+ if (route.handle) {
745
+ result += `,
746
+ ${indent} handle: ${route.handle}`;
747
+ }
748
+ if (route.loader) {
749
+ result += `,
750
+ ${indent} loader: ${route.loader}`;
751
+ }
752
+ if (route.isPublic) {
753
+ result += `,
754
+ ${indent} isPublic: true`;
755
+ }
756
+ if (route.children?.length) {
757
+ result += `,
758
+ ${indent} children: [
759
+ `;
760
+ result += route.children.map((child) => formatRoute(child, indent + " ")).join(",\n");
761
+ result += `
762
+ ${indent} ]`;
763
+ }
764
+ result += `
765
+ ${indent}}`;
766
+ return result;
581
767
  }
582
- function generateMenuItems({ srcDir, pluginExtensions }) {
583
- const routesDir = import_path3.default.join(srcDir, "routes");
584
- let index = 0;
585
- const results = [];
586
- for (const file of crawlRoutes2(routesDir)) {
587
- const result = parseMenuItemFile(file, routesDir, index);
588
- if (result) {
589
- results.push(result);
590
- index++;
768
+ function parseFile(file, routesDir, index) {
769
+ try {
770
+ const code = import_fs5.default.readFileSync(file, "utf-8");
771
+ const ast = (0, import_parser.parse)(code, getParserOptions(file));
772
+ if (!hasDefaultExport(ast)) {
773
+ return null;
591
774
  }
775
+ const { hasHandle, hasLoader } = getNamedExports(ast);
776
+ const isPublic = hasConfigPublic(ast);
777
+ const routePath = getRoute2(file, routesDir);
778
+ const imports = generateImports(file, index, hasHandle, hasLoader);
779
+ const route = generateRouteObject(routePath, index, hasHandle, hasLoader, isPublic);
780
+ return {
781
+ imports,
782
+ route
783
+ };
784
+ } catch {
785
+ return null;
592
786
  }
593
- const pluginDeclarations = pluginExtensions.map(
594
- (ext, i) => `import __plugin${i} from "${normalizePath(ext)}"`
595
- );
596
- const pluginSpreads = pluginExtensions.map(
597
- (_, i) => ` ...(__plugin${i}.menuItemModule?.menuItems ?? [])`
787
+ }
788
+ function buildRouteTree(results) {
789
+ const routeMap = /* @__PURE__ */ new Map();
790
+ const sortedResults = [...results].sort(
791
+ (a, b) => a.route.path.split("/").length - b.route.path.split("/").length
598
792
  );
599
- const appImports = results.map((r) => r.import);
600
- const appMenuItems = results.map((r) => formatMenuItem(r.menuItem));
601
- const allImports = [...appImports];
602
- const allMenuItems = [...appMenuItems, ...pluginSpreads];
603
- if (allImports.length === 0 && pluginDeclarations.length === 0 && allMenuItems.length === 0) {
604
- return `export default { menuItems: [] }`;
793
+ for (const result of sortedResults) {
794
+ const routePath = result.route.path;
795
+ const isParallel = routePath.includes("/@");
796
+ if (isParallel) {
797
+ const parentPath = routePath.split("/@")[0];
798
+ const parent = routeMap.get(parentPath);
799
+ if (parent) {
800
+ parent.route.children = parent.route.children ?? [];
801
+ parent.route.children.push({
802
+ ...result.route,
803
+ path: result.route.path.replace("/@", "/")
804
+ });
805
+ parent.imports.push(...result.imports);
806
+ } else {
807
+ routeMap.set(routePath, result);
808
+ }
809
+ } else {
810
+ routeMap.set(routePath, result);
811
+ }
605
812
  }
606
- return `${allImports.join("\n")}
607
-
608
- ${pluginDeclarations.join("\n")}
813
+ return Array.from(routeMap.values());
814
+ }
815
+ function generateRoutes(config) {
816
+ const { header, names } = emitLayers(config);
817
+ return `${header}
609
818
 
610
- export default {
611
- menuItems: [
612
- ${allMenuItems.join(",\n")}
613
- ]
614
- }`;
819
+ export const customRoutes = [
820
+ ${spreadLayers(names, "routeModule", "routes")}
821
+ ]`;
615
822
  }
616
823
 
617
824
  // src/i18n.ts
618
- var import_fs4 = __toESM(require("fs"), 1);
619
- var import_path4 = __toESM(require("path"), 1);
620
- function findI18nIndex(srcDir) {
621
- const i18nDir = import_path4.default.join(srcDir, "i18n");
622
- if (!import_fs4.default.existsSync(i18nDir)) {
623
- return null;
624
- }
625
- for (const ext of VALID_FILE_EXTENSIONS) {
626
- const filePath = import_path4.default.join(i18nDir, `index${ext}`);
627
- if (import_fs4.default.existsSync(filePath)) {
628
- return filePath;
629
- }
630
- }
631
- return null;
632
- }
633
- function generateI18n({ srcDir, pluginExtensions }) {
634
- const indexFile = findI18nIndex(srcDir);
635
- const imports = [
636
- ...indexFile ? [`import appI18n from "${normalizePath(indexFile)}"`] : [],
637
- ...pluginExtensions.map(
638
- (ext, i) => `import __plugin${i} from "${normalizePath(ext)}"`
639
- )
640
- ];
641
- const sources = [
642
- ...pluginExtensions.map((_, i) => `(__plugin${i}.i18nModule ?? {})`),
643
- ...indexFile ? ["appI18n"] : []
644
- ];
645
- return `${imports.join("\n")}
825
+ function generateI18n(config) {
826
+ const { header, names } = emitLayers(config);
827
+ return `${header}
646
828
 
647
829
  function deepMerge(target, source) {
648
830
  const result = { ...target }
@@ -668,205 +850,62 @@ function mergeResources(target, source) {
668
850
  return result
669
851
  }
670
852
 
671
- export default [${sources.join(", ")}].reduce(mergeResources, {})
853
+ export default [${names.map((name) => `(${name}.i18nModule ?? {})`).join(", ")}].reduce(mergeResources, {})
672
854
  `;
673
855
  }
674
856
 
675
- // src/widgets.ts
676
- var import_fs5 = __toESM(require("fs"), 1);
677
- var import_path5 = __toESM(require("path"), 1);
678
- function extractStrings(value, zones) {
679
- if ((0, import_types.isStringLiteral)(value)) {
680
- zones.push(value.value);
681
- } else if ((0, import_types.isArrayExpression)(value)) {
682
- for (const el of value.elements) {
683
- if ((0, import_types.isStringLiteral)(el)) zones.push(el.value);
684
- }
685
- }
686
- }
687
- function readWidgetConfig(properties) {
688
- const zones = [];
689
- const permissions = [];
690
- let id;
691
- let label;
692
- for (const prop of properties) {
693
- if (!(0, import_types.isObjectProperty)(prop)) continue;
694
- if ((0, import_types.isIdentifier)(prop.key, { name: "zone" })) {
695
- extractStrings(prop.value, zones);
696
- }
697
- if ((0, import_types.isIdentifier)(prop.key, { name: "permission" })) {
698
- extractStrings(prop.value, permissions);
699
- }
700
- if ((0, import_types.isIdentifier)(prop.key, { name: "id" }) && (0, import_types.isStringLiteral)(prop.value)) {
701
- id = prop.value.value;
857
+ // src/navigation.ts
858
+ function generateNavigation(config) {
859
+ const { header, names } = emitLayers(config);
860
+ return `${header}
861
+
862
+ const groups = new Map()
863
+ const items = new Map()
864
+ for (const navigation of [${names.map((name) => `${name}.navigationModule ?? {}`).join(", ")}]) {
865
+ for (const group of navigation.groups ?? []) {
866
+ if (group?.id) groups.set(group.id, { ...(groups.get(group.id) ?? {}), ...group })
702
867
  }
703
- if ((0, import_types.isIdentifier)(prop.key, { name: "label" }) && (0, import_types.isStringLiteral)(prop.value)) {
704
- label = prop.value.value;
868
+ for (const item of navigation.items ?? []) {
869
+ if (item?.id) items.set(item.id, { ...(items.get(item.id) ?? {}), ...item })
705
870
  }
706
- }
707
- if (zones.length === 0) return null;
708
- return { zones, id, label, permissions };
709
- }
710
- function getConfigProperties(node) {
711
- if ((0, import_types.isCallExpression)(node) && node.arguments.length > 0 && (0, import_types.isObjectExpression)(node.arguments[0])) {
712
- return node.arguments[0].properties;
713
- }
714
- if ((0, import_types.isObjectExpression)(node)) {
715
- return node.properties;
716
- }
717
- return null;
718
- }
719
- function getWidgetConfig(file) {
720
- try {
721
- const code = import_fs5.default.readFileSync(file, "utf-8");
722
- const ast = (0, import_parser.parse)(code, getParserOptions(file));
723
- if (!hasDefaultExport(ast)) return null;
724
- let info = null;
725
- const visit = (declaration) => {
726
- if (info) return;
727
- if (!(0, import_types.isVariableDeclaration)(declaration)) return;
728
- for (const decl of declaration.declarations) {
729
- if ((0, import_types.isVariableDeclarator)(decl) && (0, import_types.isIdentifier)(decl.id, { name: "config" })) {
730
- const props = getConfigProperties(decl.init);
731
- if (props) info = readWidgetConfig(props);
732
- }
733
- }
734
- };
735
- traverse(ast, {
736
- ExportNamedDeclaration(p) {
737
- visit(p.node.declaration);
738
- },
739
- VariableDeclaration(p) {
740
- visit(p.node);
741
- }
742
- });
743
- return info;
744
- } catch {
745
- return null;
746
- }
747
- }
748
- function hashPath(relative) {
749
- let hash = 0;
750
- for (let i = 0; i < relative.length; i++) {
751
- hash = (hash << 5) - hash + relative.charCodeAt(i);
752
- hash |= 0;
753
- }
754
- return Math.abs(hash).toString(36).slice(0, 6);
755
- }
756
- function widgetId(info, file, widgetsDir) {
757
- if (info.id) return info.id;
758
- const relative = normalizePath(import_path5.default.relative(widgetsDir, file));
759
- return `Widget-${hashPath(relative)}`;
760
- }
761
- function parseWidgetFile(file, widgetsDir, index) {
762
- const info = getWidgetConfig(file);
763
- if (!info) return null;
764
- const name = `Widget${index}`;
765
- const importPath = normalizePath(file);
766
- const zonesLiteral = JSON.stringify(info.zones);
767
- const id = widgetId(info, file, widgetsDir);
768
- return {
769
- import: `import ${name} from "${importPath}"`,
770
- entry: ` { Component: ${name}, zone: ${zonesLiteral}, widgetId: ${JSON.stringify(
771
- id
772
- )}${info.label ? `, label: ${JSON.stringify(info.label)}` : ""}${info.permissions.length ? `, permission: ${JSON.stringify(info.permissions)}` : ""} }`
773
- };
774
871
  }
775
- function collectWidgets(srcDir, startIndex = 0) {
776
- const widgetsDir = import_path5.default.join(srcDir, "widgets");
777
- let index = startIndex;
778
- const results = [];
779
- for (const file of crawlModuleFiles(widgetsDir)) {
780
- const result = parseWidgetFile(file, widgetsDir, index);
781
- if (result) {
782
- results.push(result);
783
- index++;
784
- }
785
- }
786
- return {
787
- imports: results.map((r) => r.import),
788
- entries: results.map((r) => r.entry)
789
- };
872
+
873
+ export default { groups: [...groups.values()], items: [...items.values()] }`;
790
874
  }
791
- function generateWidgets({
792
- srcDir,
793
- pluginExtensions
794
- }) {
795
- const { imports, entries: widgetEntries } = collectWidgets(srcDir);
796
- const pluginDeclarations = pluginExtensions.map(
797
- (ext, i) => `import __plugin${i} from "${normalizePath(ext)}"`
798
- );
799
- const pluginSpreads = pluginExtensions.map(
800
- (_, i) => ` ...(__plugin${i}.widgetModule?.widgets ?? [])`
801
- );
802
- const entries = [...widgetEntries, ...pluginSpreads];
803
- if (imports.length === 0 && pluginDeclarations.length === 0 && entries.length === 0) {
804
- return `export default { widgets: [] }`;
805
- }
806
- return `${imports.join("\n")}
807
875
 
808
- ${pluginDeclarations.join("\n")}
876
+ // src/custom-fields.ts
877
+ function generateCustomFields(config) {
878
+ const { header, names } = emitLayers(config);
879
+ return `${header}
809
880
 
810
881
  export default {
811
- widgets: [
812
- ${entries.join(",\n")}
882
+ configs: [
883
+ ${spreadLayers(names, "customFieldsModule", "configs")}
813
884
  ]
814
885
  }`;
815
886
  }
816
887
 
817
- // src/navigation.ts
818
- var import_fs6 = __toESM(require("fs"), 1);
819
- var import_path6 = __toESM(require("path"), 1);
820
- function findNavigationFile(srcDir) {
821
- for (const ext of VALID_FILE_EXTENSIONS) {
822
- const filePath = import_path6.default.join(srcDir, `_navigation${ext}`);
823
- if (import_fs6.default.existsSync(filePath)) return filePath;
824
- }
825
- return null;
826
- }
827
- function generateNavigation({ srcDir }) {
828
- const file = findNavigationFile(srcDir);
829
- if (!file) {
830
- return `export default { items: [] }`;
831
- }
832
- return `import navigationConfig from "${normalizePath(file)}"
833
- export default navigationConfig`;
888
+ // src/layouts-module.ts
889
+ function generateLayouts(config) {
890
+ const { header, names } = emitLayers(config);
891
+ return `${header}
892
+
893
+ const byId = new Map()
894
+ for (const layout of [
895
+ ${spreadLayers(names, "layoutModule", "layouts")}
896
+ ]) {
897
+ if (layout?.id) byId.set(layout.id, layout)
834
898
  }
835
899
 
836
- // src/custom-fields.ts
837
- var import_path7 = __toESM(require("path"), 1);
838
- function generateCustomFields({
839
- srcDir,
840
- pluginExtensions
841
- }) {
842
- const dir = import_path7.default.join(srcDir, "custom-fields");
843
- const files = crawlModuleFiles(dir);
844
- const imports = files.map(
845
- (file, i) => `import __cf${i} from "${normalizePath(file)}"`
846
- );
847
- const localRefs = files.map((_, i) => `__cf${i}`);
848
- const pluginDeclarations = pluginExtensions.map(
849
- (ext, i) => `const __plugin${i} = (await import("${normalizePath(ext)}")).default`
850
- );
851
- const pluginSpreads = pluginExtensions.map(
852
- (_, i) => ` ...(__plugin${i}.customFieldsModule?.configs ?? [])`
853
- );
854
- const configs = [
855
- ...pluginSpreads,
856
- ...localRefs.map((ref) => ` ${ref}`)
857
- ];
858
- if (configs.length === 0 && pluginDeclarations.length === 0) {
859
- return `export default { configs: [] }`;
860
- }
861
- return `${imports.join("\n")}
900
+ export default { layouts: [...byId.values()] }`;
901
+ }
862
902
 
863
- ${pluginDeclarations.join("\n")}
903
+ // src/preferences-module.ts
904
+ function generatePreferences(config) {
905
+ const { header, names } = emitLayers(config);
906
+ return `${header}
864
907
 
865
- export default {
866
- configs: [
867
- ${configs.join(",\n")}
868
- ]
869
- }`;
908
+ export default { layers: [${names.map((name) => `${name}.preferenceModule ?? {}`).join(", ")}] }`;
870
909
  }
871
910
 
872
911
  // src/virtual-modules.ts
@@ -901,6 +940,12 @@ function loadVirtualModule({
901
940
  if (id === RESOLVED_CUSTOM_FIELDS_MODULE) {
902
941
  return generateCustomFields(mercurConfig);
903
942
  }
943
+ if (id === RESOLVED_LAYOUTS_MODULE) {
944
+ return generateLayouts(mercurConfig);
945
+ }
946
+ if (id === RESOLVED_PREFERENCES_MODULE) {
947
+ return generatePreferences(mercurConfig);
948
+ }
904
949
  return null;
905
950
  }
906
951
  function loadConfigModule(mercurConfig) {
@@ -918,19 +963,42 @@ function loadI18nModule(mercurConfig) {
918
963
 
919
964
  // src/plugin.ts
920
965
  function isRouteFile(file) {
921
- const basename = import_path8.default.basename(file, import_path8.default.extname(file));
966
+ const basename = import_path5.default.basename(file, import_path5.default.extname(file));
922
967
  return basename === "page";
923
968
  }
924
969
  function isWidgetFile(file) {
925
970
  return normalizeSep(file).includes("/src/widgets/");
926
971
  }
927
972
  function isNavigationFile(file) {
928
- const basename = import_path8.default.basename(file, import_path8.default.extname(file));
929
- return basename === "_navigation";
973
+ const basename = import_path5.default.basename(file, import_path5.default.extname(file));
974
+ return basename === NAVIGATION_FILE;
975
+ }
976
+ function isPreferencesFile(file) {
977
+ const basename = import_path5.default.basename(file, import_path5.default.extname(file));
978
+ return basename === LAYOUT_PREFERENCES_FILE;
930
979
  }
931
980
  function isCustomFieldFile(file) {
932
981
  return normalizeSep(file).includes("/src/custom-fields/");
933
982
  }
983
+ function isLayoutFile(file) {
984
+ return normalizeSep(file).includes("/layouts/");
985
+ }
986
+ function isI18nFile(file) {
987
+ return normalizeSep(file).includes("/i18n/");
988
+ }
989
+ var LAYERED_MODULES = [
990
+ RESOLVED_ROUTES_MODULE,
991
+ RESOLVED_MENU_ITEMS_MODULE,
992
+ RESOLVED_WIDGETS_MODULE,
993
+ RESOLVED_NAVIGATION_MODULE,
994
+ RESOLVED_CUSTOM_FIELDS_MODULE,
995
+ RESOLVED_LAYOUTS_MODULE,
996
+ RESOLVED_PREFERENCES_MODULE,
997
+ RESOLVED_I18N_MODULE
998
+ ];
999
+ function isExtensionFile(file) {
1000
+ return isRouteFile(file) || isWidgetFile(file) || isNavigationFile(file) || isPreferencesFile(file) || isCustomFieldFile(file) || isLayoutFile(file) || isI18nFile(file);
1001
+ }
934
1002
  function normalizeSep(file) {
935
1003
  return file.replace(/\\/g, "/");
936
1004
  }
@@ -951,7 +1019,7 @@ function isMedusaVirtualModule(id) {
951
1019
  return MEDUSA_VIRTUAL_MODULES.includes(id);
952
1020
  }
953
1021
  function filterResolvableDeps(specifiers, fromDir) {
954
- const require2 = (0, import_module.createRequire)(import_path8.default.join(fromDir, "noop.js"));
1022
+ const require2 = (0, import_module.createRequire)(import_path5.default.join(fromDir, "noop.js"));
955
1023
  return specifiers.filter((specifier) => {
956
1024
  try {
957
1025
  require2.resolve(specifier);
@@ -969,44 +1037,44 @@ function isResolvedMedusaVirtualModule(id) {
969
1037
  }
970
1038
  function findNodeModulesRoot(configDir) {
971
1039
  let dir = configDir;
972
- while (dir !== import_path8.default.dirname(dir)) {
973
- const candidate = import_path8.default.join(dir, "node_modules");
974
- if (import_fs7.default.existsSync(candidate) && import_fs7.default.statSync(candidate).isDirectory()) {
1040
+ while (dir !== import_path5.default.dirname(dir)) {
1041
+ const candidate = import_path5.default.join(dir, "node_modules");
1042
+ if (import_fs6.default.existsSync(candidate) && import_fs6.default.statSync(candidate).isDirectory()) {
975
1043
  return candidate;
976
1044
  }
977
- dir = import_path8.default.dirname(dir);
1045
+ dir = import_path5.default.dirname(dir);
978
1046
  }
979
- return import_path8.default.join(configDir, "node_modules");
1047
+ return import_path5.default.join(configDir, "node_modules");
980
1048
  }
981
1049
  function resolvePluginRoot(resolve, configDir, nodeModulesRoot) {
982
1050
  if (resolve.startsWith(".")) {
983
1051
  try {
984
- const resolved = import_path8.default.resolve(configDir, resolve);
985
- return import_fs7.default.existsSync(resolved) ? import_fs7.default.realpathSync(resolved) : null;
1052
+ const resolved = import_path5.default.resolve(configDir, resolve);
1053
+ return import_fs6.default.existsSync(resolved) ? import_fs6.default.realpathSync(resolved) : null;
986
1054
  } catch {
987
1055
  return null;
988
1056
  }
989
1057
  }
990
- const require2 = (0, import_module.createRequire)(import_path8.default.join(configDir, "noop.js"));
1058
+ const require2 = (0, import_module.createRequire)(import_path5.default.join(configDir, "noop.js"));
991
1059
  try {
992
- return import_fs7.default.realpathSync(
993
- import_path8.default.dirname(require2.resolve(`${resolve}/package.json`))
1060
+ return import_fs6.default.realpathSync(
1061
+ import_path5.default.dirname(require2.resolve(`${resolve}/package.json`))
994
1062
  );
995
1063
  } catch {
996
1064
  }
997
1065
  try {
998
- let dir = import_path8.default.dirname(require2.resolve(resolve));
999
- while (dir !== import_path8.default.dirname(dir)) {
1000
- if (import_fs7.default.existsSync(import_path8.default.join(dir, "package.json"))) {
1001
- return import_fs7.default.realpathSync(dir);
1066
+ let dir = import_path5.default.dirname(require2.resolve(resolve));
1067
+ while (dir !== import_path5.default.dirname(dir)) {
1068
+ if (import_fs6.default.existsSync(import_path5.default.join(dir, "package.json"))) {
1069
+ return import_fs6.default.realpathSync(dir);
1002
1070
  }
1003
- dir = import_path8.default.dirname(dir);
1071
+ dir = import_path5.default.dirname(dir);
1004
1072
  }
1005
1073
  } catch {
1006
1074
  }
1007
1075
  try {
1008
- const packagePath = import_path8.default.join(nodeModulesRoot, resolve);
1009
- return import_fs7.default.existsSync(packagePath) ? import_fs7.default.realpathSync(packagePath) : null;
1076
+ const packagePath = import_path5.default.join(nodeModulesRoot, resolve);
1077
+ return import_fs6.default.existsSync(packagePath) ? import_fs6.default.realpathSync(packagePath) : null;
1010
1078
  } catch {
1011
1079
  return null;
1012
1080
  }
@@ -1024,13 +1092,13 @@ function resolvePluginExtensions(plugins, configDir, appType) {
1024
1092
  );
1025
1093
  continue;
1026
1094
  }
1027
- const extFile = import_path8.default.join(
1095
+ const extFile = import_path5.default.join(
1028
1096
  pluginRoot,
1029
1097
  ".medusa/server/src",
1030
1098
  appType,
1031
1099
  "index.mjs"
1032
1100
  );
1033
- if (import_fs7.default.existsSync(extFile)) {
1101
+ if (import_fs6.default.existsSync(extFile)) {
1034
1102
  extensions.push(extFile);
1035
1103
  }
1036
1104
  }
@@ -1044,7 +1112,7 @@ function trimTrailingSlashes(value) {
1044
1112
  return end === value.length ? value : value.slice(0, end);
1045
1113
  }
1046
1114
  async function loadMedusaConfig(medusaConfigPath, root, options) {
1047
- const configDir = import_path8.default.dirname(medusaConfigPath);
1115
+ const configDir = import_path5.default.dirname(medusaConfigPath);
1048
1116
  try {
1049
1117
  const previousCwd = process.cwd();
1050
1118
  let mod;
@@ -1074,7 +1142,7 @@ async function loadMedusaConfig(medusaConfigPath, root, options) {
1074
1142
  const value = modules[key];
1075
1143
  if (!value || typeof value !== "object" || !value.options?.appDir)
1076
1144
  continue;
1077
- const appDir = import_path8.default.resolve(configDir, value.options.appDir);
1145
+ const appDir = import_path5.default.resolve(configDir, value.options.appDir);
1078
1146
  if (appDir === root) {
1079
1147
  base = value.options.path;
1080
1148
  appType = key === "vendor_ui" ? "vendor" : "admin";
@@ -1085,7 +1153,9 @@ async function loadMedusaConfig(medusaConfigPath, root, options) {
1085
1153
  (plugin) => plugin.resolve !== "@medusajs/draft-order"
1086
1154
  ) ?? [];
1087
1155
  const pluginExtensions = resolvePluginExtensions(plugins, configDir, appType);
1088
- return { base, pluginExtensions, vendorAppUrl };
1156
+ const projectCandidate = import_path5.default.join(configDir, "src", appType);
1157
+ const projectDir = import_fs6.default.existsSync(projectCandidate) && import_fs6.default.realpathSync(projectCandidate) !== root ? projectCandidate : void 0;
1158
+ return { base, pluginExtensions, projectDir, vendorAppUrl };
1089
1159
  } catch (error) {
1090
1160
  console.warn(
1091
1161
  `[@mercurjs/dashboard-sdk] Could not load the Medusa config from "${medusaConfigPath}": ${error instanceof Error ? error.message : String(error)}. Building with base "/" and no plugin extensions \u2014 if this panel is served under a sub-path (e.g. /dashboard), its assets will not resolve.`
@@ -1101,11 +1171,11 @@ function mercurDashboardPlugin(pluginConfig) {
1101
1171
  async config(viteConfig) {
1102
1172
  root = viteConfig.root || process.cwd();
1103
1173
  const isDevelopment = (viteConfig.mode || process.env.NODE_ENV || "development") !== "production";
1104
- const medusaConfigPath = import_path8.default.resolve(
1174
+ const medusaConfigPath = import_path5.default.resolve(
1105
1175
  root,
1106
1176
  pluginConfig.medusaConfigPath
1107
1177
  );
1108
- const { base, pluginExtensions, vendorAppUrl } = await loadMedusaConfig(
1178
+ const { base, pluginExtensions, projectDir, vendorAppUrl } = await loadMedusaConfig(
1109
1179
  medusaConfigPath,
1110
1180
  root,
1111
1181
  {
@@ -1113,7 +1183,7 @@ function mercurDashboardPlugin(pluginConfig) {
1113
1183
  vendorUrl: pluginConfig.vendorUrl
1114
1184
  }
1115
1185
  );
1116
- const srcDir = import_path8.default.join(root, "src");
1186
+ const srcDir = import_path5.default.join(root, "src");
1117
1187
  const backendUrl = pluginConfig.backendUrl ?? "http://localhost:9000";
1118
1188
  const imageLimit = pluginConfig.imageLimit ?? 2 * 1024 * 1024;
1119
1189
  config = {
@@ -1122,6 +1192,7 @@ function mercurDashboardPlugin(pluginConfig) {
1122
1192
  base,
1123
1193
  root,
1124
1194
  srcDir,
1195
+ projectDir,
1125
1196
  pluginExtensions,
1126
1197
  imageLimit
1127
1198
  };
@@ -1136,16 +1207,7 @@ function mercurDashboardPlugin(pluginConfig) {
1136
1207
  dedupe: ["i18next", "react-i18next", "react", "react-dom"]
1137
1208
  },
1138
1209
  optimizeDeps: {
1139
- exclude: [
1140
- "virtual:mercur/config",
1141
- "virtual:mercur/routes",
1142
- "virtual:mercur/menu-items",
1143
- "virtual:mercur/i18n",
1144
- "virtual:mercur/widgets",
1145
- "virtual:mercur/navigation",
1146
- "virtual:mercur/custom-fields",
1147
- ...MEDUSA_VIRTUAL_MODULES
1148
- ],
1210
+ exclude: [...VIRTUAL_MODULES, ...MEDUSA_VIRTUAL_MODULES],
1149
1211
  include: filterResolvableDeps(
1150
1212
  [
1151
1213
  "react",
@@ -1191,10 +1253,9 @@ function mercurDashboardPlugin(pluginConfig) {
1191
1253
  }
1192
1254
  };
1193
1255
  const handleChange = (file) => {
1194
- if (isRouteFile(file)) invalidate(RESOLVED_ROUTES_MODULE, true);
1195
- if (isWidgetFile(file)) invalidate(RESOLVED_WIDGETS_MODULE, true);
1196
- if (isNavigationFile(file)) invalidate(RESOLVED_NAVIGATION_MODULE, true);
1197
- if (isCustomFieldFile(file)) invalidate(RESOLVED_CUSTOM_FIELDS_MODULE, true);
1256
+ if (!isExtensionFile(file)) return;
1257
+ for (const moduleId of LAYERED_MODULES) invalidate(moduleId, false);
1258
+ server.ws.send({ type: "full-reload" });
1198
1259
  };
1199
1260
  server.watcher.on("add", handleChange);
1200
1261
  server.watcher.on("unlink", handleChange);
@@ -1204,103 +1265,21 @@ function mercurDashboardPlugin(pluginConfig) {
1204
1265
  const mod = server.moduleGraph.getModuleById(moduleId);
1205
1266
  if (mod) server.moduleGraph.invalidateModule(mod);
1206
1267
  };
1207
- if (isRouteFile(file)) invalidate(RESOLVED_ROUTES_MODULE);
1208
- if (isWidgetFile(file)) invalidate(RESOLVED_WIDGETS_MODULE);
1209
- if (isNavigationFile(file)) invalidate(RESOLVED_NAVIGATION_MODULE);
1210
- if (isCustomFieldFile(file)) invalidate(RESOLVED_CUSTOM_FIELDS_MODULE);
1268
+ if (!isExtensionFile(file)) return;
1269
+ for (const moduleId of LAYERED_MODULES) invalidate(moduleId);
1211
1270
  }
1212
1271
  };
1213
1272
  }
1214
1273
 
1215
1274
  // src/generate-plugin-entry.ts
1216
- var import_path9 = __toESM(require("path"), 1);
1217
- var import_fs8 = __toESM(require("fs"), 1);
1218
- function findI18nIndex2(srcDir) {
1219
- const i18nDir = import_path9.default.join(srcDir, "i18n");
1220
- if (!import_fs8.default.existsSync(i18nDir)) return null;
1221
- for (const ext of VALID_FILE_EXTENSIONS) {
1222
- const filePath = import_path9.default.join(i18nDir, `index${ext}`);
1223
- if (import_fs8.default.existsSync(filePath)) return filePath;
1224
- }
1225
- return null;
1226
- }
1227
1275
  function generatePluginEntryModule(srcDir) {
1228
- const routesDir = import_path9.default.join(srcDir, "routes");
1229
- const files = crawlRoutes(routesDir);
1230
- let index = 0;
1231
- const routeResults = [];
1232
- const menuItemResults = [];
1233
- for (const file of files) {
1234
- const route = parseFile(file, routesDir, index);
1235
- if (route) {
1236
- routeResults.push(route);
1237
- }
1238
- const menuItem = parseMenuItemFile(file, routesDir, index);
1239
- if (menuItem) {
1240
- menuItemResults.push(menuItem);
1241
- }
1242
- index++;
1243
- }
1244
- const routeTree = buildRouteTree(routeResults.filter(Boolean));
1245
- const routeImports = routeTree.flatMap((r) => r.imports);
1246
- const routes = routeTree.map((r) => formatRoute(r.route));
1247
- const menuItemImports = menuItemResults.filter(Boolean).map((r) => r.import);
1248
- const menuItems = menuItemResults.filter(Boolean).map((r) => formatMenuItem(r.menuItem));
1249
- const { imports: widgetImports, entries: widgetEntries } = collectWidgets(
1250
- srcDir,
1251
- index
1252
- );
1253
- const cfFiles = crawlModuleFiles(import_path9.default.join(srcDir, "custom-fields"));
1254
- const cfImports = cfFiles.map(
1255
- (file, i) => `import __cf${i} from "${normalizePath(file)}"`
1256
- );
1257
- const cfRefs = cfFiles.map((_, i) => ` __cf${i}`);
1258
- const i18nFile = findI18nIndex2(srcDir);
1259
- const i18nImport = i18nFile ? `import i18nResources from "${normalizePath(i18nFile)}"` : "";
1260
- const i18nValue = i18nFile ? "i18nResources" : "{}";
1261
- const allImports = [
1262
- ...routeImports,
1263
- ...menuItemImports,
1264
- ...widgetImports,
1265
- ...cfImports
1266
- ];
1267
- if (i18nImport) allImports.push(i18nImport);
1276
+ const { imports, declaration, name } = collectLayer(srcDir, 0);
1268
1277
  return `// Auto-generated plugin extensions entry
1269
- ${allImports.join("\n")}
1270
-
1271
- const routeModule = {
1272
- routes: [
1273
- ${routes.join(",\n")}
1274
- ]
1275
- }
1276
-
1277
- const menuItemModule = {
1278
- menuItems: [
1279
- ${menuItems.join(",\n")}
1280
- ]
1281
- }
1282
-
1283
- const widgetModule = {
1284
- widgets: [
1285
- ${widgetEntries.join(",\n")}
1286
- ]
1287
- }
1288
-
1289
- const customFieldsModule = {
1290
- configs: [
1291
- ${cfRefs.join(",\n")}
1292
- ]
1293
- }
1278
+ ${imports.join("\n")}
1294
1279
 
1295
- const plugin = {
1296
- routeModule,
1297
- menuItemModule,
1298
- widgetModule,
1299
- customFieldsModule,
1300
- i18nModule: ${i18nValue},
1301
- }
1280
+ ${declaration}
1302
1281
 
1303
- export default plugin
1282
+ export default ${name}
1304
1283
  `;
1305
1284
  }
1306
1285
  // Annotate the CommonJS export names for ESM import in node: