@atlashub/smartstack-cli 4.34.0 → 4.36.0

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.
Files changed (50) hide show
  1. package/dist/index.js +28 -32
  2. package/dist/index.js.map +1 -1
  3. package/dist/mcp-entry.mjs +35 -303
  4. package/dist/mcp-entry.mjs.map +1 -1
  5. package/package.json +1 -1
  6. package/templates/skills/apex/references/checks/seed-checks.sh +1 -1
  7. package/templates/skills/apex/references/core-seed-data.md +39 -21
  8. package/templates/skills/application/references/application-roles-template.md +14 -8
  9. package/templates/skills/application/references/provider-template.md +32 -20
  10. package/templates/skills/application/templates-frontend.md +350 -89
  11. package/templates/skills/application/templates-seed.md +23 -11
  12. package/templates/skills/audit-route/SKILL.md +107 -0
  13. package/templates/skills/audit-route/references/routing-pattern.md +129 -0
  14. package/templates/skills/audit-route/steps/step-00-init.md +128 -0
  15. package/templates/skills/audit-route/steps/step-01-inventory.md +157 -0
  16. package/templates/skills/audit-route/steps/step-02-conformity.md +193 -0
  17. package/templates/skills/audit-route/steps/step-03-report.md +201 -0
  18. package/templates/skills/cli-app-sync/SKILL.md +2 -2
  19. package/templates/skills/cli-app-sync/references/comparison-map.md +1 -1
  20. package/templates/skills/dev-start/SKILL.md +12 -2
  21. package/templates/skills/documentation/steps/step-03-validate.md +12 -14
  22. package/templates/skills/efcore/SKILL.md +219 -67
  23. package/templates/agents/efcore/conflicts.md +0 -114
  24. package/templates/agents/efcore/db-deploy.md +0 -86
  25. package/templates/agents/efcore/db-reset.md +0 -98
  26. package/templates/agents/efcore/db-seed.md +0 -73
  27. package/templates/agents/efcore/db-status.md +0 -97
  28. package/templates/agents/efcore/scan.md +0 -124
  29. package/templates/mcp-scaffolding/frontend/routes.tsx.hbs +0 -126
  30. package/templates/skills/efcore/references/both-contexts.md +0 -32
  31. package/templates/skills/efcore/references/destructive-operations.md +0 -38
  32. package/templates/skills/efcore/steps/db/step-deploy.md +0 -217
  33. package/templates/skills/efcore/steps/db/step-reset.md +0 -186
  34. package/templates/skills/efcore/steps/db/step-seed.md +0 -166
  35. package/templates/skills/efcore/steps/db/step-status.md +0 -173
  36. package/templates/skills/efcore/steps/migration/step-00-init.md +0 -102
  37. package/templates/skills/efcore/steps/migration/step-01-check.md +0 -164
  38. package/templates/skills/efcore/steps/migration/step-02-create.md +0 -160
  39. package/templates/skills/efcore/steps/migration/step-03-validate.md +0 -168
  40. package/templates/skills/efcore/steps/rebase-snapshot/step-00-init.md +0 -173
  41. package/templates/skills/efcore/steps/rebase-snapshot/step-01-backup.md +0 -100
  42. package/templates/skills/efcore/steps/rebase-snapshot/step-02-fetch.md +0 -115
  43. package/templates/skills/efcore/steps/rebase-snapshot/step-03-create.md +0 -112
  44. package/templates/skills/efcore/steps/rebase-snapshot/step-04-validate.md +0 -157
  45. package/templates/skills/efcore/steps/shared/step-00-init.md +0 -131
  46. package/templates/skills/efcore/steps/squash/step-00-init.md +0 -141
  47. package/templates/skills/efcore/steps/squash/step-01-backup.md +0 -120
  48. package/templates/skills/efcore/steps/squash/step-02-fetch.md +0 -168
  49. package/templates/skills/efcore/steps/squash/step-03-create.md +0 -184
  50. package/templates/skills/efcore/steps/squash/step-04-validate.md +0 -174
