@atlashub/smartstack-cli 4.14.0 → 4.16.1

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.
@@ -26779,6 +26779,25 @@ var init_detector = __esm({
26779
26779
  }
26780
26780
  });
26781
26781
 
26782
+ // src/mcp/lib/navroute-parser.ts
26783
+ function extractNavRoutes(content) {
26784
+ const results = [];
26785
+ const regex = /\[NavRoute\s*\(\s*"([^"]+)"(?:\s*,\s*Suffix\s*=\s*"([^"]+)")?\s*\)\]/g;
26786
+ for (const match2 of content.matchAll(regex)) {
26787
+ const navRoute = match2[1];
26788
+ const suffix = match2[2];
26789
+ const fullNavRoute = suffix ? `${navRoute}.${suffix}` : navRoute;
26790
+ results.push({ navRoute, suffix, fullNavRoute });
26791
+ }
26792
+ return results;
26793
+ }
26794
+ var init_navroute_parser = __esm({
26795
+ "src/mcp/lib/navroute-parser.ts"() {
26796
+ "use strict";
26797
+ init_esm_shims();
26798
+ }
26799
+ });
26800
+
26782
26801
  // src/mcp/tools/validate-conventions.ts
26783
26802
  import path8 from "path";
26784
26803
  async function handleValidateConventions(args, config2) {
@@ -27291,9 +27310,9 @@ async function validateControllerRoutes(structure, _config, result) {
27291
27310
  const hasHardcodedRoute = content.includes('[Route("api/[controller]")]') || content.includes('[Route("api/') || /\[Route\s*\(\s*"[^"]+"\s*\)\]/.test(content);
27292
27311
  if (hasNavRoute) {
27293
27312
  navRouteCount++;
27294
- const navRouteMatch = content.match(/\[NavRoute\s*\(\s*"([^"]+)"(?:\s*,\s*Suffix\s*=\s*"([^"]+)")?\s*\)\]/);
27295
- if (navRouteMatch) {
27296
- const routePath = navRouteMatch[1];
27313
+ const parsedNavRoutes = extractNavRoutes(content);
27314
+ for (const parsedNR of parsedNavRoutes) {
27315
+ const routePath = parsedNR.navRoute;
27297
27316
  const parts = routePath.split(".");
27298
27317
  if (parts.length < 2) {
27299
27318
  result.errors.push({
@@ -28392,6 +28411,7 @@ var init_validate_conventions = __esm({
28392
28411
  init_fs();
28393
28412
  init_detector();
28394
28413
  init_logger();
28414
+ init_navroute_parser();
28395
28415
  validateConventionsTool = {
28396
28416
  name: "validate_conventions",
28397
28417
  description: "Validate AtlasHub/SmartStack conventions: SQL schemas (core/extensions), domain table prefixes (auth_, nav_, ai_, etc.), migration naming ({context}_v{version}_{sequence}_*), service interfaces (I*Service), namespace structure, controller routes (NavRoute), permission seeding safety",
@@ -57363,7 +57383,7 @@ async function scaffoldApiClient(input, config2) {
57363
57383
  const servicesPath = options?.outputPath || path18.join(webPath, "src", "services", "api");
57364
57384
  const hooksPath = path18.join(webPath, "src", "hooks");
57365
57385
  const typesPath = path18.join(webPath, "src", "types");
57366
- const apiClientContent = generateApiClient(name, nameLower, navRoute, apiPath, methods);
57386
+ const apiClientContent = generateApiClient(name, nameLower, navRoute, apiPath, methods, servicesPath, webPath);
57367
57387
  const apiClientFile = path18.join(servicesPath, `${nameLower}.ts`);
57368
57388
  if (!dryRun) {
57369
57389
  await ensureDirectory(servicesPath);
@@ -57398,7 +57418,13 @@ async function scaffoldApiClient(input, config2) {
57398
57418
  function navRouteToApiPath(navRoute) {
57399
57419
  return `/api/${navRoute.replace(/\./g, "/")}`;
57400
57420
  }
57401
- function generateApiClient(name, nameLower, navRoute, apiPath, methods) {
57421
+ function generateApiClient(name, nameLower, navRoute, apiPath, methods, servicesDir, webPath) {
57422
+ let routesImportPath = "../routes/navRoutes.generated";
57423
+ if (servicesDir && webPath) {
57424
+ const routesDir = path18.join(webPath, "src", "routes");
57425
+ const relativePath2 = path18.relative(servicesDir, routesDir).replace(/\\/g, "/");
57426
+ routesImportPath = `${relativePath2}/navRoutes.generated`;
57427
+ }
57402
57428
  const template = `/**
57403
57429
  * ${name} API Client
57404
57430
  *
@@ -57407,7 +57433,7 @@ function generateApiClient(name, nameLower, navRoute, apiPath, methods) {
57407
57433
  * API Path: ${apiPath}
57408
57434
  */
57409
57435
 
57410
- import { getRoute } from '../routes/navRoutes.generated';
57436
+ import { getRoute } from '${routesImportPath}';
57411
57437
  import { apiClient } from '../lib/apiClient';
57412
57438
  import type {
57413
57439
  ${name},
@@ -57758,6 +57784,34 @@ ensuring frontend routes stay synchronized with backend NavRoute attributes.`,
57758
57784
  }
57759
57785
  });
57760
57786
 
57787
+ // src/mcp/lib/string-utils.ts
57788
+ function toKebabCase(segment) {
57789
+ return segment.replace(/([A-Z]+)([A-Z][a-z])/g, "$1-$2").replace(/([a-z0-9])([A-Z])/g, "$1-$2").toLowerCase();
57790
+ }
57791
+ function toCamelCase2(segment) {
57792
+ return segment.replace(/-([a-z])/g, (_, c) => c.toUpperCase());
57793
+ }
57794
+ function capitalize(str) {
57795
+ return str.charAt(0).toUpperCase() + str.slice(1);
57796
+ }
57797
+ function singularize(word) {
57798
+ if (word.endsWith("ies")) return word.slice(0, -3) + "y";
57799
+ if (word.endsWith("sses")) return word.slice(0, -2);
57800
+ if (word.endsWith("shes") || word.endsWith("ches") || word.endsWith("xes") || word.endsWith("zes")) return word.slice(0, -2);
57801
+ if (word.endsWith("ses")) return word.slice(0, -1);
57802
+ if (word.endsWith("s") && !word.endsWith("ss")) return word.slice(0, -1);
57803
+ return word;
57804
+ }
57805
+ function navRouteToUrlPath(navRoute) {
57806
+ return navRoute.split(".").map(toKebabCase).join("/");
57807
+ }
57808
+ var init_string_utils = __esm({
57809
+ "src/mcp/lib/string-utils.ts"() {
57810
+ "use strict";
57811
+ init_esm_shims();
57812
+ }
57813
+ });
57814
+
57761
57815
  // src/mcp/tools/scaffold-routes.ts
57762
57816
  import path19 from "path";
57763
57817
  async function handleScaffoldRoutes(args, config2) {
@@ -57808,6 +57862,10 @@ async function scaffoldRoutes(input, config2) {
57808
57862
  result.files.push({ path: registryFile, content: registryContent, type: "created" });
57809
57863
  }
57810
57864
  const outputFormat = options?.outputFormat ?? "applicationRoutes";
57865
+ if (outputFormat === "clientRoutes") {
57866
+ if (!result.warnings) result.warnings = [];
57867
+ result.warnings.push('outputFormat "clientRoutes" is DEPRECATED. Use "applicationRoutes" instead (same behavior, correct naming).');
57868
+ }
57811
57869
  if (outputFormat === "applicationRoutes" || outputFormat === "clientRoutes") {
57812
57870
  const pageFiles = await discoverPageFiles(webPath, navRoutes);
57813
57871
  const applicationRoutesContent = generateApplicationRoutesConfig(navRoutes, pageFiles, includeGuards);
@@ -57920,6 +57978,9 @@ async function scaffoldRoutes(input, config2) {
57920
57978
  result.instructions.push("");
57921
57979
  }
57922
57980
  } else {
57981
+ if (!result.warnings) result.warnings = [];
57982
+ result.warnings.push("STANDALONE MODE: createBrowserRouter() is NOT compatible with SmartStack App.tsx which uses useRoutes() + mergeRoutes()");
57983
+ result.warnings.push("All routes are in TODO commented state \u2014 manual activation required");
57923
57984
  const routerContent = generateRouterConfig(navRoutes, includeGuards);
57924
57985
  const routerFile = path19.join(routesPath, "index.tsx");
57925
57986
  if (!dryRun) {
@@ -57941,6 +58002,7 @@ async function scaffoldRoutes(input, config2) {
57941
58002
  }
57942
58003
  }
57943
58004
  if (includeGuards) {
58005
+ result.warnings.push("Auth guards use stub implementations (ProtectedRoute, PermissionGuard) \u2014 replace with your auth hook");
57944
58006
  const guardsContent = generateRouteGuards();
57945
58007
  const guardsFile = path19.join(routesPath, "guards.tsx");
57946
58008
  if (!dryRun) {
@@ -57978,23 +58040,42 @@ async function discoverNavRoutes(structure, scope, warnings) {
57978
58040
  }
57979
58041
  logger.debug("Scanning API paths", { paths: apiPaths, scope });
57980
58042
  let controllerFiles = [];
58043
+ const isSpecificScope = scope !== "all" && scope !== "core" && scope !== "extensions";
57981
58044
  for (const apiPath of apiPaths) {
57982
- const files = await glob("**/*Controller.cs", {
57983
- cwd: apiPath,
57984
- absolute: true,
57985
- ignore: ["**/obj/**", "**/bin/**"]
57986
- });
57987
- controllerFiles = controllerFiles.concat(files);
58045
+ if (isSpecificScope) {
58046
+ const scopePascal = capitalize(scope);
58047
+ const patterns = [
58048
+ `**/${scope}/**/*Controller.cs`,
58049
+ `**/${scopePascal}/**/*Controller.cs`,
58050
+ `**/${scope}*Controller.cs`,
58051
+ `**/${scopePascal}*Controller.cs`
58052
+ ];
58053
+ for (const pattern of patterns) {
58054
+ const files = await glob(pattern, {
58055
+ cwd: apiPath,
58056
+ absolute: true,
58057
+ ignore: ["**/obj/**", "**/bin/**"]
58058
+ });
58059
+ controllerFiles = controllerFiles.concat(files);
58060
+ }
58061
+ controllerFiles = [...new Set(controllerFiles)];
58062
+ } else {
58063
+ const files = await glob("**/*Controller.cs", {
58064
+ cwd: apiPath,
58065
+ absolute: true,
58066
+ ignore: ["**/obj/**", "**/bin/**"]
58067
+ });
58068
+ controllerFiles = controllerFiles.concat(files);
58069
+ }
57988
58070
  }
57989
58071
  for (const file of controllerFiles) {
57990
58072
  try {
57991
58073
  const content = await readText(file);
57992
- const navRouteMatch = content.match(/\[NavRoute\s*\(\s*"([^"]+)"(?:\s*,\s*Suffix\s*=\s*"([^"]+)")?\s*\)\]/);
57993
- if (navRouteMatch) {
57994
- const navRoute = navRouteMatch[1];
57995
- const suffix = navRouteMatch[2];
58074
+ const parsedNavRoutes = extractNavRoutes(content);
58075
+ for (const parsed of parsedNavRoutes) {
58076
+ const { navRoute, suffix, fullNavRoute } = parsed;
57996
58077
  const application = navRoute.split(".")[0];
57997
- if (scope !== "all" && application !== scope) {
58078
+ if (scope !== "all" && scope !== "core" && scope !== "extensions" && application !== scope) {
57998
58079
  continue;
57999
58080
  }
58000
58081
  const controllerMatch = path19.basename(file).match(/(.+)Controller\.cs$/);
@@ -58010,7 +58091,6 @@ async function discoverNavRoutes(structure, scope, warnings) {
58010
58091
  for (const match2 of authorizeMatches) {
58011
58092
  permissions.push(match2[1]);
58012
58093
  }
58013
- const fullNavRoute = suffix ? `${navRoute}.${suffix}` : navRoute;
58014
58094
  routes.push({
58015
58095
  navRoute: fullNavRoute,
58016
58096
  apiPath: `/api/${navRouteToUrlPath(navRoute)}${suffix ? `/${toKebabCase(suffix)}` : ""}`,
@@ -58357,12 +58437,14 @@ async function discoverPageFiles(webPath, routes) {
58357
58437
  const moduleDir = moduleParts.join("/");
58358
58438
  const pagesDir = path19.join(webPath, "src", "pages", app, moduleDir || "");
58359
58439
  try {
58360
- const files = await glob("*Page.tsx", { cwd: pagesDir, absolute: false });
58440
+ const files = await glob("**/*Page.tsx", { cwd: pagesDir, absolute: false });
58361
58441
  if (files.length > 0) {
58362
58442
  const discovery = {};
58363
58443
  for (const f of files) {
58364
- const componentName = f.replace(".tsx", "");
58365
- const importPath = `@/pages/${app}/${moduleDir ? moduleDir + "/" : ""}${componentName}`;
58444
+ const componentName = path19.basename(f, ".tsx");
58445
+ const subDir = path19.dirname(f);
58446
+ const importBase = subDir !== "." ? `${moduleDir ? moduleDir + "/" : ""}${subDir}/` : `${moduleDir ? moduleDir + "/" : ""}`;
58447
+ const importPath = `@/pages/${app}/${importBase}${componentName}`;
58366
58448
  const entry = { importPath, componentName };
58367
58449
  if (componentName.endsWith("CreatePage")) {
58368
58450
  discovery.create = entry;
@@ -58547,24 +58629,6 @@ function generateApplicationRoutesConfig(routes, pageFiles, includeGuards) {
58547
58629
  function getLayoutName(_application) {
58548
58630
  return "AppLayout";
58549
58631
  }
58550
- function toKebabCase(segment) {
58551
- return segment.replace(/([a-z0-9])([A-Z])/g, "$1-$2").toLowerCase();
58552
- }
58553
- function toCamelCase2(segment) {
58554
- return segment.replace(/-([a-z])/g, (_, c) => c.toUpperCase());
58555
- }
58556
- function navRouteToUrlPath(navRoute) {
58557
- return navRoute.split(".").map(toKebabCase).join("/");
58558
- }
58559
- function capitalize(str) {
58560
- return str.charAt(0).toUpperCase() + str.slice(1);
58561
- }
58562
- function singularize(word) {
58563
- if (word.endsWith("ies")) return word.slice(0, -3) + "y";
58564
- if (word.endsWith("ses") || word.endsWith("xes") || word.endsWith("zes") || word.endsWith("ches") || word.endsWith("shes")) return word.slice(0, -2);
58565
- if (word.endsWith("s") && !word.endsWith("ss")) return word.slice(0, -1);
58566
- return word;
58567
- }
58568
58632
  function deriveEntityNames(navRoute) {
58569
58633
  const parts = navRoute.split(".");
58570
58634
  const lastSegment = parts[parts.length - 1];
@@ -58595,11 +58659,23 @@ function formatResult5(result, input) {
58595
58659
  const relativePath2 = file.path.replace(/\\/g, "/").split("/src/").pop() || file.path;
58596
58660
  lines.push(`### ${relativePath2}`);
58597
58661
  lines.push("");
58662
+ if (file.content.length > 2e3) {
58663
+ lines.push(`> **Note:** Content truncated to 2000 chars. Full file written to: \`${relativePath2}\``);
58664
+ lines.push("");
58665
+ }
58598
58666
  lines.push("```tsx");
58599
58667
  lines.push(file.content.substring(0, 2e3) + (file.content.length > 2e3 ? "\n// ... (truncated)" : ""));
58600
58668
  lines.push("```");
58601
58669
  lines.push("");
58602
58670
  }
58671
+ if (result.warnings && result.warnings.length > 0) {
58672
+ lines.push("## Warnings");
58673
+ lines.push("");
58674
+ for (const warning of result.warnings) {
58675
+ lines.push(`> ${warning}`);
58676
+ }
58677
+ lines.push("");
58678
+ }
58603
58679
  lines.push("## Instructions");
58604
58680
  lines.push("");
58605
58681
  for (const instruction of result.instructions) {
@@ -58617,6 +58693,8 @@ var init_scaffold_routes = __esm({
58617
58693
  init_detector();
58618
58694
  init_fs();
58619
58695
  init_esm8();
58696
+ init_string_utils();
58697
+ init_navroute_parser();
58620
58698
  scaffoldRoutesTool = {
58621
58699
  name: "scaffold_routes",
58622
58700
  description: `Generate React Router configuration from backend NavRoute attributes.
@@ -58735,11 +58813,9 @@ async function discoverBackendNavRoutes(structure) {
58735
58813
  for (const file of controllerFiles) {
58736
58814
  try {
58737
58815
  const content = await readText(file);
58738
- const navRouteMatch = content.match(/\[NavRoute\s*\(\s*"([^"]+)"(?:\s*,\s*Suffix\s*=\s*"([^"]+)")?\s*\)\]/);
58739
- if (navRouteMatch) {
58740
- const navRoute = navRouteMatch[1];
58741
- const suffix = navRouteMatch[2];
58742
- const fullNavRoute = suffix ? `${navRoute}.${suffix}` : navRoute;
58816
+ const parsedNavRoutes = extractNavRoutes(content);
58817
+ for (const parsed of parsedNavRoutes) {
58818
+ const { navRoute, suffix, fullNavRoute } = parsed;
58743
58819
  const controllerMatch = path20.basename(file).match(/(.+)Controller\.cs$/);
58744
58820
  const controllerName = controllerMatch ? controllerMatch[1] : "Unknown";
58745
58821
  const methods = [];
@@ -58755,8 +58831,8 @@ async function discoverBackendNavRoutes(structure) {
58755
58831
  }
58756
58832
  routes.push({
58757
58833
  navRoute: fullNavRoute,
58758
- apiPath: `/api/${navRoute.split(".").map(toKebabCase2).join("/")}${suffix ? `/${toKebabCase2(suffix)}` : ""}`,
58759
- webPath: `/${navRoute.split(".").map(toKebabCase2).join("/")}${suffix ? `/${toKebabCase2(suffix)}` : ""}`,
58834
+ apiPath: `/api/${navRoute.split(".").map(toKebabCase).join("/")}${suffix ? `/${toKebabCase(suffix)}` : ""}`,
58835
+ webPath: `/${navRoute.split(".").map(toKebabCase).join("/")}${suffix ? `/${toKebabCase(suffix)}` : ""}`,
58760
58836
  permissions,
58761
58837
  controller: controllerName,
58762
58838
  methods
@@ -58845,6 +58921,43 @@ async function validateApiClients(webPath, backendRoutes, result) {
58845
58921
  logger.debug(`Failed to parse API client: ${file}`);
58846
58922
  }
58847
58923
  }
58924
+ const pagesPath = path20.join(webPath, "src", "pages");
58925
+ try {
58926
+ const pageFiles = await glob("**/*.tsx", {
58927
+ cwd: pagesPath,
58928
+ absolute: true,
58929
+ ignore: ["**/node_modules/**"]
58930
+ });
58931
+ for (const file of pageFiles) {
58932
+ try {
58933
+ const content = await readText(file);
58934
+ const relativePath2 = path20.relative(webPath, file);
58935
+ const hardcodedNavigateMatches = content.matchAll(/navigate\s*\(\s*['"`](\/[^'"`]+)['"`]/g);
58936
+ for (const match2 of hardcodedNavigateMatches) {
58937
+ result.apiClients.issues.push({
58938
+ type: "invalid-path",
58939
+ severity: "warning",
58940
+ file: relativePath2,
58941
+ message: `Hardcoded navigate path: ${match2[1]}`,
58942
+ suggestion: "Use getWebRoute() from navRoutes.generated.ts for type-safe navigation"
58943
+ });
58944
+ }
58945
+ const hardcodedToMatches = content.matchAll(/to\s*=\s*['"`](\/[a-z][^'"`]+)['"`]/g);
58946
+ for (const match2 of hardcodedToMatches) {
58947
+ result.apiClients.issues.push({
58948
+ type: "invalid-path",
58949
+ severity: "warning",
58950
+ file: relativePath2,
58951
+ message: `Hardcoded Link to path: ${match2[1]}`,
58952
+ suggestion: "Use getWebRoute() from navRoutes.generated.ts for type-safe navigation"
58953
+ });
58954
+ }
58955
+ } catch {
58956
+ logger.debug(`Failed to parse page file: ${file}`);
58957
+ }
58958
+ }
58959
+ } catch {
58960
+ }
58848
58961
  }
58849
58962
  async function validateRoutes(webPath, backendRoutes, result) {
58850
58963
  const routeCandidates = ["applicationRoutes.generated.tsx", "clientRoutes.generated.tsx", "index.tsx"];
@@ -58874,16 +58987,9 @@ async function validateRoutes(webPath, backendRoutes, result) {
58874
58987
  }
58875
58988
  result.routes.total = frontendPaths.size;
58876
58989
  for (const backendRoute of backendRoutes) {
58877
- const webPath2 = backendRoute.webPath.replace(/^\//, "");
58878
- const parts = webPath2.split("/");
58879
- let found = false;
58880
- for (const pathPart of parts) {
58881
- if (frontendPaths.has(pathPart)) {
58882
- found = true;
58883
- break;
58884
- }
58885
- }
58886
- if (!found && parts.length > 0) {
58990
+ const modulePath = backendRoute.navRoute.split(".").slice(1).map(toKebabCase).join("/");
58991
+ const found = frontendPaths.has(modulePath) || [...frontendPaths].some((fp) => fp.startsWith(modulePath + "/") || fp === modulePath);
58992
+ if (!found && modulePath.length > 0) {
58887
58993
  result.routes.missing.push(backendRoute.navRoute);
58888
58994
  }
58889
58995
  }
@@ -58891,9 +58997,13 @@ async function validateRoutes(webPath, backendRoutes, result) {
58891
58997
  if (frontendPath === "*" || frontendPath === "" || frontendPath.startsWith(":")) {
58892
58998
  continue;
58893
58999
  }
58894
- const matchingBackend = backendRoutes.find(
58895
- (r) => r.webPath.includes(frontendPath) || r.navRoute.includes(frontendPath)
58896
- );
59000
+ if (frontendPath.endsWith("/create") || frontendPath.endsWith("/edit") || frontendPath.includes(":")) {
59001
+ continue;
59002
+ }
59003
+ const matchingBackend = backendRoutes.find((r) => {
59004
+ const backendModulePath = r.navRoute.split(".").slice(1).map(toKebabCase).join("/");
59005
+ return backendModulePath === frontendPath || frontendPath.startsWith(backendModulePath + "/");
59006
+ });
58897
59007
  if (!matchingBackend) {
58898
59008
  result.routes.orphaned.push(frontendPath);
58899
59009
  }
@@ -58959,7 +59069,7 @@ async function validateAppWiring(webPath, backendRoutes, result) {
58959
59069
  for (const route of backendRoutes) {
58960
59070
  const modulePath = route.webPath.replace(/^\//, "");
58961
59071
  if (modulePath !== modulePath.toLowerCase()) {
58962
- const kebabPath = modulePath.replace(/([a-z])([A-Z])/g, "$1-$2").toLowerCase();
59072
+ const kebabPath = modulePath.split("/").map(toKebabCase).join("/");
58963
59073
  caseMismatches.push(
58964
59074
  `Route "${route.navRoute}" uses PascalCase in URL. Found: ${modulePath} \u2192 Expected: ${kebabPath}`
58965
59075
  );
@@ -58975,9 +59085,8 @@ async function validateAppWiring(webPath, backendRoutes, result) {
58975
59085
  );
58976
59086
  }
58977
59087
  for (const route of backendRoutes) {
58978
- const modulePath = route.navRoute.split(".").slice(1).map(toKebabCase2).join("/");
58979
- const lastSegment = route.navRoute.split(".").pop() || "";
58980
- const pathInApp = appContent.includes(`path="${modulePath}"`) || appContent.includes(`path='${modulePath}'`) || appContent.includes(`path="${lastSegment}"`) || appContent.includes(`path='${lastSegment}'`);
59088
+ const modulePath = route.navRoute.split(".").slice(1).map(toKebabCase).join("/");
59089
+ const pathInApp = appContent.includes(`path="${modulePath}"`) || appContent.includes(`path='${modulePath}'`) || appContent.includes(`'${modulePath}'`) || appContent.includes(`"${modulePath}"`);
58981
59090
  if (!pathInApp) {
58982
59091
  result.appWiring.issues.push(
58983
59092
  `Route "${route.navRoute}" (path: ${modulePath}) not wired in App.tsx`
@@ -59019,9 +59128,6 @@ function generateRecommendations2(result) {
59019
59128
  result.recommendations.push("All routes are synchronized between frontend and backend");
59020
59129
  }
59021
59130
  }
59022
- function toKebabCase2(segment) {
59023
- return segment.replace(/([a-z0-9])([A-Z])/g, "$1-$2").toLowerCase();
59024
- }
59025
59131
  function formatResult6(result, _input) {
59026
59132
  const lines = [];
59027
59133
  const statusIcon = result.valid ? "\u2705" : "\u274C";
@@ -59130,6 +59236,8 @@ var init_validate_frontend_routes = __esm({
59130
59236
  init_detector();
59131
59237
  init_fs();
59132
59238
  init_esm8();
59239
+ init_string_utils();
59240
+ init_navroute_parser();
59133
59241
  validateFrontendRoutesTool = {
59134
59242
  name: "validate_frontend_routes",
59135
59243
  description: `Validate frontend routes against backend NavRoute attributes.