@atlashub/smartstack-cli 3.37.0 → 3.39.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 +16 -24
- package/dist/index.js.map +1 -1
- package/dist/mcp-entry.mjs +235 -265
- package/dist/mcp-entry.mjs.map +1 -1
- package/package.json +1 -1
- package/scripts/extract-api-endpoints.ts +5 -5
- package/scripts/generate-doc-with-mock-ui.ts +10 -17
- package/templates/agents/ba-reader.md +9 -9
- package/templates/agents/ba-writer.md +12 -15
- package/templates/agents/code-reviewer.md +1 -1
- package/templates/agents/docs-context-reader.md +1 -1
- package/templates/agents/efcore/scan.md +3 -1
- package/templates/agents/gitflow/commit.md +74 -0
- package/templates/agents/gitflow/finish.md +5 -2
- package/templates/agents/gitflow/init-clone.md +3 -3
- package/templates/agents/gitflow/init-validate.md +3 -2
- package/templates/agents/gitflow/merge.md +5 -4
- package/templates/agents/gitflow/pr.md +5 -4
- package/templates/agents/gitflow/start.md +37 -5
- package/templates/hooks/hooks.json +11 -0
- package/templates/hooks/wsl-dotnet-cleanup.sh +24 -0
- package/templates/mcp-scaffolding/frontend/nav-routes.ts.hbs +20 -20
- package/templates/mcp-scaffolding/frontend/routes.tsx.hbs +16 -24
- package/templates/mcp-scaffolding/migrations/seed-roles.cs.hbs +2 -2
- package/templates/skills/_resources/mcp-validate-documentation-spec.md +3 -3
- package/templates/skills/_shared.md +15 -17
- package/templates/skills/ai-prompt/SKILL.md +1 -1
- package/templates/skills/ai-prompt/steps/step-00-init.md +47 -0
- package/templates/skills/apex/SKILL.md +3 -4
- package/templates/skills/apex/_shared.md +10 -20
- package/templates/skills/apex/references/analysis-methods.md +141 -0
- package/templates/skills/apex/references/challenge-questions.md +1 -21
- package/templates/skills/apex/references/core-seed-data.md +35 -58
- package/templates/skills/apex/references/examine-build-validation.md +82 -0
- package/templates/skills/apex/references/execution-frontend-gates.md +177 -0
- package/templates/skills/apex/references/execution-frontend-patterns.md +105 -0
- package/templates/skills/apex/references/execution-layer1-rules.md +96 -0
- package/templates/skills/apex/references/initialization-challenge-flow.md +110 -0
- package/templates/skills/apex/references/planning-layer-mapping.md +151 -0
- package/templates/skills/apex/references/post-checks.md +145 -40
- package/templates/skills/apex/references/smartstack-api.md +35 -51
- package/templates/skills/apex/references/smartstack-frontend.md +18 -18
- package/templates/skills/apex/references/smartstack-layers.md +38 -62
- package/templates/skills/apex/steps/step-00-init.md +14 -26
- package/templates/skills/apex/steps/step-01-analyze.md +10 -143
- package/templates/skills/apex/steps/step-02-plan.md +10 -92
- package/templates/skills/apex/steps/step-03-execute.md +45 -252
- package/templates/skills/apex/steps/step-04-examine.md +14 -78
- package/templates/skills/apex/steps/step-05-deep-review.md +2 -2
- package/templates/skills/apex/steps/step-08-run-tests.md +1 -0
- package/templates/skills/application/SKILL.md +241 -242
- package/templates/skills/application/references/backend-controller-hierarchy.md +16 -16
- package/templates/skills/application/references/backend-seeding-and-dto-output.md +83 -0
- package/templates/skills/application/references/backend-table-prefix-mapping.md +79 -0
- package/templates/skills/application/references/backend-verification.md +1 -1
- package/templates/skills/application/references/frontend-i18n-and-output.md +67 -0
- package/templates/skills/application/references/frontend-route-naming.md +117 -0
- package/templates/skills/application/references/frontend-route-wiring-app-tsx.md +107 -0
- package/templates/skills/application/references/frontend-verification.md +12 -12
- package/templates/skills/application/references/init-parameter-detection.md +121 -0
- package/templates/skills/application/references/migration-checklist-troubleshooting.md +100 -0
- package/templates/skills/application/references/nav-fallback-procedure.md +199 -200
- package/templates/skills/application/references/provider-template.md +2 -6
- package/templates/skills/application/references/roles-client-project-handling.md +55 -0
- package/templates/skills/application/references/roles-fallback-procedure.md +149 -0
- package/templates/skills/application/references/test-coverage-requirements.md +213 -0
- package/templates/skills/application/references/test-frontend.md +3 -3
- package/templates/skills/application/steps/step-00-init.md +130 -260
- package/templates/skills/application/steps/step-01-navigation.md +170 -170
- package/templates/skills/application/steps/step-02-permissions.md +196 -196
- package/templates/skills/application/steps/step-03-roles.md +182 -339
- package/templates/skills/application/steps/step-03b-provider.md +133 -134
- package/templates/skills/application/steps/step-04-backend.md +174 -265
- package/templates/skills/application/steps/step-05-frontend.md +18 -144
- package/templates/skills/application/steps/step-06-migration.md +12 -60
- package/templates/skills/application/steps/step-07-tests.md +9 -76
- package/templates/skills/application/templates-backend.md +29 -27
- package/templates/skills/application/templates-frontend.md +49 -49
- package/templates/skills/application/templates-seed.md +57 -131
- package/templates/skills/business-analyse/SKILL.md +27 -30
- package/templates/skills/business-analyse/_architecture.md +6 -6
- package/templates/skills/business-analyse/_shared.md +60 -88
- package/templates/skills/business-analyse/questionnaire/04-data.md +3 -3
- package/templates/skills/business-analyse/questionnaire/06-security.md +1 -1
- package/templates/skills/business-analyse/questionnaire/13-cross-module.md +1 -1
- package/templates/skills/business-analyse/react/application-viewer.md +12 -12
- package/templates/skills/business-analyse/react/components.md +8 -12
- package/templates/skills/business-analyse/react/schema.md +836 -836
- package/templates/skills/business-analyse/references/agent-module-prompt.md +2 -3
- package/templates/skills/business-analyse/references/analysis-semantic-checks.md +190 -0
- package/templates/skills/business-analyse/references/cache-warming-strategy.md +2 -2
- package/templates/skills/business-analyse/references/cadrage-challenge-patterns.md +41 -0
- package/templates/skills/business-analyse/references/cadrage-coverage-matrix.md +74 -0
- package/templates/skills/business-analyse/references/cadrage-shared-modules.md +69 -0
- package/templates/skills/business-analyse/references/cadrage-structure-cards.md +1 -1
- package/templates/skills/business-analyse/references/compilation-structure-cards.md +297 -0
- package/templates/skills/business-analyse/references/consolidation-structural-checks.md +2 -2
- package/templates/skills/business-analyse/references/deploy-modes.md +5 -5
- package/templates/skills/business-analyse/references/detection-strategies.md +7 -7
- package/templates/skills/business-analyse/references/handoff-file-templates.md +14 -22
- package/templates/skills/business-analyse/references/handoff-mappings.md +4 -4
- package/templates/skills/business-analyse/references/handoff-seeddata-generation.md +312 -0
- package/templates/skills/business-analyse/references/init-schema-deployment.md +3 -3
- package/templates/skills/business-analyse/references/naming-conventions.md +22 -24
- package/templates/skills/business-analyse/references/prd-generation.md +2 -2
- package/templates/skills/business-analyse/references/review-data-mapping.md +2 -2
- package/templates/skills/business-analyse/references/robustness-checks.md +1 -1
- package/templates/skills/business-analyse/references/spec-auto-inference.md +3 -3
- package/templates/skills/business-analyse/references/team-orchestration.md +49 -6
- package/templates/skills/business-analyse/references/ui-dashboard-spec.md +1 -1
- package/templates/skills/business-analyse/references/ui-resource-cards.md +18 -18
- package/templates/skills/business-analyse/references/validate-incremental-html.md +2 -2
- package/templates/skills/business-analyse/references/validation-checklist.md +2 -2
- package/templates/skills/business-analyse/schemas/application-schema.json +4 -5
- package/templates/skills/business-analyse/schemas/project-schema.json +1 -6
- package/templates/skills/business-analyse/schemas/sections/metadata-schema.json +2 -3
- package/templates/skills/business-analyse/schemas/sections/specification-schema.json +4 -4
- package/templates/skills/business-analyse/steps/step-00-init.md +8 -17
- package/templates/skills/business-analyse/steps/step-01-cadrage.md +35 -198
- package/templates/skills/business-analyse/steps/step-01b-applications.md +16 -20
- package/templates/skills/business-analyse/steps/step-02-decomposition.md +1 -1
- package/templates/skills/business-analyse/steps/step-03a1-setup.md +4 -4
- package/templates/skills/business-analyse/steps/step-03a2-analysis.md +1 -1
- package/templates/skills/business-analyse/steps/step-03b-ui.md +4 -4
- package/templates/skills/business-analyse/steps/step-03c-compile.md +66 -140
- package/templates/skills/business-analyse/steps/step-03d-validate.md +2 -2
- package/templates/skills/business-analyse/steps/step-04a-collect.md +2 -2
- package/templates/skills/business-analyse/steps/step-04b-analyze.md +42 -160
- package/templates/skills/business-analyse/steps/step-04c-decide.md +1 -1
- package/templates/skills/business-analyse/steps/step-05a-handoff.md +74 -104
- package/templates/skills/business-analyse/steps/step-05b-deploy.md +13 -11
- package/templates/skills/business-analyse/steps/step-06-review.md +3 -3
- package/templates/skills/business-analyse/templates/tpl-frd.md +13 -13
- package/templates/skills/business-analyse/templates/tpl-handoff.md +12 -12
- package/templates/skills/business-analyse/templates/tpl-progress.md +1 -1
- package/templates/skills/business-analyse/templates-frd.md +25 -25
- package/templates/skills/business-analyse/templates-react.md +15 -21
- package/templates/skills/controller/SKILL.md +1 -1
- package/templates/skills/controller/postman-templates.md +1 -1
- package/templates/skills/controller/references/controller-code-templates.md +2 -2
- package/templates/skills/controller/references/mcp-scaffold-workflow.md +209 -0
- package/templates/skills/controller/references/permission-sync-templates.md +13 -16
- package/templates/skills/controller/steps/step-00-init.md +11 -11
- package/templates/skills/controller/steps/step-03-generate.md +64 -103
- package/templates/skills/controller/templates.md +67 -71
- package/templates/skills/debug/SKILL.md +13 -218
- package/templates/skills/debug/steps/step-00-init.md +57 -0
- package/templates/skills/debug/steps/step-01-analyze.md +219 -0
- package/templates/skills/debug/steps/step-02-resolve.md +85 -0
- package/templates/skills/documentation/SKILL.md +49 -345
- package/templates/skills/documentation/data-schema.md +11 -8
- package/templates/skills/documentation/steps/step-00-init.md +70 -0
- package/templates/skills/documentation/steps/step-01-scan.md +113 -0
- package/templates/skills/documentation/steps/step-02-generate.md +231 -0
- package/templates/skills/documentation/steps/step-03-validate.md +238 -0
- package/templates/skills/documentation/templates.md +480 -322
- package/templates/skills/efcore/SKILL.md +1 -1
- package/templates/skills/efcore/references/both-contexts.md +32 -0
- package/templates/skills/efcore/references/database-operations.md +67 -0
- package/templates/skills/efcore/references/destructive-operations.md +38 -0
- package/templates/skills/efcore/references/reset-operations.md +81 -0
- package/templates/skills/efcore/references/seed-methods.md +86 -0
- package/templates/skills/efcore/references/shared-init-functions.md +250 -0
- package/templates/skills/efcore/references/sql-objects-injection.md +61 -0
- package/templates/skills/efcore/references/troubleshooting.md +81 -0
- package/templates/skills/efcore/steps/db/step-deploy.md +1 -32
- package/templates/skills/efcore/steps/db/step-reset.md +7 -103
- package/templates/skills/efcore/steps/db/step-seed.md +10 -132
- package/templates/skills/efcore/steps/db/step-status.md +5 -44
- package/templates/skills/efcore/steps/migration/step-02-create.md +1 -14
- package/templates/skills/efcore/steps/migration/step-03-validate.md +8 -62
- package/templates/skills/efcore/steps/rebase-snapshot/step-03-create.md +1 -57
- package/templates/skills/efcore/steps/shared/step-00-init.md +11 -254
- package/templates/skills/efcore/steps/squash/step-03-create.md +1 -58
- package/templates/skills/feature-full/SKILL.md +1 -1
- package/templates/skills/feature-full/steps/step-00-init.md +57 -0
- package/templates/skills/feature-full/steps/step-01-implementation.md +1 -1
- package/templates/skills/gitflow/SKILL.md +28 -5
- package/templates/skills/gitflow/_shared.md +109 -12
- package/templates/skills/gitflow/phases/abort.md +4 -0
- package/templates/skills/gitflow/phases/cleanup.md +4 -0
- package/templates/skills/gitflow/references/commit-message-generation.md +58 -0
- package/templates/skills/gitflow/references/commit-migration-validation.md +49 -0
- package/templates/skills/gitflow/references/finish-cleanup.md +55 -0
- package/templates/skills/gitflow/references/finish-version-bumping.md +45 -0
- package/templates/skills/gitflow/references/init-environment-detection.md +41 -0
- package/templates/skills/gitflow/references/init-questions.md +185 -0
- package/templates/skills/gitflow/references/init-structure-creation.md +75 -0
- package/templates/skills/gitflow/references/init-version-detection.md +21 -0
- package/templates/skills/gitflow/references/init-workspace-detection.md +43 -0
- package/templates/skills/gitflow/references/merge-ci-status.md +36 -0
- package/templates/skills/gitflow/references/merge-execution.md +62 -0
- package/templates/skills/gitflow/references/merge-pr-context.md +76 -0
- package/templates/skills/gitflow/references/pr-build-checks.md +60 -0
- package/templates/skills/gitflow/references/pr-generation.md +58 -0
- package/templates/skills/gitflow/references/start-branch-normalization.md +28 -0
- package/templates/skills/gitflow/references/start-worktree-creation.md +50 -0
- package/templates/skills/gitflow/references/sync-push-verify.md +44 -0
- package/templates/skills/gitflow/references/sync-rebase-conflicts.md +38 -0
- package/templates/skills/gitflow/steps/step-commit.md +12 -91
- package/templates/skills/gitflow/steps/step-finish.md +15 -159
- package/templates/skills/gitflow/steps/step-init.md +24 -326
- package/templates/skills/gitflow/steps/step-merge.md +17 -176
- package/templates/skills/gitflow/steps/step-pr.md +10 -116
- package/templates/skills/gitflow/steps/step-start.md +16 -109
- package/templates/skills/gitflow/steps/step-sync.md +6 -69
- package/templates/skills/ralph-loop/SKILL.md +6 -0
- package/templates/skills/ralph-loop/references/category-completeness.md +185 -0
- package/templates/skills/ralph-loop/references/compact-loop.md +1 -1
- package/templates/skills/ralph-loop/references/init-resume-recovery.md +127 -0
- package/templates/skills/ralph-loop/references/module-transition.md +151 -0
- package/templates/skills/ralph-loop/references/multi-module-queue.md +171 -0
- package/templates/skills/ralph-loop/references/parallel-execution.md +246 -0
- package/templates/skills/ralph-loop/references/task-transform-legacy.md +6 -9
- package/templates/skills/ralph-loop/references/team-orchestration.md +45 -3
- package/templates/skills/ralph-loop/steps/step-00-init.md +36 -109
- package/templates/skills/ralph-loop/steps/step-01-task.md +15 -163
- package/templates/skills/ralph-loop/steps/step-02-execute.md +8 -154
- package/templates/skills/ralph-loop/steps/step-04-check.md +20 -73
- package/templates/skills/review-code/references/owasp-api-top10.md +5 -5
- package/templates/skills/review-code/references/smartstack-conventions.md +568 -568
- package/templates/skills/validate-feature/references/api-smoke-tests.md +140 -0
- package/templates/skills/validate-feature/references/db-validation-checks.md +180 -0
- package/templates/skills/validate-feature/steps/step-01-compile.md +1 -3
- package/templates/skills/validate-feature/steps/step-04-api-smoke.md +34 -145
- package/templates/skills/validate-feature/steps/step-05-db-validation.md +74 -260
- package/templates/skills/workflow/SKILL.md +1 -1
- package/templates/skills/workflow/steps/step-00-init.md +57 -0
package/dist/mcp-entry.mjs
CHANGED
|
@@ -25916,7 +25916,7 @@ var init_types3 = __esm({
|
|
|
25916
25916
|
withValidation: external_exports.boolean().optional().describe("For feature type: generate FluentValidation validators"),
|
|
25917
25917
|
withRepository: external_exports.boolean().optional().describe("For feature type: generate repository pattern"),
|
|
25918
25918
|
entityProperties: external_exports.array(EntityPropertySchema).optional().describe("Entity properties for DTO/Validator generation"),
|
|
25919
|
-
navRoute: external_exports.string().optional().describe('Navigation route path for controller (e.g., "
|
|
25919
|
+
navRoute: external_exports.string().optional().describe('Navigation route path for controller (e.g., "administration.users"). Required for controllers.'),
|
|
25920
25920
|
navRouteSuffix: external_exports.string().optional().describe('Optional suffix for NavRoute (e.g., "dashboard" for sub-resources)'),
|
|
25921
25921
|
withHierarchyFunction: external_exports.boolean().optional().describe("For entity type with self-reference (ParentId): generate TVF SQL script for hierarchy traversal"),
|
|
25922
25922
|
hierarchyDirection: external_exports.enum(["ancestors", "descendants", "both"]).optional().describe("Direction for hierarchy traversal function (default: both)")
|
|
@@ -25933,7 +25933,7 @@ var init_types3 = __esm({
|
|
|
25933
25933
|
version: external_exports.string().optional().describe('Semver version (e.g., "1.0.0", "1.2.0"). If not provided, uses latest from existing migrations.')
|
|
25934
25934
|
});
|
|
25935
25935
|
GeneratePermissionsInputSchema = external_exports.object({
|
|
25936
|
-
navRoute: external_exports.string().optional().describe('NavRoute path (e.g., "
|
|
25936
|
+
navRoute: external_exports.string().optional().describe('NavRoute path (e.g., "administration.entra"). If not provided, scans all controllers.'),
|
|
25937
25937
|
actions: external_exports.array(external_exports.string()).optional().describe("Custom actions to generate (default: read, create, update, delete)"),
|
|
25938
25938
|
includeStandardActions: external_exports.boolean().default(true).describe("Include standard CRUD actions (read, create, update, delete)"),
|
|
25939
25939
|
generateMigration: external_exports.boolean().default(true).describe("Generate EF Core migration to seed permissions in database"),
|
|
@@ -26014,7 +26014,7 @@ var init_types3 = __esm({
|
|
|
26014
26014
|
});
|
|
26015
26015
|
ScaffoldApiClientInputSchema = external_exports.object({
|
|
26016
26016
|
path: external_exports.string().optional().describe("Path to SmartStack project root (defaults to configured project path)"),
|
|
26017
|
-
navRoute: external_exports.string().min(1).describe('NavRoute path (e.g., "
|
|
26017
|
+
navRoute: external_exports.string().min(1).describe('NavRoute path (e.g., "administration.users")'),
|
|
26018
26018
|
name: external_exports.string().min(1).describe('Entity name in PascalCase (e.g., "User", "Order")'),
|
|
26019
26019
|
methods: external_exports.array(external_exports.enum(["getAll", "getById", "create", "update", "delete", "search", "export"])).default(["getAll", "getById", "create", "update", "delete"]).describe("API methods to generate"),
|
|
26020
26020
|
options: external_exports.object({
|
|
@@ -26027,7 +26027,7 @@ var init_types3 = __esm({
|
|
|
26027
26027
|
ScaffoldRoutesInputSchema = external_exports.object({
|
|
26028
26028
|
path: external_exports.string().optional().describe("Path to SmartStack project root (defaults to configured project path)"),
|
|
26029
26029
|
source: external_exports.enum(["controllers", "navigation", "manual"]).default("controllers").describe("Source for route discovery: controllers (scan NavRoute attributes), navigation (from DB), manual (from config)"),
|
|
26030
|
-
scope: external_exports.
|
|
26030
|
+
scope: external_exports.string().default("all").describe('Scope of routes to generate. Use "all" or a specific application name (e.g., "administration")'),
|
|
26031
26031
|
options: external_exports.object({
|
|
26032
26032
|
outputPath: external_exports.string().optional().describe("Custom output path"),
|
|
26033
26033
|
includeLayouts: external_exports.boolean().default(true).describe("Generate layout components"),
|
|
@@ -27185,13 +27185,13 @@ async function validateControllerRoutes(structure, _config, result) {
|
|
|
27185
27185
|
if (navRouteMatch) {
|
|
27186
27186
|
const routePath = navRouteMatch[1];
|
|
27187
27187
|
const parts = routePath.split(".");
|
|
27188
|
-
if (parts.length <
|
|
27189
|
-
result.
|
|
27190
|
-
type: "
|
|
27188
|
+
if (parts.length < 3) {
|
|
27189
|
+
result.errors.push({
|
|
27190
|
+
type: "error",
|
|
27191
27191
|
category: "controllers",
|
|
27192
27192
|
message: `Controller "${fileName}" has NavRoute with insufficient depth: "${routePath}"`,
|
|
27193
27193
|
file: path8.relative(structure.root, file),
|
|
27194
|
-
suggestion: 'NavRoute
|
|
27194
|
+
suggestion: 'NavRoute must have at least 3 levels: "application.module.section" (e.g., "administration.users.management")'
|
|
27195
27195
|
});
|
|
27196
27196
|
}
|
|
27197
27197
|
const hasUppercase = parts.some((part) => part !== part.toLowerCase());
|
|
@@ -27201,7 +27201,7 @@ async function validateControllerRoutes(structure, _config, result) {
|
|
|
27201
27201
|
category: "controllers",
|
|
27202
27202
|
message: `Controller "${fileName}" has NavRoute with uppercase characters: "${routePath}"`,
|
|
27203
27203
|
file: path8.relative(structure.root, file),
|
|
27204
|
-
suggestion: 'NavRoute paths must be lowercase (e.g., "
|
|
27204
|
+
suggestion: 'NavRoute paths must be lowercase (e.g., "administration.users")'
|
|
27205
27205
|
});
|
|
27206
27206
|
}
|
|
27207
27207
|
const routeAttrMatch = content.match(/\[Route\s*\(\s*"([^"]+)"\s*\)\]/);
|
|
@@ -27222,7 +27222,7 @@ async function validateControllerRoutes(structure, _config, result) {
|
|
|
27222
27222
|
category: "controllers",
|
|
27223
27223
|
message: `Controller "${fileName}" uses hardcoded Route instead of NavRoute`,
|
|
27224
27224
|
file: path8.relative(structure.root, file),
|
|
27225
|
-
suggestion: 'Use [NavRoute("
|
|
27225
|
+
suggestion: 'Use [NavRoute("application.module.section")] for navigation-based routing'
|
|
27226
27226
|
});
|
|
27227
27227
|
}
|
|
27228
27228
|
}
|
|
@@ -27975,15 +27975,6 @@ async function validateFeatureJson(structure, _config, result) {
|
|
|
27975
27975
|
suggestion: 'Rename "lastModified" to "updatedAt" for schema compliance'
|
|
27976
27976
|
});
|
|
27977
27977
|
}
|
|
27978
|
-
if (data.metadata?.context && data.metadata.context !== "business" && relPath.includes("Business")) {
|
|
27979
|
-
result.warnings.push({
|
|
27980
|
-
type: "warning",
|
|
27981
|
-
category: "feature-json",
|
|
27982
|
-
message: `feature.json context is "${data.metadata.context}" but file is in Business directory`,
|
|
27983
|
-
file: relPath,
|
|
27984
|
-
suggestion: 'Use context: "business" for business domain features'
|
|
27985
|
-
});
|
|
27986
|
-
}
|
|
27987
27978
|
if (data.metadata?.analysisMode && data.metadata.analysisMode !== "interactive") {
|
|
27988
27979
|
result.errors.push({
|
|
27989
27980
|
type: "error",
|
|
@@ -34432,24 +34423,24 @@ var require_lib = __commonJS({
|
|
|
34432
34423
|
import path10 from "path";
|
|
34433
34424
|
function resolveHierarchy(navRoute) {
|
|
34434
34425
|
if (!navRoute) {
|
|
34435
|
-
return {
|
|
34426
|
+
return { application: "", module: "", section: "", domainPath: "", infraPath: "", controllerArea: "" };
|
|
34436
34427
|
}
|
|
34437
34428
|
const segments = navRoute.split(".");
|
|
34438
34429
|
const toPascal = (s) => s.split("-").map((p) => p.charAt(0).toUpperCase() + p.slice(1)).join("");
|
|
34439
|
-
const
|
|
34440
|
-
const
|
|
34441
|
-
const
|
|
34430
|
+
const application = segments[0] ? toPascal(segments[0]) : "";
|
|
34431
|
+
const module = segments[1] ? toPascal(segments[1]) : "";
|
|
34432
|
+
const section = segments[2] ? toPascal(segments[2]) : "";
|
|
34442
34433
|
let domainPath = "";
|
|
34443
34434
|
if (segments.length >= 3) {
|
|
34444
|
-
domainPath = path10.join(
|
|
34445
|
-
} else if (segments.length
|
|
34446
|
-
domainPath = path10.join(
|
|
34435
|
+
domainPath = path10.join(application, module, section);
|
|
34436
|
+
} else if (segments.length >= 2) {
|
|
34437
|
+
domainPath = path10.join(application, module);
|
|
34447
34438
|
} else if (segments.length === 1) {
|
|
34448
|
-
domainPath =
|
|
34439
|
+
domainPath = application;
|
|
34449
34440
|
}
|
|
34450
34441
|
const infraPath = domainPath;
|
|
34451
|
-
const controllerArea =
|
|
34452
|
-
return {
|
|
34442
|
+
const controllerArea = application;
|
|
34443
|
+
return { application, module, section, domainPath, infraPath, controllerArea };
|
|
34453
34444
|
}
|
|
34454
34445
|
async function handleScaffoldExtension(args, config2) {
|
|
34455
34446
|
const input = ScaffoldExtensionInputSchema.parse(args);
|
|
@@ -34596,7 +34587,7 @@ async function scaffoldFeature(name, options, structure, config2, result, dryRun
|
|
|
34596
34587
|
result.instructions.push(`${withRepository ? withValidation ? "6" : "5" : withValidation ? "5" : "4"}. Run migration: \`dotnet ef database update --context ${dbContextName}\``);
|
|
34597
34588
|
if (!skipComponent) {
|
|
34598
34589
|
const featureHierarchy = resolveHierarchy(options?.navRoute);
|
|
34599
|
-
const featureComponentPath = featureHierarchy.
|
|
34590
|
+
const featureComponentPath = featureHierarchy.application && featureHierarchy.module ? `@/components/${featureHierarchy.application.toLowerCase()}/${featureHierarchy.module.toLowerCase()}/${name}` : `./components/${name}`;
|
|
34600
34591
|
result.instructions.push(`Import component: \`import { ${name} } from '${featureComponentPath}';\``);
|
|
34601
34592
|
}
|
|
34602
34593
|
}
|
|
@@ -35413,12 +35404,12 @@ public class {{name}}Controller : ControllerBase
|
|
|
35413
35404
|
result.instructions.push("");
|
|
35414
35405
|
result.instructions.push("NavRoute resolves API routes from Navigation entities in the database.");
|
|
35415
35406
|
result.instructions.push("Ensure the navigation path exists (seed data required):");
|
|
35416
|
-
result.instructions.push(`
|
|
35407
|
+
result.instructions.push(` Application > Module > Section matching "${navRoute}"`);
|
|
35417
35408
|
} else {
|
|
35418
35409
|
result.instructions.push("Controller created with traditional routing.");
|
|
35419
35410
|
result.instructions.push("");
|
|
35420
35411
|
result.instructions.push("\u26A0\uFE0F Consider using NavRoute for navigation-based routing:");
|
|
35421
|
-
result.instructions.push(` [NavRoute("
|
|
35412
|
+
result.instructions.push(` [NavRoute("application.module.section")]`);
|
|
35422
35413
|
result.instructions.push("");
|
|
35423
35414
|
result.instructions.push("API endpoints (with traditional routing):");
|
|
35424
35415
|
result.instructions.push(` GET /api/${name.toLowerCase()}`);
|
|
@@ -35583,7 +35574,7 @@ export function use{{name}}(options: Use{{name}}Options = {}) {
|
|
|
35583
35574
|
const projectRoot = config2.smartstack.projectPath;
|
|
35584
35575
|
const webPath = structure.web || path10.join(projectRoot, "web");
|
|
35585
35576
|
const componentsBase = path10.join(webPath, "src", "components");
|
|
35586
|
-
const componentsPath = options?.outputPath ? options.outputPath : hierarchy.
|
|
35577
|
+
const componentsPath = options?.outputPath ? options.outputPath : hierarchy.application && hierarchy.module ? path10.join(componentsBase, hierarchy.application.toLowerCase(), hierarchy.module.toLowerCase()) : componentsBase;
|
|
35587
35578
|
const hooksPath = path10.join(webPath, "src", "hooks");
|
|
35588
35579
|
const componentFilePath = path10.join(componentsPath, `${name}.tsx`);
|
|
35589
35580
|
const hookFilePath = path10.join(hooksPath, `use${name}.ts`);
|
|
@@ -35598,7 +35589,7 @@ export function use{{name}}(options: Use{{name}}Options = {}) {
|
|
|
35598
35589
|
result.files.push({ path: componentFilePath, content: componentContent, type: "created" });
|
|
35599
35590
|
result.files.push({ path: hookFilePath, content: hookContent, type: "created" });
|
|
35600
35591
|
result.instructions.push("Import and use the component:");
|
|
35601
|
-
const componentImportPath = hierarchy.
|
|
35592
|
+
const componentImportPath = hierarchy.application && hierarchy.module ? `@/components/${hierarchy.application.toLowerCase()}/${hierarchy.module.toLowerCase()}/${name}` : `./components/${name}`;
|
|
35602
35593
|
result.instructions.push(`import { ${name} } from '${componentImportPath}';`);
|
|
35603
35594
|
result.instructions.push(`import { use${name} } from '@/hooks/use${name}';`);
|
|
35604
35595
|
result.instructions.push("");
|
|
@@ -35614,7 +35605,7 @@ export function use{{name}}(options: Use{{name}}Options = {}) {
|
|
|
35614
35605
|
result.instructions.push('import { EntityLookup } from "@/components/ui/EntityLookup";');
|
|
35615
35606
|
result.instructions.push("");
|
|
35616
35607
|
result.instructions.push("<EntityLookup");
|
|
35617
|
-
result.instructions.push(' apiEndpoint="/api/{
|
|
35608
|
+
result.instructions.push(' apiEndpoint="/api/{app}/{related-entity}"');
|
|
35618
35609
|
result.instructions.push(" value={formData.relatedEntityId}");
|
|
35619
35610
|
result.instructions.push(' onChange={(id) => handleChange("relatedEntityId", id)}');
|
|
35620
35611
|
result.instructions.push(' label="Related Entity"');
|
|
@@ -36482,7 +36473,7 @@ var init_scaffold_extension = __esm({
|
|
|
36482
36473
|
},
|
|
36483
36474
|
navRoute: {
|
|
36484
36475
|
type: "string",
|
|
36485
|
-
description: 'Navigation route path for controller (e.g., "
|
|
36476
|
+
description: 'Navigation route path for controller (e.g., "administration.users.management"). Minimum 3 levels: application.module.section. Optional 4th: resource.'
|
|
36486
36477
|
},
|
|
36487
36478
|
navRouteSuffix: {
|
|
36488
36479
|
type: "string",
|
|
@@ -53028,16 +53019,16 @@ async function handleGeneratePermissions(args, config2) {
|
|
|
53028
53019
|
function generatePermissionsForNavRoute(navRoute, customActions, includeStandardActions, includeWildcard = true) {
|
|
53029
53020
|
const permissions = [];
|
|
53030
53021
|
const parts = navRoute.split(".");
|
|
53031
|
-
const
|
|
53022
|
+
const application = parts[0];
|
|
53032
53023
|
if (parts.length < 3) {
|
|
53033
|
-
throw new Error(`Invalid NavRoute format: ${navRoute}. Expected format:
|
|
53024
|
+
throw new Error(`Invalid NavRoute format: ${navRoute}. Expected format: application.module.section (minimum 3 levels, optional 4th: resource)`);
|
|
53034
53025
|
}
|
|
53035
53026
|
if (includeWildcard) {
|
|
53036
53027
|
permissions.push({
|
|
53037
53028
|
code: `${navRoute}.*`,
|
|
53038
53029
|
name: formatPermissionName(navRoute, "Full Access"),
|
|
53039
53030
|
description: `Full ${parts[parts.length - 1]} management`,
|
|
53040
|
-
category:
|
|
53031
|
+
category: application
|
|
53041
53032
|
});
|
|
53042
53033
|
}
|
|
53043
53034
|
for (const customAction of customActions) {
|
|
@@ -53057,7 +53048,7 @@ function generatePermissionsForNavRoute(navRoute, customActions, includeStandard
|
|
|
53057
53048
|
code,
|
|
53058
53049
|
name,
|
|
53059
53050
|
description,
|
|
53060
|
-
category:
|
|
53051
|
+
category: application
|
|
53061
53052
|
});
|
|
53062
53053
|
}
|
|
53063
53054
|
return permissions;
|
|
@@ -53138,14 +53129,23 @@ function formatPermissionName(navRoute, action) {
|
|
|
53138
53129
|
}
|
|
53139
53130
|
function formatPermissionDescription(navRoute, action) {
|
|
53140
53131
|
const parts = navRoute.split(".");
|
|
53141
|
-
const
|
|
53142
|
-
const
|
|
53143
|
-
const
|
|
53144
|
-
const
|
|
53145
|
-
const applicationName = application
|
|
53146
|
-
const moduleName = module
|
|
53132
|
+
const application = parts[0];
|
|
53133
|
+
const module = parts[1];
|
|
53134
|
+
const section = parts[2];
|
|
53135
|
+
const toTitleCase = (s) => s.split(/[-_]/).map((word) => word.charAt(0).toUpperCase() + word.slice(1)).join(" ");
|
|
53136
|
+
const applicationName = toTitleCase(application);
|
|
53137
|
+
const moduleName = toTitleCase(module);
|
|
53147
53138
|
const actionVerb = getActionVerb(action);
|
|
53148
|
-
|
|
53139
|
+
if (parts.length >= 4) {
|
|
53140
|
+
const sectionName = toTitleCase(section);
|
|
53141
|
+
const resourceName = toTitleCase(parts[parts.length - 1]);
|
|
53142
|
+
return `${actionVerb} ${resourceName} in ${applicationName} > ${moduleName} > ${sectionName}`;
|
|
53143
|
+
}
|
|
53144
|
+
if (section) {
|
|
53145
|
+
const sectionName = toTitleCase(section);
|
|
53146
|
+
return `${actionVerb} ${sectionName} in ${applicationName} > ${moduleName}`;
|
|
53147
|
+
}
|
|
53148
|
+
return `${actionVerb} ${moduleName} in ${applicationName}`;
|
|
53149
53149
|
}
|
|
53150
53150
|
function getActionVerb(action) {
|
|
53151
53151
|
const verbs = {
|
|
@@ -53200,12 +53200,27 @@ function getUniqueNavRouteCount(permissions) {
|
|
|
53200
53200
|
}
|
|
53201
53201
|
function generateHasDataCode(permissions, navRoute) {
|
|
53202
53202
|
const parts = navRoute.split(".");
|
|
53203
|
-
const
|
|
53204
|
-
const
|
|
53203
|
+
const lastSegment = parts[parts.length - 1];
|
|
53204
|
+
const depth = parts.length;
|
|
53205
|
+
let permissionLevel;
|
|
53206
|
+
let fkProperty;
|
|
53207
|
+
let fkVarName;
|
|
53208
|
+
let seedDataSource;
|
|
53209
|
+
if (depth >= 4) {
|
|
53210
|
+
permissionLevel = "PermissionLevel.Resource";
|
|
53211
|
+
fkProperty = "ResourceId";
|
|
53212
|
+
fkVarName = `${lastSegment}ResourceId`;
|
|
53213
|
+
seedDataSource = "NavigationResourceSeedData.cs";
|
|
53214
|
+
} else {
|
|
53215
|
+
permissionLevel = "PermissionLevel.Section";
|
|
53216
|
+
fkProperty = "SectionId";
|
|
53217
|
+
fkVarName = `${lastSegment}SectionId`;
|
|
53218
|
+
seedDataSource = "NavigationSectionSeedData.cs";
|
|
53219
|
+
}
|
|
53205
53220
|
let code = "```csharp\n";
|
|
53206
|
-
code += `// 1. Add
|
|
53221
|
+
code += `// 1. Add ${fkProperty} variable (get from ${seedDataSource})
|
|
53207
53222
|
`;
|
|
53208
|
-
code += `var ${
|
|
53223
|
+
code += `var ${fkVarName} = Guid.Parse("XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX"); // TODO: Replace with actual ID
|
|
53209
53224
|
|
|
53210
53225
|
`;
|
|
53211
53226
|
code += `// 2. Add these entries to the HasData() return array:
|
|
@@ -53215,11 +53230,11 @@ function generateHasDataCode(permissions, navRoute) {
|
|
|
53215
53230
|
const action = perm.code.split(".").pop();
|
|
53216
53231
|
const guidPlaceholder = generatePlaceholderGuid();
|
|
53217
53232
|
if (isWildcard) {
|
|
53218
|
-
code += `new { Id = Guid.Parse("${guidPlaceholder}"), Path = "${perm.code}", Level =
|
|
53233
|
+
code += `new { Id = Guid.Parse("${guidPlaceholder}"), Path = "${perm.code}", Level = ${permissionLevel}, IsWildcard = true, ${fkProperty} = ${fkVarName}, Description = "${perm.description}", CreatedAt = seedDate },
|
|
53219
53234
|
`;
|
|
53220
53235
|
} else {
|
|
53221
53236
|
const actionEnum = getActionEnum(action || "read");
|
|
53222
|
-
code += `new { Id = Guid.Parse("${guidPlaceholder}"), Path = "${perm.code}", Level =
|
|
53237
|
+
code += `new { Id = Guid.Parse("${guidPlaceholder}"), Path = "${perm.code}", Level = ${permissionLevel}, Action = PermissionAction.${actionEnum}, IsWildcard = false, ${fkProperty} = ${fkVarName}, Description = "${perm.description}", CreatedAt = seedDate },
|
|
53223
53238
|
`;
|
|
53224
53239
|
}
|
|
53225
53240
|
}
|
|
@@ -53289,13 +53304,13 @@ IMPORTANT: This tool does NOT generate migrations with raw SQL (forbidden by Sma
|
|
|
53289
53304
|
Instead, it outputs HasData() code that must be manually added to the Configuration file.
|
|
53290
53305
|
|
|
53291
53306
|
Example:
|
|
53292
|
-
navRoute: "
|
|
53307
|
+
navRoute: "administration.entra"
|
|
53293
53308
|
Outputs HasData() code for:
|
|
53294
|
-
-
|
|
53295
|
-
-
|
|
53296
|
-
-
|
|
53297
|
-
-
|
|
53298
|
-
-
|
|
53309
|
+
- administration.entra.*
|
|
53310
|
+
- administration.entra.read
|
|
53311
|
+
- administration.entra.create
|
|
53312
|
+
- administration.entra.update
|
|
53313
|
+
- administration.entra.delete
|
|
53299
53314
|
|
|
53300
53315
|
After adding to PermissionConfiguration.cs, run: dotnet ef migrations add <MigrationName>`,
|
|
53301
53316
|
inputSchema: {
|
|
@@ -53303,7 +53318,7 @@ After adding to PermissionConfiguration.cs, run: dotnet ef migrations add <Migra
|
|
|
53303
53318
|
properties: {
|
|
53304
53319
|
navRoute: {
|
|
53305
53320
|
type: "string",
|
|
53306
|
-
description: 'NavRoute path (e.g., "
|
|
53321
|
+
description: 'NavRoute path (e.g., "administration.entra"). If not provided, scans all controllers.'
|
|
53307
53322
|
},
|
|
53308
53323
|
actions: {
|
|
53309
53324
|
type: "array",
|
|
@@ -53318,7 +53333,7 @@ After adding to PermissionConfiguration.cs, run: dotnet ef migrations add <Migra
|
|
|
53318
53333
|
includeWildcard: {
|
|
53319
53334
|
type: "boolean",
|
|
53320
53335
|
default: true,
|
|
53321
|
-
description: "Include wildcard permission (e.g.,
|
|
53336
|
+
description: "Include wildcard permission (e.g., myspace.tenants.*)"
|
|
53322
53337
|
}
|
|
53323
53338
|
}
|
|
53324
53339
|
}
|
|
@@ -57383,7 +57398,7 @@ Creates:
|
|
|
57383
57398
|
- Integration with navRoutes.generated.ts registry
|
|
57384
57399
|
|
|
57385
57400
|
Example:
|
|
57386
|
-
scaffold_api_client navRoute="
|
|
57401
|
+
scaffold_api_client navRoute="administration.users" name="User"
|
|
57387
57402
|
|
|
57388
57403
|
The generated client automatically resolves the API path from the NavRoute registry,
|
|
57389
57404
|
ensuring frontend routes stay synchronized with backend NavRoute attributes.`,
|
|
@@ -57396,7 +57411,7 @@ ensuring frontend routes stay synchronized with backend NavRoute attributes.`,
|
|
|
57396
57411
|
},
|
|
57397
57412
|
navRoute: {
|
|
57398
57413
|
type: "string",
|
|
57399
|
-
description: 'NavRoute path (e.g., "
|
|
57414
|
+
description: 'NavRoute path (e.g., "administration.users")'
|
|
57400
57415
|
},
|
|
57401
57416
|
name: {
|
|
57402
57417
|
type: "string",
|
|
@@ -57493,9 +57508,9 @@ async function scaffoldRoutes(input, config2) {
|
|
|
57493
57508
|
result.instructions.push("**Detect App.tsx pattern first**, then follow the matching instructions:");
|
|
57494
57509
|
result.instructions.push("");
|
|
57495
57510
|
const routeTree = buildRouteTree(navRoutes);
|
|
57496
|
-
result.instructions.push("### Pattern A: mergeRoutes (
|
|
57511
|
+
result.instructions.push("### Pattern A: mergeRoutes (applicationRoutes pattern)");
|
|
57497
57512
|
result.instructions.push("");
|
|
57498
|
-
result.instructions.push("Add routes to `
|
|
57513
|
+
result.instructions.push("Add routes to `applicationRoutes.{application}[]` with **RELATIVE** paths (no leading `/`):");
|
|
57499
57514
|
result.instructions.push("");
|
|
57500
57515
|
result.instructions.push("**IMPORTANT:** Pages are lazy-loaded. Use `<Suspense fallback={<PageLoader />}>` wrapper.");
|
|
57501
57516
|
result.instructions.push("");
|
|
@@ -57504,9 +57519,9 @@ async function scaffoldRoutes(input, config2) {
|
|
|
57504
57519
|
result.instructions.push("import { PageLoader } from '@/components/ui/PageLoader';");
|
|
57505
57520
|
result.instructions.push("");
|
|
57506
57521
|
const importedComponents = /* @__PURE__ */ new Set();
|
|
57507
|
-
for (const [
|
|
57508
|
-
for (const [,
|
|
57509
|
-
for (const route of
|
|
57522
|
+
for (const [_app, modules] of Object.entries(routeTree)) {
|
|
57523
|
+
for (const [, moduleRoutes] of Object.entries(modules)) {
|
|
57524
|
+
for (const route of moduleRoutes) {
|
|
57510
57525
|
const pageEntry = pageFiles.get(route.navRoute);
|
|
57511
57526
|
if (pageEntry) {
|
|
57512
57527
|
for (const entry of pageEntry) {
|
|
@@ -57528,11 +57543,11 @@ async function scaffoldRoutes(input, config2) {
|
|
|
57528
57543
|
}
|
|
57529
57544
|
}
|
|
57530
57545
|
result.instructions.push("");
|
|
57531
|
-
result.instructions.push("const
|
|
57532
|
-
for (const [
|
|
57533
|
-
result.instructions.push(` ${
|
|
57534
|
-
for (const [,
|
|
57535
|
-
for (const route of
|
|
57546
|
+
result.instructions.push("const applicationRoutes = {");
|
|
57547
|
+
for (const [app, modules] of Object.entries(routeTree)) {
|
|
57548
|
+
result.instructions.push(` '${app}': [`);
|
|
57549
|
+
for (const [, moduleRoutes] of Object.entries(modules)) {
|
|
57550
|
+
for (const route of moduleRoutes) {
|
|
57536
57551
|
const modulePath = route.navRoute.split(".").slice(1).map(toKebabCase).join("/");
|
|
57537
57552
|
const pageEntry = pageFiles.get(route.navRoute);
|
|
57538
57553
|
const component = pageEntry?.[0]?.componentName || `${route.navRoute.split(".").map(capitalize).join("")}Page`;
|
|
@@ -57545,20 +57560,19 @@ async function scaffoldRoutes(input, config2) {
|
|
|
57545
57560
|
result.instructions.push("```");
|
|
57546
57561
|
result.instructions.push("");
|
|
57547
57562
|
result.instructions.push("Routes are automatically injected into BOTH standard and tenant-prefixed trees by `mergeRoutes()`.");
|
|
57548
|
-
result.instructions.push("**DO NOT** add business/platform/personal routes to `clientRoutes[]` \u2014 that array is only for routes outside SmartStack contexts.");
|
|
57549
57563
|
result.instructions.push("");
|
|
57550
|
-
result.instructions.push('### Pattern B: JSX Routes (if App.tsx uses `<Route path="/{
|
|
57564
|
+
result.instructions.push('### Pattern B: JSX Routes (if App.tsx uses `<Route path="/{application}" element={<{Layout} />}>`)');
|
|
57551
57565
|
result.instructions.push("");
|
|
57552
57566
|
result.instructions.push("Insert `<Route>` children INSIDE the appropriate Layout wrapper.");
|
|
57553
57567
|
result.instructions.push("**IMPORTANT:** Use `<Suspense fallback={<PageLoader />}>` for lazy-loaded pages.");
|
|
57554
57568
|
result.instructions.push("");
|
|
57555
|
-
for (const [
|
|
57556
|
-
const layoutName = getLayoutName(
|
|
57557
|
-
result.instructions.push(`#### ${capitalize(
|
|
57569
|
+
for (const [app, modules] of Object.entries(routeTree)) {
|
|
57570
|
+
const layoutName = getLayoutName(app);
|
|
57571
|
+
result.instructions.push(`#### ${capitalize(app)} application (inside \`<Route path="/${app}" element={<${layoutName} />}>\`):`);
|
|
57558
57572
|
result.instructions.push("");
|
|
57559
57573
|
result.instructions.push("```tsx");
|
|
57560
|
-
for (const [,
|
|
57561
|
-
for (const route of
|
|
57574
|
+
for (const [, moduleRoutes] of Object.entries(modules)) {
|
|
57575
|
+
for (const route of moduleRoutes) {
|
|
57562
57576
|
const modulePath = route.navRoute.split(".").slice(1).map(toKebabCase).join("/");
|
|
57563
57577
|
const pageEntry = pageFiles.get(route.navRoute);
|
|
57564
57578
|
const component = pageEntry?.[0]?.componentName || `${route.navRoute.split(".").map(capitalize).join("")}Page`;
|
|
@@ -57580,10 +57594,10 @@ async function scaffoldRoutes(input, config2) {
|
|
|
57580
57594
|
result.files.push({ path: routerFile, content: routerContent, type: "created" });
|
|
57581
57595
|
if (includeLayouts) {
|
|
57582
57596
|
const layoutsPath = path19.join(webPath, "src", "layouts");
|
|
57583
|
-
const
|
|
57584
|
-
for (const
|
|
57585
|
-
const layoutContent = generateLayout(
|
|
57586
|
-
const layoutFile = path19.join(layoutsPath, `${capitalize(
|
|
57597
|
+
const applications = [...new Set(navRoutes.map((r) => r.navRoute.split(".")[0]))];
|
|
57598
|
+
for (const application of applications) {
|
|
57599
|
+
const layoutContent = generateLayout(application);
|
|
57600
|
+
const layoutFile = path19.join(layoutsPath, `${capitalize(application)}Layout.tsx`);
|
|
57587
57601
|
if (!dryRun) {
|
|
57588
57602
|
await ensureDirectory(layoutsPath);
|
|
57589
57603
|
await writeText(layoutFile, layoutContent);
|
|
@@ -57644,8 +57658,8 @@ async function discoverNavRoutes(structure, scope, warnings) {
|
|
|
57644
57658
|
if (navRouteMatch) {
|
|
57645
57659
|
const navRoute = navRouteMatch[1];
|
|
57646
57660
|
const suffix = navRouteMatch[2];
|
|
57647
|
-
const
|
|
57648
|
-
if (scope !== "all" &&
|
|
57661
|
+
const application = navRoute.split(".")[0];
|
|
57662
|
+
if (scope !== "all" && application !== scope) {
|
|
57649
57663
|
continue;
|
|
57650
57664
|
}
|
|
57651
57665
|
const controllerMatch = path19.basename(file).match(/(.+)Controller\.cs$/);
|
|
@@ -57738,28 +57752,28 @@ function generateNavRouteRegistry(routes) {
|
|
|
57738
57752
|
lines.push("}");
|
|
57739
57753
|
lines.push("");
|
|
57740
57754
|
lines.push("/**");
|
|
57741
|
-
lines.push(" * Get all routes for
|
|
57755
|
+
lines.push(" * Get all routes for an application");
|
|
57742
57756
|
lines.push(" */");
|
|
57743
|
-
lines.push("export function
|
|
57744
|
-
lines.push(" return Object.values(ROUTES).filter(r => r.navRoute.startsWith(`${
|
|
57757
|
+
lines.push("export function getRoutesByApplication(application: string): NavRoute[] {");
|
|
57758
|
+
lines.push(" return Object.values(ROUTES).filter(r => r.navRoute.startsWith(`${application}.`));");
|
|
57745
57759
|
lines.push("}");
|
|
57746
57760
|
lines.push("");
|
|
57747
57761
|
lines.push("/**");
|
|
57748
57762
|
lines.push(" * Get web route with optional tenant slug prefix");
|
|
57749
57763
|
lines.push(" *");
|
|
57750
|
-
lines.push(" * @param navRoute - NavRoute path (e.g., '
|
|
57764
|
+
lines.push(" * @param navRoute - NavRoute path (e.g., 'administration.users')");
|
|
57751
57765
|
lines.push(" * @param options - Configuration for URL building");
|
|
57752
57766
|
lines.push(" * @returns Web route path, optionally prefixed with /t/{slug}");
|
|
57753
57767
|
lines.push(" *");
|
|
57754
57768
|
lines.push(" * @example");
|
|
57755
57769
|
lines.push(" * // Single-tenant mode or no tenant");
|
|
57756
|
-
lines.push(" * getWebRoute('
|
|
57757
|
-
lines.push(" * // Returns: '/
|
|
57770
|
+
lines.push(" * getWebRoute('administration.users', { isSingleTenant: true })");
|
|
57771
|
+
lines.push(" * // Returns: '/administration/users'");
|
|
57758
57772
|
lines.push(" *");
|
|
57759
57773
|
lines.push(" * @example");
|
|
57760
57774
|
lines.push(" * // Multi-tenant mode with slug");
|
|
57761
|
-
lines.push(" * getWebRoute('
|
|
57762
|
-
lines.push(" * // Returns: '/t/acme/
|
|
57775
|
+
lines.push(" * getWebRoute('administration.users', { slug: 'acme', isSingleTenant: false })");
|
|
57776
|
+
lines.push(" * // Returns: '/t/acme/administration/users'");
|
|
57763
57777
|
lines.push(" */");
|
|
57764
57778
|
lines.push("export function getWebRoute(");
|
|
57765
57779
|
lines.push(" navRoute: string,");
|
|
@@ -57804,9 +57818,9 @@ function generateRouterConfig(routes, includeGuards) {
|
|
|
57804
57818
|
if (includeGuards) {
|
|
57805
57819
|
lines.push("import { ProtectedRoute, PermissionGuard } from './guards';");
|
|
57806
57820
|
}
|
|
57807
|
-
const
|
|
57808
|
-
for (const
|
|
57809
|
-
const name = `${capitalize(
|
|
57821
|
+
const applications = Object.keys(routeTree);
|
|
57822
|
+
for (const app of applications) {
|
|
57823
|
+
const name = `${capitalize(app)}Layout`;
|
|
57810
57824
|
lines.push(`const ${name} = lazy(() => import('../layouts/${name}').then(m => ({ default: m.${name} })));`);
|
|
57811
57825
|
}
|
|
57812
57826
|
lines.push("");
|
|
@@ -57817,37 +57831,32 @@ function generateRouterConfig(routes, includeGuards) {
|
|
|
57817
57831
|
}
|
|
57818
57832
|
lines.push("");
|
|
57819
57833
|
lines.push("const routes: RouteObject[] = [");
|
|
57820
|
-
for (const [
|
|
57834
|
+
for (const [app, modules] of Object.entries(routeTree)) {
|
|
57821
57835
|
lines.push(" {");
|
|
57822
|
-
lines.push(` path: '${
|
|
57823
|
-
lines.push(` element: <Suspense fallback={<PageLoader />}><${capitalize(
|
|
57836
|
+
lines.push(` path: '${app}',`);
|
|
57837
|
+
lines.push(` element: <Suspense fallback={<PageLoader />}><${capitalize(app)}Layout /></Suspense>,`);
|
|
57824
57838
|
lines.push(" children: [");
|
|
57825
|
-
for (const [
|
|
57826
|
-
|
|
57827
|
-
|
|
57828
|
-
lines.push(" children: [");
|
|
57829
|
-
for (const route of modules) {
|
|
57830
|
-
const modulePath = route.navRoute.split(".").slice(2).map(toKebabCase).join("/");
|
|
57839
|
+
for (const [, moduleRoutes] of Object.entries(modules)) {
|
|
57840
|
+
for (const route of moduleRoutes) {
|
|
57841
|
+
const modulePath = route.navRoute.split(".").slice(1).map(toKebabCase).join("/");
|
|
57831
57842
|
const pageName = route.navRoute.split(".").map(capitalize).join("");
|
|
57832
57843
|
if (includeGuards && route.permissions.length > 0) {
|
|
57833
|
-
lines.push("
|
|
57834
|
-
lines.push(`
|
|
57835
|
-
lines.push(`
|
|
57836
|
-
lines.push(`
|
|
57837
|
-
lines.push(`
|
|
57838
|
-
lines.push(`
|
|
57839
|
-
lines.push(`
|
|
57840
|
-
lines.push(`
|
|
57841
|
-
lines.push("
|
|
57844
|
+
lines.push(" {");
|
|
57845
|
+
lines.push(` path: '${modulePath || ""}',`);
|
|
57846
|
+
lines.push(` element: (`);
|
|
57847
|
+
lines.push(` <PermissionGuard permissions={ROUTES['${route.navRoute}'].permissions}>`);
|
|
57848
|
+
lines.push(` {/* <${pageName}Page /> */}`);
|
|
57849
|
+
lines.push(` <div>TODO: ${pageName}Page</div>`);
|
|
57850
|
+
lines.push(` </PermissionGuard>`);
|
|
57851
|
+
lines.push(` ),`);
|
|
57852
|
+
lines.push(" },");
|
|
57842
57853
|
} else {
|
|
57843
|
-
lines.push("
|
|
57844
|
-
lines.push(`
|
|
57845
|
-
lines.push(`
|
|
57846
|
-
lines.push("
|
|
57854
|
+
lines.push(" {");
|
|
57855
|
+
lines.push(` path: '${modulePath || ""}',`);
|
|
57856
|
+
lines.push(` element: <div>TODO: ${pageName}Page</div>,`);
|
|
57857
|
+
lines.push(" },");
|
|
57847
57858
|
}
|
|
57848
57859
|
}
|
|
57849
|
-
lines.push(" ],");
|
|
57850
|
-
lines.push(" },");
|
|
57851
57860
|
}
|
|
57852
57861
|
lines.push(" ],");
|
|
57853
57862
|
lines.push(" },");
|
|
@@ -57864,44 +57873,44 @@ function buildRouteTree(routes) {
|
|
|
57864
57873
|
const tree = {};
|
|
57865
57874
|
for (const route of routes) {
|
|
57866
57875
|
const parts = route.navRoute.split(".");
|
|
57867
|
-
const
|
|
57868
|
-
const
|
|
57869
|
-
if (!tree[
|
|
57870
|
-
tree[
|
|
57876
|
+
const app = parts[0];
|
|
57877
|
+
const module = parts[1] || "default";
|
|
57878
|
+
if (!tree[app]) {
|
|
57879
|
+
tree[app] = {};
|
|
57871
57880
|
}
|
|
57872
|
-
if (!tree[
|
|
57873
|
-
tree[
|
|
57881
|
+
if (!tree[app][module]) {
|
|
57882
|
+
tree[app][module] = [];
|
|
57874
57883
|
}
|
|
57875
|
-
tree[
|
|
57884
|
+
tree[app][module].push(route);
|
|
57876
57885
|
}
|
|
57877
57886
|
return tree;
|
|
57878
57887
|
}
|
|
57879
|
-
function generateLayout(
|
|
57880
|
-
const
|
|
57888
|
+
function generateLayout(application) {
|
|
57889
|
+
const appCapitalized = capitalize(application);
|
|
57881
57890
|
return `/**
|
|
57882
|
-
* ${
|
|
57891
|
+
* ${appCapitalized} Layout
|
|
57883
57892
|
*
|
|
57884
57893
|
* Auto-generated by SmartStack MCP - Customize as needed
|
|
57885
57894
|
*/
|
|
57886
57895
|
|
|
57887
57896
|
import React from 'react';
|
|
57888
57897
|
import { Outlet, Link, useLocation } from 'react-router-dom';
|
|
57889
|
-
import { ROUTES,
|
|
57898
|
+
import { ROUTES, getRoutesByApplication } from '../routes/navRoutes.generated';
|
|
57890
57899
|
|
|
57891
|
-
export const ${
|
|
57900
|
+
export const ${appCapitalized}Layout: React.FC = () => {
|
|
57892
57901
|
const location = useLocation();
|
|
57893
|
-
const
|
|
57902
|
+
const appRoutes = getRoutesByApplication('${application}');
|
|
57894
57903
|
|
|
57895
57904
|
return (
|
|
57896
57905
|
<div className="flex h-screen bg-gray-100">
|
|
57897
57906
|
{/* Sidebar */}
|
|
57898
57907
|
<aside className="w-64 bg-white shadow-sm">
|
|
57899
57908
|
<div className="p-4 border-b">
|
|
57900
|
-
<h1 className="text-xl font-semibold text-gray-900">${
|
|
57909
|
+
<h1 className="text-xl font-semibold text-gray-900">${appCapitalized}</h1>
|
|
57901
57910
|
</div>
|
|
57902
57911
|
<nav className="p-4">
|
|
57903
57912
|
<ul className="space-y-2">
|
|
57904
|
-
{
|
|
57913
|
+
{appRoutes.map((route) => (
|
|
57905
57914
|
<li key={route.navRoute}>
|
|
57906
57915
|
<Link
|
|
57907
57916
|
to={route.web}
|
|
@@ -57929,7 +57938,7 @@ export const ${contextCapitalized}Layout: React.FC = () => {
|
|
|
57929
57938
|
);
|
|
57930
57939
|
};
|
|
57931
57940
|
|
|
57932
|
-
export default ${
|
|
57941
|
+
export default ${appCapitalized}Layout;
|
|
57933
57942
|
`;
|
|
57934
57943
|
}
|
|
57935
57944
|
function generateRouteGuards() {
|
|
@@ -58011,17 +58020,16 @@ async function discoverPageFiles(webPath, routes) {
|
|
|
58011
58020
|
const pageMap = /* @__PURE__ */ new Map();
|
|
58012
58021
|
for (const route of routes) {
|
|
58013
58022
|
const parts = route.navRoute.split(".");
|
|
58014
|
-
const
|
|
58015
|
-
const
|
|
58016
|
-
const moduleParts = parts.slice(2).map(capitalize);
|
|
58023
|
+
const app = capitalize(parts[0]);
|
|
58024
|
+
const moduleParts = parts.slice(1).map(capitalize);
|
|
58017
58025
|
const moduleDir = moduleParts.join("/");
|
|
58018
|
-
const pagesDir = path19.join(webPath, "src", "pages",
|
|
58026
|
+
const pagesDir = path19.join(webPath, "src", "pages", app, moduleDir || "");
|
|
58019
58027
|
try {
|
|
58020
58028
|
const pageFiles = await glob("*Page.tsx", { cwd: pagesDir, absolute: false });
|
|
58021
58029
|
if (pageFiles.length > 0) {
|
|
58022
58030
|
const entries = pageFiles.map((f) => {
|
|
58023
58031
|
const componentName = f.replace(".tsx", "");
|
|
58024
|
-
const importPath = `@/pages/${
|
|
58032
|
+
const importPath = `@/pages/${app}/${moduleDir ? moduleDir + "/" : ""}${componentName}`;
|
|
58025
58033
|
return { importPath, componentName };
|
|
58026
58034
|
});
|
|
58027
58035
|
pageMap.set(route.navRoute, entries);
|
|
@@ -58042,12 +58050,12 @@ function generateClientRoutesConfig(routes, pageFiles, includeGuards) {
|
|
|
58042
58050
|
' * Run `scaffold_routes` with outputFormat: "clientRoutes" to regenerate.',
|
|
58043
58051
|
" *",
|
|
58044
58052
|
" * These routes must be added INSIDE the appropriate Layout wrapper in App.tsx.",
|
|
58045
|
-
" * They are NOT standalone - they are children of the
|
|
58053
|
+
" * They are NOT standalone - they are children of the application layout routes.",
|
|
58046
58054
|
" * Routes must be added in BOTH the standard block and the tenant-prefixed block.",
|
|
58047
58055
|
" */",
|
|
58048
58056
|
"",
|
|
58049
58057
|
"import type { RouteObject } from 'react-router-dom';",
|
|
58050
|
-
"import type {
|
|
58058
|
+
"import type { ApplicationRouteExtensions } from '@atlashub/smartstack';",
|
|
58051
58059
|
"import { lazy, Suspense } from 'react';",
|
|
58052
58060
|
"import { Navigate } from 'react-router-dom';",
|
|
58053
58061
|
"import { PageLoader } from '@/components/ui/PageLoader';"
|
|
@@ -58078,108 +58086,71 @@ function generateClientRoutesConfig(routes, pageFiles, includeGuards) {
|
|
|
58078
58086
|
}
|
|
58079
58087
|
}
|
|
58080
58088
|
lines.push("");
|
|
58081
|
-
for (const [
|
|
58082
|
-
const
|
|
58083
|
-
const layoutName = getLayoutName(
|
|
58089
|
+
for (const [app, modules] of Object.entries(routeTree)) {
|
|
58090
|
+
const appUpper = capitalize(app);
|
|
58091
|
+
const layoutName = getLayoutName(app);
|
|
58084
58092
|
lines.push("/**");
|
|
58085
|
-
lines.push(` * Routes for ${
|
|
58086
|
-
lines.push(` * Add these as children of <Route path="/${
|
|
58093
|
+
lines.push(` * Routes for ${appUpper} application`);
|
|
58094
|
+
lines.push(` * Add these as children of <Route path="/${app}" element={<${layoutName} />}>`);
|
|
58087
58095
|
lines.push(" */");
|
|
58088
|
-
lines.push(`export const ${
|
|
58089
|
-
for (const [
|
|
58090
|
-
|
|
58091
|
-
|
|
58092
|
-
|
|
58093
|
-
|
|
58094
|
-
const
|
|
58095
|
-
|
|
58096
|
-
|
|
58097
|
-
|
|
58098
|
-
|
|
58099
|
-
|
|
58100
|
-
|
|
58101
|
-
const hasRealPage = pageFiles.has(route.navRoute);
|
|
58102
|
-
if (includeGuards && route.permissions.length > 0) {
|
|
58103
|
-
lines.push(` {`);
|
|
58104
|
-
lines.push(` path: '${modulePath}',`);
|
|
58105
|
-
if (hasRealPage) {
|
|
58106
|
-
lines.push(` element: <Suspense fallback={<PageLoader />}><PermissionGuard permissions={ROUTES['${route.navRoute}'].permissions}><${component} /></PermissionGuard></Suspense>,`);
|
|
58107
|
-
} else {
|
|
58108
|
-
lines.push(` element: <PermissionGuard permissions={ROUTES['${route.navRoute}'].permissions}><div>TODO: ${component}</div></PermissionGuard>,`);
|
|
58109
|
-
}
|
|
58110
|
-
lines.push(` },`);
|
|
58111
|
-
} else {
|
|
58112
|
-
lines.push(` {`);
|
|
58113
|
-
lines.push(` path: '${modulePath}',`);
|
|
58114
|
-
lines.push(` element: ${hasRealPage ? `<Suspense fallback={<PageLoader />}><${component} /></Suspense>` : `<div>TODO: ${component}</div>`},`);
|
|
58115
|
-
lines.push(` },`);
|
|
58116
|
-
}
|
|
58117
|
-
}
|
|
58118
|
-
lines.push(" ],");
|
|
58119
|
-
lines.push(" },");
|
|
58120
|
-
} else {
|
|
58121
|
-
for (const route of modules) {
|
|
58122
|
-
const fullPath = route.navRoute.split(".").slice(1).map(toKebabCase).join("/");
|
|
58123
|
-
const pageName = route.navRoute.split(".").map(capitalize).join("") + "Page";
|
|
58124
|
-
const pageEntry = pageFiles.get(route.navRoute);
|
|
58125
|
-
const component = pageEntry?.[0]?.componentName || pageName;
|
|
58126
|
-
const hasRealPage = pageFiles.has(route.navRoute);
|
|
58127
|
-
if (includeGuards && route.permissions.length > 0) {
|
|
58128
|
-
lines.push(` {`);
|
|
58129
|
-
lines.push(` path: '${fullPath}',`);
|
|
58130
|
-
if (hasRealPage) {
|
|
58131
|
-
lines.push(` element: <Suspense fallback={<PageLoader />}><PermissionGuard permissions={ROUTES['${route.navRoute}'].permissions}><${component} /></PermissionGuard></Suspense>,`);
|
|
58132
|
-
} else {
|
|
58133
|
-
lines.push(` element: <PermissionGuard permissions={ROUTES['${route.navRoute}'].permissions}><div>TODO: ${component}</div></PermissionGuard>,`);
|
|
58134
|
-
}
|
|
58135
|
-
lines.push(` },`);
|
|
58096
|
+
lines.push(`export const ${app}Routes: RouteObject[] = [`);
|
|
58097
|
+
for (const [, moduleRoutes] of Object.entries(modules)) {
|
|
58098
|
+
for (const route of moduleRoutes) {
|
|
58099
|
+
const modulePath = route.navRoute.split(".").slice(1).map(toKebabCase).join("/");
|
|
58100
|
+
const pageName = route.navRoute.split(".").map(capitalize).join("") + "Page";
|
|
58101
|
+
const pageEntry = pageFiles.get(route.navRoute);
|
|
58102
|
+
const component = pageEntry?.[0]?.componentName || pageName;
|
|
58103
|
+
const hasRealPage = pageFiles.has(route.navRoute);
|
|
58104
|
+
if (includeGuards && route.permissions.length > 0) {
|
|
58105
|
+
lines.push(` {`);
|
|
58106
|
+
lines.push(` path: '${modulePath}',`);
|
|
58107
|
+
if (hasRealPage) {
|
|
58108
|
+
lines.push(` element: <Suspense fallback={<PageLoader />}><PermissionGuard permissions={ROUTES['${route.navRoute}'].permissions}><${component} /></PermissionGuard></Suspense>,`);
|
|
58136
58109
|
} else {
|
|
58137
|
-
lines.push(`
|
|
58138
|
-
lines.push(` path: '${fullPath}',`);
|
|
58139
|
-
lines.push(` element: ${hasRealPage ? `<Suspense fallback={<PageLoader />}><${component} /></Suspense>` : `<div>TODO: ${component}</div>`},`);
|
|
58140
|
-
lines.push(` },`);
|
|
58110
|
+
lines.push(` element: <PermissionGuard permissions={ROUTES['${route.navRoute}'].permissions}><div>TODO: ${component}</div></PermissionGuard>,`);
|
|
58141
58111
|
}
|
|
58112
|
+
lines.push(` },`);
|
|
58113
|
+
} else {
|
|
58114
|
+
lines.push(` {`);
|
|
58115
|
+
lines.push(` path: '${modulePath}',`);
|
|
58116
|
+
lines.push(` element: ${hasRealPage ? `<Suspense fallback={<PageLoader />}><${component} /></Suspense>` : `<div>TODO: ${component}</div>`},`);
|
|
58117
|
+
lines.push(` },`);
|
|
58142
58118
|
}
|
|
58143
58119
|
}
|
|
58144
58120
|
}
|
|
58145
58121
|
lines.push("];");
|
|
58146
58122
|
lines.push("");
|
|
58147
58123
|
}
|
|
58148
|
-
const
|
|
58149
|
-
lines.push("/** All generated routes grouped by
|
|
58124
|
+
const appKeys = Object.keys(routeTree);
|
|
58125
|
+
lines.push("/** All generated routes grouped by application */");
|
|
58150
58126
|
lines.push("export const generatedRoutes = {");
|
|
58151
|
-
for (const
|
|
58152
|
-
lines.push(` ${
|
|
58127
|
+
for (const app of appKeys) {
|
|
58128
|
+
lines.push(` ${app}: ${app}Routes,`);
|
|
58153
58129
|
}
|
|
58154
58130
|
lines.push("};");
|
|
58155
58131
|
lines.push("");
|
|
58156
58132
|
lines.push("/**");
|
|
58157
|
-
lines.push(" *
|
|
58158
|
-
lines.push(" * Import and spread into your App.tsx
|
|
58133
|
+
lines.push(" * Application route extensions for mergeRoutes() pattern.");
|
|
58134
|
+
lines.push(" * Import and spread into your App.tsx applicationRoutes:");
|
|
58159
58135
|
lines.push(" *");
|
|
58160
58136
|
lines.push(" * ```tsx");
|
|
58161
|
-
lines.push(" * import {
|
|
58162
|
-
lines.push(" * const
|
|
58163
|
-
lines.push(" * ...
|
|
58137
|
+
lines.push(" * import { applicationRouteExtensions } from './routes/clientRoutes.generated';");
|
|
58138
|
+
lines.push(" * const applicationRoutes = {");
|
|
58139
|
+
lines.push(" * ...applicationRouteExtensions,");
|
|
58164
58140
|
lines.push(" * // your additional routes...");
|
|
58165
58141
|
lines.push(" * };");
|
|
58166
58142
|
lines.push(" * ```");
|
|
58167
58143
|
lines.push(" */");
|
|
58168
|
-
lines.push("export const
|
|
58169
|
-
for (const
|
|
58170
|
-
lines.push(` ${
|
|
58144
|
+
lines.push("export const applicationRouteExtensions: Record<string, RouteObject[]> = {");
|
|
58145
|
+
for (const app of appKeys) {
|
|
58146
|
+
lines.push(` ${app}: ${app}Routes,`);
|
|
58171
58147
|
}
|
|
58172
58148
|
lines.push("};");
|
|
58173
58149
|
lines.push("");
|
|
58174
58150
|
return lines.join("\n");
|
|
58175
58151
|
}
|
|
58176
|
-
function getLayoutName(
|
|
58177
|
-
|
|
58178
|
-
platform: "AdminLayout",
|
|
58179
|
-
business: "BusinessLayout",
|
|
58180
|
-
personal: "UserLayout"
|
|
58181
|
-
};
|
|
58182
|
-
return layoutMap[context] || `${capitalize(context)}Layout`;
|
|
58152
|
+
function getLayoutName(_application) {
|
|
58153
|
+
return "AppLayout";
|
|
58183
58154
|
}
|
|
58184
58155
|
function toKebabCase(segment) {
|
|
58185
58156
|
return segment.replace(/([a-z0-9])([A-Z])/g, "$1-$2").toLowerCase();
|
|
@@ -58243,7 +58214,7 @@ Creates:
|
|
|
58243
58214
|
Example:
|
|
58244
58215
|
scaffold_routes source="controllers" scope="all"
|
|
58245
58216
|
|
|
58246
|
-
Scans backend controllers for [NavRoute("
|
|
58217
|
+
Scans backend controllers for [NavRoute("application.module.section")] attributes
|
|
58247
58218
|
and generates corresponding frontend routing infrastructure.`,
|
|
58248
58219
|
inputSchema: {
|
|
58249
58220
|
type: "object",
|
|
@@ -58260,9 +58231,8 @@ and generates corresponding frontend routing infrastructure.`,
|
|
|
58260
58231
|
},
|
|
58261
58232
|
scope: {
|
|
58262
58233
|
type: "string",
|
|
58263
|
-
enum: ["all", "platform", "business", "extensions"],
|
|
58264
58234
|
default: "all",
|
|
58265
|
-
description:
|
|
58235
|
+
description: 'Scope of routes to generate. Use "all" or a specific application name (e.g., "administration")'
|
|
58266
58236
|
},
|
|
58267
58237
|
options: {
|
|
58268
58238
|
type: "object",
|
|
@@ -58715,7 +58685,7 @@ function formatResult6(result, _input) {
|
|
|
58715
58685
|
lines.push('scaffold_routes source="controllers"');
|
|
58716
58686
|
lines.push("");
|
|
58717
58687
|
lines.push("# Generate API client for a specific NavRoute");
|
|
58718
|
-
lines.push('scaffold_api_client navRoute="
|
|
58688
|
+
lines.push('scaffold_api_client navRoute="administration.users" name="User"');
|
|
58719
58689
|
lines.push("```");
|
|
58720
58690
|
return lines.join("\n");
|
|
58721
58691
|
}
|
|
@@ -58974,7 +58944,7 @@ function generateTypesFile() {
|
|
|
58974
58944
|
export interface ExtensionConfig {
|
|
58975
58945
|
/**
|
|
58976
58946
|
* Page component overrides
|
|
58977
|
-
* Key: NavRoute path (e.g., '
|
|
58947
|
+
* Key: NavRoute path (e.g., 'administration.users')
|
|
58978
58948
|
* Value: React component to render instead of the default page
|
|
58979
58949
|
*/
|
|
58980
58950
|
pages?: Record<string, ComponentType<PageProps>>;
|
|
@@ -59426,28 +59396,28 @@ export function ExtendablePage({ pageKey, defaultComponent: DefaultComponent, pa
|
|
|
59426
59396
|
}
|
|
59427
59397
|
|
|
59428
59398
|
export const PAGE_KEYS = {
|
|
59429
|
-
USERS_LIST: '
|
|
59430
|
-
USERS_DETAIL: '
|
|
59431
|
-
ROLES_LIST: '
|
|
59432
|
-
ROLES_DETAIL: '
|
|
59433
|
-
PERMISSIONS_LIST: '
|
|
59434
|
-
PERMISSIONS_DETAIL: '
|
|
59435
|
-
ASSIGNMENTS: '
|
|
59436
|
-
AI_DASHBOARD: '
|
|
59437
|
-
AI_PROMPTS_LIST: '
|
|
59438
|
-
AI_PROMPTS_DETAIL: '
|
|
59439
|
-
WORKFLOWS_LIST: '
|
|
59440
|
-
WORKFLOWS_DETAIL: '
|
|
59441
|
-
EMAIL_TEMPLATES_LIST: '
|
|
59442
|
-
EMAIL_TEMPLATES_DETAIL: '
|
|
59443
|
-
TICKETS_LIST: '
|
|
59444
|
-
TICKETS_DETAIL: '
|
|
59445
|
-
SUPPORT_DASHBOARD: '
|
|
59446
|
-
USER_PROFILE: '
|
|
59447
|
-
USER_PREFERENCES: '
|
|
59448
|
-
USER_DASHBOARD: '
|
|
59449
|
-
MY_TICKETS_LIST: '
|
|
59450
|
-
MY_TICKETS_DETAIL: '
|
|
59399
|
+
USERS_LIST: 'administration.users',
|
|
59400
|
+
USERS_DETAIL: 'administration.users.detail',
|
|
59401
|
+
ROLES_LIST: 'administration.permissions.roles',
|
|
59402
|
+
ROLES_DETAIL: 'administration.permissions.roles.detail',
|
|
59403
|
+
PERMISSIONS_LIST: 'administration.permissions.permissions',
|
|
59404
|
+
PERMISSIONS_DETAIL: 'administration.permissions.permissions.detail',
|
|
59405
|
+
ASSIGNMENTS: 'administration.permissions.assignments',
|
|
59406
|
+
AI_DASHBOARD: 'administration.ai.dashboard',
|
|
59407
|
+
AI_PROMPTS_LIST: 'administration.ai.prompts',
|
|
59408
|
+
AI_PROMPTS_DETAIL: 'administration.ai.prompts.detail',
|
|
59409
|
+
WORKFLOWS_LIST: 'administration.workflows.list',
|
|
59410
|
+
WORKFLOWS_DETAIL: 'administration.workflows.detail',
|
|
59411
|
+
EMAIL_TEMPLATES_LIST: 'administration.workflows.email-templates',
|
|
59412
|
+
EMAIL_TEMPLATES_DETAIL: 'administration.workflows.email-templates.detail',
|
|
59413
|
+
TICKETS_LIST: 'support.tickets',
|
|
59414
|
+
TICKETS_DETAIL: 'support.tickets.detail',
|
|
59415
|
+
SUPPORT_DASHBOARD: 'support.dashboard',
|
|
59416
|
+
USER_PROFILE: 'myspace.profile',
|
|
59417
|
+
USER_PREFERENCES: 'myspace.preferences',
|
|
59418
|
+
USER_DASHBOARD: 'myspace.dashboard',
|
|
59419
|
+
MY_TICKETS_LIST: 'support-client.my-tickets',
|
|
59420
|
+
MY_TICKETS_DETAIL: 'support-client.my-tickets.detail',
|
|
59451
59421
|
} as const;
|
|
59452
59422
|
|
|
59453
59423
|
export type PageKey = typeof PAGE_KEYS[keyof typeof PAGE_KEYS];
|
|
@@ -60857,7 +60827,7 @@ async function checkAuthorization(structure, result) {
|
|
|
60857
60827
|
message: `Controller ${controllerName} missing authorization attribute`,
|
|
60858
60828
|
file: path23.relative(structure.root, file),
|
|
60859
60829
|
line: lineNumber,
|
|
60860
|
-
suggestion: 'Add [NavRoute("
|
|
60830
|
+
suggestion: 'Add [NavRoute("application.module.section")] or [Authorize] attribute to the controller class',
|
|
60861
60831
|
cweId: "CWE-862"
|
|
60862
60832
|
});
|
|
60863
60833
|
}
|
|
@@ -64764,7 +64734,7 @@ Tables are organized by domain using prefixes:
|
|
|
64764
64734
|
| Prefix | Domain | Example Tables |
|
|
64765
64735
|
|--------|--------|----------------|
|
|
64766
64736
|
| \`auth_\` | Authorization | auth_Users, auth_Roles, auth_Permissions |
|
|
64767
|
-
| \`nav_\` | Navigation |
|
|
64737
|
+
| \`nav_\` | Navigation | nav_Applications, nav_Modules, nav_Sections, nav_Resources |
|
|
64768
64738
|
| \`usr_\` | User profiles | usr_Profiles, usr_Preferences |
|
|
64769
64739
|
| \`ai_\` | AI features | ai_Providers, ai_Models, ai_Prompts |
|
|
64770
64740
|
| \`cfg_\` | Configuration | cfg_Settings |
|
|
@@ -64798,7 +64768,7 @@ In addition to the platform prefixes above, client applications define their own
|
|
|
64798
64768
|
|
|
64799
64769
|
### Navigation Scope System
|
|
64800
64770
|
|
|
64801
|
-
All navigation data (
|
|
64771
|
+
All navigation data (Application, Module, Section, Resource) uses a **Scope** field to distinguish system data from client extensions:
|
|
64802
64772
|
|
|
64803
64773
|
| Scope | Usage | Description | Example Code |
|
|
64804
64774
|
|-------|-------|-------------|--------------|
|
|
@@ -64810,17 +64780,17 @@ All navigation data (Context, Application, Module, Section) uses a **Scope** fie
|
|
|
64810
64780
|
**Navigation Hierarchy (4 levels):**
|
|
64811
64781
|
|
|
64812
64782
|
\`\`\`
|
|
64813
|
-
|
|
64783
|
+
Application \u2192 Module \u2192 Section \u2192 Resource (optional)
|
|
64814
64784
|
\`\`\`
|
|
64815
64785
|
|
|
64816
64786
|
**Examples for each level:**
|
|
64817
64787
|
|
|
64818
64788
|
| Level | Core Example | Extension Example |
|
|
64819
64789
|
|-------|--------------|-------------------|
|
|
64820
|
-
| Context | Code: \`platform\`<br/>Scope: \`${scopeTypes[0]}\` | Code: \`business\`<br/>Scope: \`${scopeTypes[1]}\` |
|
|
64821
64790
|
| Application | Code: \`administration\`<br/>Scope: \`${scopeTypes[0]}\` | Code: \`custom_app\`<br/>Scope: \`${scopeTypes[1]}\` |
|
|
64822
|
-
| Module | Code: \`users\`<br/>Route: \`
|
|
64791
|
+
| Module | Code: \`users\`<br/>Route: \`administration.users.management\`<br/>Scope: \`${scopeTypes[0]}\` | Code: \`inventory\`<br/>Route: \`operations.inventory.list\`<br/>Scope: \`${scopeTypes[1]}\` |
|
|
64823
64792
|
| Section | Code: \`management\`<br/>Scope: \`${scopeTypes[0]}\` | Code: \`reports\`<br/>Scope: \`${scopeTypes[1]}\` |
|
|
64793
|
+
| Resource (optional) | Code: \`dashboard\`<br/>Scope: \`${scopeTypes[0]}\` | Code: \`analytics\`<br/>Scope: \`${scopeTypes[1]}\` |
|
|
64824
64794
|
|
|
64825
64795
|
**Rules:**
|
|
64826
64796
|
1. \`${scopeTypes[0]}\` modules are **protected** - clients cannot create or modify them
|
|
@@ -64844,18 +64814,18 @@ Permissions and Roles also use the **Scope** field for the same categorization:
|
|
|
64844
64814
|
Permissions inherit their Scope from the module's NavRoute:
|
|
64845
64815
|
|
|
64846
64816
|
\`\`\`
|
|
64847
|
-
NavRoute: "
|
|
64817
|
+
NavRoute: "administration.users"
|
|
64848
64818
|
Module Scope: Core
|
|
64849
64819
|
\u2192 Permissions Scope: Core
|
|
64850
|
-
-
|
|
64851
|
-
-
|
|
64852
|
-
-
|
|
64853
|
-
-
|
|
64820
|
+
- administration.users.read (Core)
|
|
64821
|
+
- administration.users.create (Core)
|
|
64822
|
+
- administration.users.update (Core)
|
|
64823
|
+
- administration.users.delete (Core)
|
|
64854
64824
|
\`\`\`
|
|
64855
64825
|
|
|
64856
64826
|
**Detection Rules:**
|
|
64857
|
-
- Routes
|
|
64858
|
-
- Routes
|
|
64827
|
+
- Routes for platform applications (administration, support, etc.) \u2192 \`${scopeTypes[0]}\`
|
|
64828
|
+
- Routes for client/custom applications \u2192 \`${scopeTypes[1]}\`
|
|
64859
64829
|
- Partner-provided routes \u2192 \`${scopeTypes[2]}\`
|
|
64860
64830
|
- Community routes \u2192 \`${scopeTypes[3]}\`
|
|
64861
64831
|
|
|
@@ -65638,7 +65608,7 @@ All tenant-related tables use the \`tenant_\` prefix:
|
|
|
65638
65608
|
|
|
65639
65609
|
### Layout Standards
|
|
65640
65610
|
|
|
65641
|
-
All
|
|
65611
|
+
All applications use the unified AppLayout for consistent user experience.
|
|
65642
65612
|
|
|
65643
65613
|
#### Standard Structure
|
|
65644
65614
|
|
|
@@ -65760,8 +65730,8 @@ import { Breadcrumb } from '@/components/ui/Breadcrumb';
|
|
|
65760
65730
|
// In your page component
|
|
65761
65731
|
<Breadcrumb
|
|
65762
65732
|
items={[
|
|
65763
|
-
{ label: t('nav.administration'), href: '/
|
|
65764
|
-
{ label: t('nav.users'), href: '/
|
|
65733
|
+
{ label: t('nav.administration'), href: '/administration' },
|
|
65734
|
+
{ label: t('nav.users'), href: '/administration/users' },
|
|
65765
65735
|
{ label: user.fullName } // Last item = current page (no href)
|
|
65766
65736
|
]}
|
|
65767
65737
|
/>
|
|
@@ -65771,7 +65741,7 @@ import { Breadcrumb } from '@/components/ui/Breadcrumb';
|
|
|
65771
65741
|
|
|
65772
65742
|
| Rule | Description |
|
|
65773
65743
|
|------|-------------|
|
|
65774
|
-
| First item |
|
|
65744
|
+
| First item | Application level with Home icon (auto-added if no icon) |
|
|
65775
65745
|
| Intermediate items | Clickable links to parent pages |
|
|
65776
65746
|
| Last item | Current page name, no href, bold text |
|
|
65777
65747
|
| Labels | Use i18n translation keys from \`navigation.json\` |
|
|
@@ -65780,7 +65750,7 @@ import { Breadcrumb } from '@/components/ui/Breadcrumb';
|
|
|
65780
65750
|
#### Hierarchy Pattern
|
|
65781
65751
|
|
|
65782
65752
|
\`\`\`
|
|
65783
|
-
|
|
65753
|
+
Application > Module > [Section] > [Resource] > [Entity Name]
|
|
65784
65754
|
\`\`\`
|
|
65785
65755
|
|
|
65786
65756
|
**Examples:**
|