@@ -26102,7 +26102,7 @@ var init_types3 = __esm({
26102
26102
  includeLayouts: external_exports.boolean().default(true).describe("Generate layout components"),
26103
26103
  includeGuards: external_exports.boolean().default(true).describe("Include route guards for permissions"),
26104
26104
  generateRegistry: external_exports.boolean().default(true).describe("Generate navRoutes.generated.ts"),
26105
- outputFormat: external_exports.enum(["standalone", "applicationRoutes", "clientRoutes", "componentRegistry"]).default("componentRegistry").describe('Output format: "componentRegistry" (v3.7+) generates PageRegistry.register() calls for DynamicRouter. "applicationRoutes" (deprecated) generates RouteObject[] arrays for App.tsx. "standalone" generates createBrowserRouter(). "clientRoutes" is a deprecated alias for "applicationRoutes".'),
26105
+ outputFormat: external_exports.enum(["standalone", "componentRegistry"]).default("componentRegistry").describe('Output format: "componentRegistry" (v3.7+, default) generates PageRegistry.register() calls for DynamicRouter. "standalone" generates createBrowserRouter() for non-SmartStack projects.'),
26106
26106
  dryRun: external_exports.boolean().default(false).describe("Preview without writing files")
26107
26107
  }).optional()
26108
26108
  });
@@ -28009,7 +28009,7 @@ async function validateFrontendRoutes(structure, _config, result) {
28009
28009
  const missingRoutes2 = [];
28010
28010
  for (const nav of seedNavRoutes2) {
28011
28011
  const segments = nav.route.split("/").filter(Boolean);
28012
- const componentKey = segments.join(".");
28012
+ const componentKey = segments.map((s) => s.replace(/([a-z])([A-Z])/g, "$1-$2").toLowerCase()).join(".");
28013
28013
  if (!registeredKeys.has(componentKey)) {
28014
28014
  missingRoutes2.push(nav);
28015
28015
  }
@@ -28039,15 +28039,24 @@ async function validateFrontendRoutes(structure, _config, result) {
28039
28039
  });
28040
28040
  return;
28041
28041
  }
28042
+ const hasDynamicRouter = appContent.includes("DynamicRouter") || appContent.includes("<DynamicRouter");
28043
+ if (hasDynamicRouter) {
28044
+ result.warnings.push({
28045
+ type: "warning",
28046
+ category: "frontend-routes",
28047
+ message: "App.tsx uses DynamicRouter but componentRegistry.generated.ts was not found. Run scaffold_routes to generate it."
28048
+ });
28049
+ return;
28050
+ }
28042
28051
  const hasApplicationRoutes = appContent.includes("applicationRoutes") || appContent.includes("clientRoutes");
28043
28052
  const hasRouteComponents = /<Route\s/.test(appContent);
28044
28053
  if (!hasApplicationRoutes && !hasRouteComponents) {
28045
28054
  result.errors.push({
28046
28055
  type: "error",
28047
28056
  category: "frontend-routes",
28048
- message: "App.tsx has no route definitions (neither applicationRoutes import nor <Route> components)",
28057
+ message: "App.tsx has no route definitions (neither DynamicRouter, applicationRoutes import, nor <Route> components)",
28049
28058
  file: path8.relative(structure.root, appFiles[0]),
28050
- suggestion: "Wire generated routes to App.tsx. Import applicationRouteExtensions from the generated file and render them."
28059
+ suggestion: 'For v3.7+: Use DynamicRouter with componentRegistry.generated.ts (run scaffold_routes outputFormat="componentRegistry"). For legacy: Import route configuration from generated routes file.'
28051
28060
  });
28052
28061
  return;
28053
28062
  }
