@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.
- package/dist/mcp-entry.mjs +174 -66
- package/dist/mcp-entry.mjs.map +1 -1
- package/package.json +1 -1
- package/templates/mcp-scaffolding/frontend/api-client.ts.hbs +1 -0
- package/templates/skills/apex/references/agent-teams-protocol.md +28 -5
- package/templates/skills/apex/steps/step-02-plan.md +132 -0
- package/templates/skills/apex/steps/step-03-execute.md +71 -0
- package/templates/skills/apex/steps/step-04-examine.md +21 -0
- package/templates/skills/apex/steps/step-05-deep-review.md +11 -0
- package/templates/skills/apex/steps/step-06-resolve.md +24 -5
- package/templates/skills/apex/steps/step-07-tests.md +11 -0
- package/templates/skills/application/references/frontend-route-wiring-app-tsx.md +36 -4
- package/templates/skills/application/references/nav-fallback-procedure.md +6 -4
- package/templates/skills/controller/steps/step-01-analyze.md +8 -2
- package/templates/skills/documentation/steps/step-03-validate.md +47 -16
- package/templates/mcp-scaffolding/frontend/nav-routes.ts.hbs +0 -133
- package/templates/mcp-scaffolding/frontend/routes.tsx.hbs +0 -126
package/dist/mcp-entry.mjs
CHANGED
|
@@ -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
|
|
27295
|
-
|
|
27296
|
-
const routePath =
|
|
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 '
|
|
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
|
-
|
|
57983
|
-
|
|
57984
|
-
|
|
57985
|
-
|
|
57986
|
-
|
|
57987
|
-
|
|
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
|
|
57993
|
-
|
|
57994
|
-
const navRoute =
|
|
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("
|
|
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 =
|
|
58365
|
-
const
|
|
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
|
|
58739
|
-
|
|
58740
|
-
const navRoute =
|
|
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(
|
|
58759
|
-
webPath: `/${navRoute.split(".").map(
|
|
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
|
|
58878
|
-
const
|
|
58879
|
-
|
|
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
|
-
|
|
58895
|
-
|
|
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.
|
|
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(
|
|
58979
|
-
const
|
|
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.
|