@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.
- package/dist/index.js +28 -32
- package/dist/index.js.map +1 -1
- package/dist/mcp-entry.mjs +35 -303
- package/dist/mcp-entry.mjs.map +1 -1
- package/package.json +1 -1
- package/templates/skills/apex/references/checks/seed-checks.sh +1 -1
- package/templates/skills/apex/references/core-seed-data.md +39 -21
- package/templates/skills/application/references/application-roles-template.md +14 -8
- package/templates/skills/application/references/provider-template.md +32 -20
- package/templates/skills/application/templates-frontend.md +350 -89
- package/templates/skills/application/templates-seed.md +23 -11
- package/templates/skills/audit-route/SKILL.md +107 -0
- package/templates/skills/audit-route/references/routing-pattern.md +129 -0
- package/templates/skills/audit-route/steps/step-00-init.md +128 -0
- package/templates/skills/audit-route/steps/step-01-inventory.md +157 -0
- package/templates/skills/audit-route/steps/step-02-conformity.md +193 -0
- package/templates/skills/audit-route/steps/step-03-report.md +201 -0
- package/templates/skills/cli-app-sync/SKILL.md +2 -2
- package/templates/skills/cli-app-sync/references/comparison-map.md +1 -1
- package/templates/skills/dev-start/SKILL.md +12 -2
- package/templates/skills/documentation/steps/step-03-validate.md +12 -14
- package/templates/skills/efcore/SKILL.md +219 -67
- package/templates/agents/efcore/conflicts.md +0 -114
- package/templates/agents/efcore/db-deploy.md +0 -86
- package/templates/agents/efcore/db-reset.md +0 -98
- package/templates/agents/efcore/db-seed.md +0 -73
- package/templates/agents/efcore/db-status.md +0 -97
- package/templates/agents/efcore/scan.md +0 -124
- package/templates/mcp-scaffolding/frontend/routes.tsx.hbs +0 -126
- package/templates/skills/efcore/references/both-contexts.md +0 -32
- package/templates/skills/efcore/references/destructive-operations.md +0 -38
- package/templates/skills/efcore/steps/db/step-deploy.md +0 -217
- package/templates/skills/efcore/steps/db/step-reset.md +0 -186
- package/templates/skills/efcore/steps/db/step-seed.md +0 -166
- package/templates/skills/efcore/steps/db/step-status.md +0 -173
- package/templates/skills/efcore/steps/migration/step-00-init.md +0 -102
- package/templates/skills/efcore/steps/migration/step-01-check.md +0 -164
- package/templates/skills/efcore/steps/migration/step-02-create.md +0 -160
- package/templates/skills/efcore/steps/migration/step-03-validate.md +0 -168
- package/templates/skills/efcore/steps/rebase-snapshot/step-00-init.md +0 -173
- package/templates/skills/efcore/steps/rebase-snapshot/step-01-backup.md +0 -100
- package/templates/skills/efcore/steps/rebase-snapshot/step-02-fetch.md +0 -115
- package/templates/skills/efcore/steps/rebase-snapshot/step-03-create.md +0 -112
- package/templates/skills/efcore/steps/rebase-snapshot/step-04-validate.md +0 -157
- package/templates/skills/efcore/steps/shared/step-00-init.md +0 -131
- package/templates/skills/efcore/steps/squash/step-00-init.md +0 -141
- package/templates/skills/efcore/steps/squash/step-01-backup.md +0 -120
- package/templates/skills/efcore/steps/squash/step-02-fetch.md +0 -168
- package/templates/skills/efcore/steps/squash/step-03-create.md +0 -184
- package/templates/skills/efcore/steps/squash/step-04-validate.md +0 -174
package/dist/mcp-entry.mjs
CHANGED
|
@@ -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", "
|
|
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:
|
|
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
|
|
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
|
|
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
|
-
|
|
58882
|
-
|
|
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", "
|
|
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 = ["
|
|
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 =
|
|
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 = ["
|
|
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
|
-
-
|
|
59277
|
+
- App.tsx wiring is complete
|
|
59546
59278
|
|
|
59547
59279
|
Example:
|
|
59548
59280
|
validate_frontend_routes scope="all"
|