@@ -57901,9 +57910,6 @@ ensuring frontend routes stay synchronized with backend NavRoute attributes.`,
57901
57910
  function toKebabCase(segment) {
57902
57911
  return segment.replace(/([A-Z]+)([A-Z][a-z])/g, "$1-$2").replace(/([a-z0-9])([A-Z])/g, "$1-$2").toLowerCase();
57903
57912
  }
57904
- function toCamelCase2(segment) {
57905
- return segment.replace(/-([a-z])/g, (_, c) => c.toUpperCase());
57906
- }
57907
57913
  function capitalize(str) {
57908
57914
  return str.charAt(0).toUpperCase() + str.slice(1);
57909
57915
  }
@@ -57975,16 +57981,6 @@ async function scaffoldRoutes(input, config2) {
57975
57981
  result.files.push({ path: registryFile, content: registryContent, type: "created" });
57976
57982
  }
57977
57983
  const outputFormat = options?.outputFormat ?? "componentRegistry";
57978
- if (outputFormat === "clientRoutes") {
57979
- if (!result.warnings) result.warnings = [];
57980
- result.warnings.push('outputFormat "clientRoutes" is DEPRECATED. Use "applicationRoutes" instead (same behavior, correct naming).');
57981
- }
57982
- if (outputFormat === "applicationRoutes") {
57983
- if (!result.warnings) result.warnings = [];
57984
- result.warnings.push(
57985
- 'outputFormat "applicationRoutes" is DEPRECATED (v3.7+). Use "componentRegistry" for PageRegistry + DynamicRouter pattern. See: DynamicRouter replaces manual App.tsx wiring.'
57986
- );
57987
- }
57988
57984
  if (outputFormat === "componentRegistry") {
57989
57985
  const pageFiles = await discoverPageFiles(webPath, navRoutes);
57990
57986
  const registryContent = generateComponentRegistry(navRoutes, pageFiles, webPath);
@@ -58003,121 +57999,9 @@ async function scaffoldRoutes(input, config2) {
58003
57999
  result.instructions.push("1. Ensure `main.tsx` imports `./extensions/componentRegistry.generated`");
58004
58000
  result.instructions.push("2. Ensure navigation seed data has matching `componentKey` values");
58005
58001
  result.instructions.push("3. DynamicRouter resolves routes automatically \u2014 no App.tsx wiring needed");
58006
- }
58007
- if (outputFormat === "applicationRoutes" || outputFormat === "clientRoutes") {
58008
- const pageFiles = await discoverPageFiles(webPath, navRoutes);
58009
- const applicationRoutesContent = generateApplicationRoutesConfig(navRoutes, pageFiles, includeGuards);
58010
- const applicationRoutesFile = path19.join(routesPath, "applicationRoutes.generated.tsx");
58011
- if (!dryRun) {
58012
- await ensureDirectory(routesPath);
58013
- await writeText(applicationRoutesFile, applicationRoutesContent);
58014
- }
58015
- result.files.push({ path: applicationRoutesFile, content: applicationRoutesContent, type: "created" });
58016
- const routeCount = navRoutes.length * 4;
58017
- result.instructions.push(`Generated ${routeCount} routes (4 CRUD per NavRoute) for ${navRoutes.length} NavRoutes in applicationRoutes format`);
58018
- result.instructions.push("");
58019
- result.instructions.push("## App.tsx Wiring Instructions");
58020
- result.instructions.push("");
58021
- result.instructions.push("**Detect App.tsx pattern first**, then follow the matching instructions:");
58022
- result.instructions.push("");
58023
- const routeTree = buildRouteTree(navRoutes);
58024
- result.instructions.push("### Pattern A: mergeRoutes (applicationRoutes pattern)");
58025
- result.instructions.push("");
58026
- result.instructions.push("Routes are FLAT (no nested children) \u2014 compatible with `mergeRoutes()`.");
58027
- result.instructions.push("Each NavRoute generates 4 routes: list, create, detail (:id), edit (:id/edit).");
58028
- result.instructions.push("");
58029
- result.instructions.push("**IMPORTANT:** Pages are lazy-loaded. Use `<Suspense fallback={<PageLoader />}>` wrapper.");
58030
- result.instructions.push("");
58031
- result.instructions.push("```tsx");
58032
- result.instructions.push("import { lazy, Suspense } from 'react';");
58033
- result.instructions.push("import { PageLoader } from '@/components/ui/PageLoader';");
58034
- result.instructions.push("");
58035
- const importedComponents = /* @__PURE__ */ new Set();
58036
- for (const [_app, modules] of Object.entries(routeTree)) {
58037
- for (const [, moduleRoutes] of Object.entries(modules)) {
58038
- for (const route of moduleRoutes) {
58039
- const discovery = pageFiles.get(route.navRoute);
58040
- if (discovery) {
58041
- for (const page of [discovery.list, discovery.create, discovery.detail, discovery.edit]) {
58042
- if (page && !importedComponents.has(page.componentName)) {
58043
- importedComponents.add(page.componentName);
58044
- result.instructions.push(`const ${page.componentName} = lazy(() =>`);
58045
- result.instructions.push(` import('${page.importPath}').then(m => ({ default: m.${page.componentName} }))`);
58046
- result.instructions.push(");");
58047
- }
58048
- }
58049
- } else {
58050
- const { entityPlural, entitySingular, basePath } = deriveEntityNames(route.navRoute);
58051
- for (const comp of [`${entityPlural}Page`, `${entitySingular}CreatePage`, `${entitySingular}DetailPage`, `${entitySingular}EditPage`]) {
58052
- if (!importedComponents.has(comp)) {
58053
- importedComponents.add(comp);
58054
- result.instructions.push(`// TODO: const ${comp} = lazy(() => import('${basePath}/${comp}').then(m => ({ default: m.${comp} })));`);
58055
- }
58056
- }
58057
- }
58058
- }
58059
- }
58060
- }
58061
- result.instructions.push("");
58062
- result.instructions.push("const applicationRoutes = {");
58063
- for (const [app, modules] of Object.entries(routeTree)) {
58064
- result.instructions.push(` '${toKebabCase(app)}': [`);
58065
- for (const [, moduleRoutes] of Object.entries(modules)) {
58066
- for (const route of moduleRoutes) {
58067
- const modulePath = route.navRoute.split(".").slice(1).map(toKebabCase).join("/");
58068
- const discovery = pageFiles.get(route.navRoute);
58069
- const { entityPlural, entitySingular } = deriveEntityNames(route.navRoute);
58070
- const listComp = discovery?.list?.componentName || `${entityPlural}Page`;
58071
- const createComp = discovery?.create?.componentName || `${entitySingular}CreatePage`;
58072
- const detailComp = discovery?.detail?.componentName || `${entitySingular}DetailPage`;
58073
- const editComp = discovery?.edit?.componentName || `${entitySingular}EditPage`;
58074
- result.instructions.push(` // === ${entityPlural.toLowerCase()} (NavRoute: ${route.navRoute}) ===`);
58075
- result.instructions.push(` { path: '${modulePath}', element: <Suspense fallback={<PageLoader />}><${listComp} /></Suspense> },`);
58076
- result.instructions.push(` { path: '${modulePath}/create', element: <Suspense fallback={<PageLoader />}><${createComp} /></Suspense> },`);
58077
- result.instructions.push(` { path: '${modulePath}/:id', element: <Suspense fallback={<PageLoader />}><${detailComp} /></Suspense> },`);
58078
- result.instructions.push(` { path: '${modulePath}/:id/edit', element: <Suspense fallback={<PageLoader />}><${editComp} /></Suspense> },`);
58079
- }
58080
- }
58081
- result.instructions.push(" ],");
58082
- }
58083
- result.instructions.push("};");
58084
- result.instructions.push("```");
58085
- result.instructions.push("");
58086
- result.instructions.push("Routes are automatically injected into BOTH standard and tenant-prefixed trees by `mergeRoutes()`.");
58087
- result.instructions.push("");
58088
- result.instructions.push('### Pattern B: JSX Routes (if App.tsx uses `<Route path="/{application}" element={<{Layout} />}>`)');
58089
- result.instructions.push("");
58090
- result.instructions.push("Insert `<Route>` children INSIDE the appropriate Layout wrapper.");
58091
- result.instructions.push("**IMPORTANT:** Use `<Suspense fallback={<PageLoader />}>` for lazy-loaded pages.");
58092
- result.instructions.push("");
58093
- for (const [app, modules] of Object.entries(routeTree)) {
58094
- const layoutName = getLayoutName(app);
58095
- result.instructions.push(`#### ${capitalize(app)} application (inside \`<Route path="/${toKebabCase(app)}" element={<${layoutName} />}>\`):`);
58096
- result.instructions.push("");
58097
- result.instructions.push("```tsx");
58098
- for (const [, moduleRoutes] of Object.entries(modules)) {
58099
- for (const route of moduleRoutes) {
58100
- const modulePath = route.navRoute.split(".").slice(1).map(toKebabCase).join("/");
58101
- const discovery = pageFiles.get(route.navRoute);
58102
- const { entityPlural, entitySingular } = deriveEntityNames(route.navRoute);
58103
- const listComp = discovery?.list?.componentName || `${entityPlural}Page`;
58104
- const createComp = discovery?.create?.componentName || `${entitySingular}CreatePage`;
58105
- const detailComp = discovery?.detail?.componentName || `${entitySingular}DetailPage`;
58106
- const editComp = discovery?.edit?.componentName || `${entitySingular}EditPage`;
58107
- result.instructions.push(`<Route path="${modulePath}" element={<Suspense fallback={<PageLoader />}><${listComp} /></Suspense>} />`);
58108
- result.instructions.push(`<Route path="${modulePath}/create" element={<Suspense fallback={<PageLoader />}><${createComp} /></Suspense>} />`);
58109
- result.instructions.push(`<Route path="${modulePath}/:id" element={<Suspense fallback={<PageLoader />}><${detailComp} /></Suspense>} />`);
58110
- result.instructions.push(`<Route path="${modulePath}/:id/edit" element={<Suspense fallback={<PageLoader />}><${editComp} /></Suspense>} />`);
58111
- }
58112
- }
58113
- result.instructions.push("```");
58114
- result.instructions.push("");
58115
- result.instructions.push("**IMPORTANT:** Also add the same routes inside the tenant-prefixed block (`/t/:slug/...`)");
58116
- result.instructions.push("");
58117
- }
58118
58002
  } else {
58119
58003
  if (!result.warnings) result.warnings = [];
58120
- result.warnings.push("STANDALONE MODE: createBrowserRouter() is NOT compatible with SmartStack App.tsx which uses useRoutes() + mergeRoutes()");
58004
+ result.warnings.push("STANDALONE MODE: createBrowserRouter() is for non-SmartStack projects. SmartStack uses DynamicRouter + PageRegistry (v3.7+).");
58121
58005
  result.warnings.push("All routes are in TODO commented state \u2014 manual activation required");
58122
58006
  const routerContent = generateRouterConfig(navRoutes, includeGuards);
58123
58007
  const routerFile = path19.join(routesPath, "index.tsx");
@@ -58610,164 +58494,6 @@ async function discoverPageFiles(webPath, routes) {
58610
58494
  }
58611
58495
  return pageMap;
58612
58496
  }
58613
- function generateApplicationRoutesConfig(routes, pageFiles, includeGuards) {
58614
- const routeTree = buildRouteTree(routes);
58615
- const lines = [
58616
- "/**",
58617
- " * Application Routes Configuration",
58618
- " *",
58619
- " * Auto-generated by SmartStack MCP - DO NOT EDIT MANUALLY",
58620
- ' * Run `scaffold_routes` with outputFormat: "applicationRoutes" to regenerate.',
58621
- " *",
58622
- " * These routes are FLAT (not nested children) \u2014 compatible with mergeRoutes().",
58623
- " * Each NavRoute generates up to 4 CRUD routes: list, create, detail, edit.",
58624
- " */",
58625
- "",
58626
- "import type { RouteObject } from 'react-router-dom';",
58627
- "import type { ApplicationRouteExtensions } from '@atlashub/smartstack';",
58628
- "import { lazy, Suspense } from 'react';",
58629
- "import { Navigate } from 'react-router-dom';",
58630
- "import { PageLoader } from '@/components/ui/PageLoader';"
58631
- ];
58632
- if (includeGuards) {
58633
- lines.push("import { ROUTES } from './navRoutes.generated';");
58634
- lines.push("import { PermissionGuard } from './guards';");
58635
- }
58636
- lines.push("");
58637
- const importedComponents = /* @__PURE__ */ new Set();
58638
- for (const route of routes) {
58639
- const discovery = pageFiles.get(route.navRoute);
58640
- if (discovery) {
58641
- for (const page of [discovery.list, discovery.create, discovery.detail, discovery.edit]) {
58642
- if (page && !importedComponents.has(page.componentName)) {
58643
- importedComponents.add(page.componentName);
58644
- lines.push(`const ${page.componentName} = lazy(() =>`);
58645
- lines.push(` import('${page.importPath}').then(m => ({ default: m.${page.componentName} }))`);
58646
- lines.push(");");
58647
- }
58648
- }
58649
- }
58650
- }
58651
- for (const route of routes) {
58652
- const discovery = pageFiles.get(route.navRoute);
58653
- const { entityPlural, entitySingular, basePath } = deriveEntityNames(route.navRoute);
58654
- if (!discovery?.list && !importedComponents.has(`${entityPlural}Page`)) {
58655
- importedComponents.add(`${entityPlural}Page`);
58656
- lines.push(`const ${entityPlural}Page = lazy(() =>`);
58657
- lines.push(` import('${basePath}/${entityPlural}Page').then(m => ({ default: m.${entityPlural}Page }))`);
58658
- lines.push(");");
58659
- }
58660
- if (!discovery?.create && !importedComponents.has(`${entitySingular}CreatePage`)) {
58661
- importedComponents.add(`${entitySingular}CreatePage`);
58662
- lines.push(`const ${entitySingular}CreatePage = lazy(() =>`);
58663
- lines.push(` import('${basePath}/${entitySingular}CreatePage').then(m => ({ default: m.${entitySingular}CreatePage }))`);
58664
- lines.push(");");
58665
- }
58666
- if (!discovery?.detail && !importedComponents.has(`${entitySingular}DetailPage`)) {
58667
- importedComponents.add(`${entitySingular}DetailPage`);
58668
- lines.push(`const ${entitySingular}DetailPage = lazy(() =>`);
58669
- lines.push(` import('${basePath}/${entitySingular}DetailPage').then(m => ({ default: m.${entitySingular}DetailPage }))`);
58670
- lines.push(");");
58671
- }
58672
- if (!discovery?.edit && !importedComponents.has(`${entitySingular}EditPage`)) {
58673
- importedComponents.add(`${entitySingular}EditPage`);
58674
- lines.push(`const ${entitySingular}EditPage = lazy(() =>`);
58675
- lines.push(` import('${basePath}/${entitySingular}EditPage').then(m => ({ default: m.${entitySingular}EditPage }))`);
58676
- lines.push(");");
58677
- }
58678
- }
58679
- lines.push("");
58680
- for (const [app, modules] of Object.entries(routeTree)) {
58681
- const appCamel = toCamelCase2(app);
58682
- const appKebab = toKebabCase(app);
58683
- const appUpper = capitalize(appCamel);
58684
- const layoutName = getLayoutName(app);
58685
- lines.push("/**");
58686
- lines.push(` * Routes for ${appUpper} application`);
58687
- lines.push(` * Flat routes for mergeRoutes() \u2014 children of <Route path="/${appKebab}" element={<${layoutName} />}>`);
58688
- lines.push(" */");
58689
- lines.push(`export const ${appCamel}Routes: RouteObject[] = [`);
58690
- for (const [, moduleRoutes] of Object.entries(modules)) {
58691
- for (const route of moduleRoutes) {
58692
- const modulePath = route.navRoute.split(".").slice(1).map(toKebabCase).join("/");
58693
- const discovery = pageFiles.get(route.navRoute);
58694
- const { entityPlural, entitySingular } = deriveEntityNames(route.navRoute);
58695
- const permGuardOpen = includeGuards && route.permissions.length > 0 ? `<PermissionGuard permissions={ROUTES['${route.navRoute}'].permissions}>` : "";
58696
- const permGuardClose = permGuardOpen ? "</PermissionGuard>" : "";
58697
- lines.push(` // === ${entityPlural.toLowerCase()} (NavRoute: ${route.navRoute}) ===`);
58698
- const listComp = discovery?.list?.componentName || `${entityPlural}Page`;
58699
- const listElement = `<Suspense fallback={<PageLoader />}>${permGuardOpen}<${listComp} />${permGuardClose}</Suspense>`;
58700
- lines.push(` { path: '${modulePath}', element: ${listElement} },`);
58701
- const createComp = discovery?.create?.componentName || `${entitySingular}CreatePage`;
58702
- const createElement = `<Suspense fallback={<PageLoader />}>${permGuardOpen}<${createComp} />${permGuardClose}</Suspense>`;
58703
- lines.push(` { path: '${modulePath}/create', element: ${createElement} },`);
58704
- const detailComp = discovery?.detail?.componentName || `${entitySingular}DetailPage`;
58705
- const detailElement = `<Suspense fallback={<PageLoader />}>${permGuardOpen}<${detailComp} />${permGuardClose}</Suspense>`;
58706
- lines.push(` { path: '${modulePath}/:id', element: ${detailElement} },`);
58707
- const editComp = discovery?.edit?.componentName || `${entitySingular}EditPage`;
58708
- const editElement = `<Suspense fallback={<PageLoader />}>${permGuardOpen}<${editComp} />${permGuardClose}</Suspense>`;
58709
- lines.push(` { path: '${modulePath}/:id/edit', element: ${editElement} },`);
58710
- }
58711
- }
58712
- lines.push("");
58713
- lines.push(" // --- Parent redirect routes ---");
58714
- let appFirstPath;
58715
- const moduleRedirects = /* @__PURE__ */ new Map();
58716
- for (const [moduleName, modRoutes] of Object.entries(modules)) {
58717
- if (modRoutes.length > 0) {
58718
- const firstRoute = modRoutes[0];
58719
- const segments = firstRoute.navRoute.split(".").slice(1).map(toKebabCase);
58720
- const firstFullPath = segments.join("/");
58721
- const moduleKebab = toKebabCase(moduleName);
58722
- if (!appFirstPath) {
58723
- appFirstPath = firstFullPath;
58724
- }
58725
- if (segments.length > 1 && !moduleRedirects.has(moduleKebab)) {
58726
- moduleRedirects.set(moduleKebab, firstFullPath);
58727
- }
58728
- }
58729
- }
58730
- for (const [modulePath, targetPath] of moduleRedirects) {
58731
- lines.push(` { path: '${modulePath}', element: <Navigate to="${targetPath}" replace /> },`);
58732
- }
58733
- if (appFirstPath) {
58734
- lines.push(` { path: '', element: <Navigate to="${appFirstPath}" replace /> },`);
58735
- }
58736
- lines.push("];");
58737
- lines.push("");
58738
- }
58739
- const appKeys = Object.keys(routeTree);
58740
- lines.push("/** All generated routes grouped by application */");
58741
- lines.push("export const generatedRoutes: Record<string, RouteObject[]> = {");
58742
- for (const app of appKeys) {
58743
- const appCamel = toCamelCase2(app);
58744
- const appKebab = toKebabCase(app);
58745
- lines.push(` '${appKebab}': ${appCamel}Routes,`);
58746
- }
58747
- lines.push("};");
58748
- lines.push("");
58749
- lines.push("/**");
58750
- lines.push(" * Application route extensions for mergeRoutes() pattern.");
58751
- lines.push(" * Import and spread into your App.tsx applicationRoutes:");
58752
- lines.push(" *");
58753
- lines.push(" * ```tsx");
58754
- lines.push(" * import { applicationRouteExtensions } from './routes/applicationRoutes.generated';");
58755
- lines.push(" * const applicationRoutes = {");
58756
- lines.push(" * ...applicationRouteExtensions,");
58757
- lines.push(" * // your additional routes...");
58758
- lines.push(" * };");
58759
- lines.push(" * ```");
58760
- lines.push(" */");
58761
- lines.push("export const applicationRouteExtensions: Record<string, RouteObject[]> = {");
58762
- for (const app of appKeys) {
58763
- const appCamel = toCamelCase2(app);
58764
- const appKebab = toKebabCase(app);
58765
- lines.push(` '${appKebab}': ${appCamel}Routes,`);
58766
- }
58767
- lines.push("};");
58768
- lines.push("");
58769
- return lines.join("\n");
58770
- }
58771
58497
  function generateComponentRegistry(navRoutes, pageFiles, _webPath) {
58772
58498
  const lines = [];
58773
58499
  lines.push("/**");
@@ -58803,9 +58529,6 @@ function generateComponentRegistry(navRoutes, pageFiles, _webPath) {
58803
58529
  }
58804
58530
  return lines.join("\n");
58805
58531
  }
58806
- function getLayoutName(_application) {
58807
- return "AppLayout";
58808
- }
58809
58532
  function deriveEntityNames(navRoute) {
58810
58533
  const parts = navRoute.split(".");
58811
58534
  const lastSegment = parts[parts.length - 1];
@@ -58874,12 +58597,16 @@ var init_scaffold_routes = __esm({
58874
58597
  init_navroute_parser();
58875
58598
  scaffoldRoutesTool = {
58876
58599
  name: "scaffold_routes",
58877
- description: `Generate React Router configuration from backend NavRoute attributes.
58600
+ description: `Generate frontend routing infrastructure from backend NavRoute attributes.
58878
58601
 
58879
- Creates:
58602
+ Creates (v3.7+ DynamicRouter, default):
58603
+ - componentRegistry.generated.ts: PageRegistry.register() calls with lazy imports for DynamicRouter
58880
58604
  - navRoutes.generated.ts: Registry of all routes with API paths and permissions
58881
- - routes.tsx: React Router configuration with nested routes
58882
- - Layout components (optional)
58605
+
58606
+ Creates (standalone, for non-SmartStack projects):
58607
+ - routes/index.tsx: createBrowserRouter() configuration with nested routes
58608
+ - navRoutes.generated.ts: Registry of all routes
58609
+ - Layout components and route guards (optional)
58883
58610
 
58884
58611
  Example:
58885
58612
  scaffold_routes source="controllers" scope="all"
@@ -58911,7 +58638,7 @@ and generates corresponding frontend routing infrastructure.`,
58911
58638
  includeLayouts: { type: "boolean", default: true },
58912
58639
  includeGuards: { type: "boolean", default: true },
58913
58640
  generateRegistry: { type: "boolean", default: true },
58914
- outputFormat: { type: "string", enum: ["standalone", "applicationRoutes", "clientRoutes", "componentRegistry"], default: "componentRegistry", description: "componentRegistry (v3.7+): PageRegistry.register() calls for DynamicRouter. applicationRoutes (deprecated): RouteObject[] arrays for App.tsx. standalone: createBrowserRouter(). clientRoutes: deprecated alias for applicationRoutes." },
58641
+ outputFormat: { type: "string", enum: ["standalone", "componentRegistry"], default: "componentRegistry", description: "componentRegistry (v3.7+, default): PageRegistry.register() calls for DynamicRouter. standalone: createBrowserRouter() for non-SmartStack projects." },
58915
58642
  dryRun: { type: "boolean", default: false }
58916
58643
  }
58917
58644
  }
@@ -59149,7 +58876,7 @@ async function validateApiClients(webPath, backendRoutes, result) {
59149
58876
  }
59150
58877
  }
59151
58878
  async function validateRoutes(webPath, backendRoutes, result) {
59152
- const routeCandidates = ["applicationRoutes.generated.tsx", "clientRoutes.generated.tsx", "index.tsx"];
58879
+ const routeCandidates = ["index.tsx"];
59153
58880
  let routesContent = "";
59154
58881
  for (const candidate of routeCandidates) {
59155
58882
  const candidatePath = path20.join(webPath, "src", "routes", candidate);
@@ -59246,10 +58973,9 @@ async function validateAppWiring(webPath, backendRoutes, result) {
59246
58973
  );
59247
58974
  return;
59248
58975
  }
59249
- const hasApplicationRoutesImport = appContent.includes("applicationRoutes.generated") || appContent.includes("clientRoutes.generated");
59250
58976
  const hasRoutesImport = appContent.includes("from './routes") || appContent.includes("from '../routes");
59251
58977
  const hasInlineRoutes = appContent.includes("<Route ");
59252
- result.appWiring.routesImported = hasApplicationRoutesImport || hasRoutesImport || hasInlineRoutes;
58978
+ result.appWiring.routesImported = hasRoutesImport || hasInlineRoutes;
59253
58979
  const hasDynamicRouter = appContent.includes("DynamicRouter") || appContent.includes("<DynamicRouter");
59254
58980
  const hasComponentRegistry = appContent.includes("componentRegistry.generated");
59255
58981
  if (hasDynamicRouter || hasComponentRegistry) {
@@ -59291,7 +59017,7 @@ async function validateAppWiring(webPath, backendRoutes, result) {
59291
59017
  }
59292
59018
  }
59293
59019
  async function validateCrudRoutes(webPath, backendRoutes, result) {
59294
- const routeCandidates = ["applicationRoutes.generated.tsx", "clientRoutes.generated.tsx"];
59020
+ const routeCandidates = ["index.tsx"];
59295
59021
  let routesContent = "";
59296
59022
  for (const candidate of routeCandidates) {
59297
59023
  const candidatePath = path20.join(webPath, "src", "routes", candidate);
@@ -59538,11 +59264,17 @@ var init_validate_frontend_routes = __esm({
59538
59264
  name: "validate_frontend_routes",
59539
59265
  description: `Validate frontend routes against backend NavRoute attributes.
59540
59266
 
59541
- Checks:
59267
+ Checks (v3.7+ DynamicRouter pattern):
59268
+ - componentRegistry.generated.ts exists with all PageRegistry.register() entries
59269
+ - main.tsx imports componentRegistry.generated
59270
+ - Backend NavRoutes have matching component keys
59271
+ - API clients use getRoute() instead of hardcoded paths
59272
+ - No hardcoded navigate()/Link paths in page components
59273
+
59274
+ Checks (legacy pattern):
59542
59275
  - navRoutes.generated.ts exists and is up-to-date
59543
- - API clients use correct NavRoute paths
59544
59276
  - React Router configuration matches backend routes
59545
- - Permission configurations are synchronized
59277
+ - App.tsx wiring is complete
59546
59278
 
59547
59279
  Example:
59548
59280
  validate_frontend_routes scope="all"