@atlashub/smartstack-cli 3.36.0 → 3.38.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 +201 -256
- package/dist/mcp-entry.mjs.map +1 -1
- package/package.json +3 -2
- package/scripts/extract-api-endpoints.ts +325 -0
- package/scripts/extract-business-rules.ts +440 -0
- package/scripts/generate-doc-with-mock-ui.ts +804 -0
- package/scripts/health-check.sh +168 -0
- package/scripts/postinstall.js +18 -0
- 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/gitflow/merge.md +0 -4
- package/templates/agents/gitflow/pr.md +0 -4
- package/templates/agents/gitflow/start.md +30 -5
- 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 -57
- package/templates/skills/apex/references/examine-build-validation.md +87 -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 +17 -17
- 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 +47 -249
- 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 +2 -0
- package/templates/skills/application/SKILL.md +6 -7
- 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 +120 -0
- package/templates/skills/application/references/migration-checklist-troubleshooting.md +100 -0
- package/templates/skills/application/references/nav-fallback-procedure.md +5 -6
- 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 +11 -141
- package/templates/skills/application/steps/step-01-navigation.md +3 -3
- package/templates/skills/application/steps/step-02-permissions.md +4 -4
- package/templates/skills/application/steps/step-03-roles.md +18 -175
- package/templates/skills/application/steps/step-03b-provider.md +1 -2
- package/templates/skills/application/steps/step-04-backend.md +19 -110
- package/templates/skills/application/steps/step-05-frontend.md +17 -143
- 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 +48 -48
- 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 +11 -11
- 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-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/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-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 +1 -1
- package/templates/skills/gitflow/_shared.md +23 -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 +51 -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 +71 -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 +21 -73
- package/templates/skills/review-code/references/owasp-api-top10.md +5 -5
- package/templates/skills/review-code/references/smartstack-conventions.md +11 -11
- 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 +5 -2
- 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"),
|
|
@@ -27191,7 +27191,7 @@ async function validateControllerRoutes(structure, _config, result) {
|
|
|
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 should have at least 2 levels: "
|
|
27194
|
+
suggestion: 'NavRoute should have at least 2 levels: "application.module" (e.g., "administration.users")'
|
|
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")] 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,21 @@ 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: "", 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 module = segments[2] ? toPascal(segments[2]) : segments[1] ? toPascal(segments[1]) : "";
|
|
34430
|
+
const application = segments[0] ? toPascal(segments[0]) : "";
|
|
34431
|
+
const module = segments[1] ? toPascal(segments[1]) : "";
|
|
34442
34432
|
let domainPath = "";
|
|
34443
|
-
if (segments.length >=
|
|
34444
|
-
domainPath = path10.join(
|
|
34445
|
-
} else if (segments.length === 2) {
|
|
34446
|
-
domainPath = path10.join(context, module);
|
|
34433
|
+
if (segments.length >= 2) {
|
|
34434
|
+
domainPath = path10.join(application, module);
|
|
34447
34435
|
} else if (segments.length === 1) {
|
|
34448
|
-
domainPath =
|
|
34436
|
+
domainPath = application;
|
|
34449
34437
|
}
|
|
34450
34438
|
const infraPath = domainPath;
|
|
34451
|
-
const controllerArea =
|
|
34452
|
-
return {
|
|
34439
|
+
const controllerArea = application;
|
|
34440
|
+
return { application, module, domainPath, infraPath, controllerArea };
|
|
34453
34441
|
}
|
|
34454
34442
|
async function handleScaffoldExtension(args, config2) {
|
|
34455
34443
|
const input = ScaffoldExtensionInputSchema.parse(args);
|
|
@@ -34596,7 +34584,7 @@ async function scaffoldFeature(name, options, structure, config2, result, dryRun
|
|
|
34596
34584
|
result.instructions.push(`${withRepository ? withValidation ? "6" : "5" : withValidation ? "5" : "4"}. Run migration: \`dotnet ef database update --context ${dbContextName}\``);
|
|
34597
34585
|
if (!skipComponent) {
|
|
34598
34586
|
const featureHierarchy = resolveHierarchy(options?.navRoute);
|
|
34599
|
-
const featureComponentPath = featureHierarchy.
|
|
34587
|
+
const featureComponentPath = featureHierarchy.application && featureHierarchy.module ? `@/components/${featureHierarchy.application.toLowerCase()}/${featureHierarchy.module.toLowerCase()}/${name}` : `./components/${name}`;
|
|
34600
34588
|
result.instructions.push(`Import component: \`import { ${name} } from '${featureComponentPath}';\``);
|
|
34601
34589
|
}
|
|
34602
34590
|
}
|
|
@@ -35413,12 +35401,12 @@ public class {{name}}Controller : ControllerBase
|
|
|
35413
35401
|
result.instructions.push("");
|
|
35414
35402
|
result.instructions.push("NavRoute resolves API routes from Navigation entities in the database.");
|
|
35415
35403
|
result.instructions.push("Ensure the navigation path exists (seed data required):");
|
|
35416
|
-
result.instructions.push(`
|
|
35404
|
+
result.instructions.push(` Application > Module matching "${navRoute}"`);
|
|
35417
35405
|
} else {
|
|
35418
35406
|
result.instructions.push("Controller created with traditional routing.");
|
|
35419
35407
|
result.instructions.push("");
|
|
35420
35408
|
result.instructions.push("\u26A0\uFE0F Consider using NavRoute for navigation-based routing:");
|
|
35421
|
-
result.instructions.push(` [NavRoute("
|
|
35409
|
+
result.instructions.push(` [NavRoute("application.module")]`);
|
|
35422
35410
|
result.instructions.push("");
|
|
35423
35411
|
result.instructions.push("API endpoints (with traditional routing):");
|
|
35424
35412
|
result.instructions.push(` GET /api/${name.toLowerCase()}`);
|
|
@@ -35583,7 +35571,7 @@ export function use{{name}}(options: Use{{name}}Options = {}) {
|
|
|
35583
35571
|
const projectRoot = config2.smartstack.projectPath;
|
|
35584
35572
|
const webPath = structure.web || path10.join(projectRoot, "web");
|
|
35585
35573
|
const componentsBase = path10.join(webPath, "src", "components");
|
|
35586
|
-
const componentsPath = options?.outputPath ? options.outputPath : hierarchy.
|
|
35574
|
+
const componentsPath = options?.outputPath ? options.outputPath : hierarchy.application && hierarchy.module ? path10.join(componentsBase, hierarchy.application.toLowerCase(), hierarchy.module.toLowerCase()) : componentsBase;
|
|
35587
35575
|
const hooksPath = path10.join(webPath, "src", "hooks");
|
|
35588
35576
|
const componentFilePath = path10.join(componentsPath, `${name}.tsx`);
|
|
35589
35577
|
const hookFilePath = path10.join(hooksPath, `use${name}.ts`);
|
|
@@ -35598,7 +35586,7 @@ export function use{{name}}(options: Use{{name}}Options = {}) {
|
|
|
35598
35586
|
result.files.push({ path: componentFilePath, content: componentContent, type: "created" });
|
|
35599
35587
|
result.files.push({ path: hookFilePath, content: hookContent, type: "created" });
|
|
35600
35588
|
result.instructions.push("Import and use the component:");
|
|
35601
|
-
const componentImportPath = hierarchy.
|
|
35589
|
+
const componentImportPath = hierarchy.application && hierarchy.module ? `@/components/${hierarchy.application.toLowerCase()}/${hierarchy.module.toLowerCase()}/${name}` : `./components/${name}`;
|
|
35602
35590
|
result.instructions.push(`import { ${name} } from '${componentImportPath}';`);
|
|
35603
35591
|
result.instructions.push(`import { use${name} } from '@/hooks/use${name}';`);
|
|
35604
35592
|
result.instructions.push("");
|
|
@@ -35614,7 +35602,7 @@ export function use{{name}}(options: Use{{name}}Options = {}) {
|
|
|
35614
35602
|
result.instructions.push('import { EntityLookup } from "@/components/ui/EntityLookup";');
|
|
35615
35603
|
result.instructions.push("");
|
|
35616
35604
|
result.instructions.push("<EntityLookup");
|
|
35617
|
-
result.instructions.push(' apiEndpoint="/api/{
|
|
35605
|
+
result.instructions.push(' apiEndpoint="/api/{app}/{related-entity}"');
|
|
35618
35606
|
result.instructions.push(" value={formData.relatedEntityId}");
|
|
35619
35607
|
result.instructions.push(' onChange={(id) => handleChange("relatedEntityId", id)}');
|
|
35620
35608
|
result.instructions.push(' label="Related Entity"');
|
|
@@ -36482,7 +36470,7 @@ var init_scaffold_extension = __esm({
|
|
|
36482
36470
|
},
|
|
36483
36471
|
navRoute: {
|
|
36484
36472
|
type: "string",
|
|
36485
|
-
description: 'Navigation route path for controller (e.g., "
|
|
36473
|
+
description: 'Navigation route path for controller (e.g., "administration.users"). Required for controllers.'
|
|
36486
36474
|
},
|
|
36487
36475
|
navRouteSuffix: {
|
|
36488
36476
|
type: "string",
|
|
@@ -53028,16 +53016,16 @@ async function handleGeneratePermissions(args, config2) {
|
|
|
53028
53016
|
function generatePermissionsForNavRoute(navRoute, customActions, includeStandardActions, includeWildcard = true) {
|
|
53029
53017
|
const permissions = [];
|
|
53030
53018
|
const parts = navRoute.split(".");
|
|
53031
|
-
const
|
|
53032
|
-
if (parts.length <
|
|
53033
|
-
throw new Error(`Invalid NavRoute format: ${navRoute}. Expected format:
|
|
53019
|
+
const application = parts[0];
|
|
53020
|
+
if (parts.length < 2) {
|
|
53021
|
+
throw new Error(`Invalid NavRoute format: ${navRoute}. Expected format: application.module`);
|
|
53034
53022
|
}
|
|
53035
53023
|
if (includeWildcard) {
|
|
53036
53024
|
permissions.push({
|
|
53037
53025
|
code: `${navRoute}.*`,
|
|
53038
53026
|
name: formatPermissionName(navRoute, "Full Access"),
|
|
53039
53027
|
description: `Full ${parts[parts.length - 1]} management`,
|
|
53040
|
-
category:
|
|
53028
|
+
category: application
|
|
53041
53029
|
});
|
|
53042
53030
|
}
|
|
53043
53031
|
for (const customAction of customActions) {
|
|
@@ -53057,7 +53045,7 @@ function generatePermissionsForNavRoute(navRoute, customActions, includeStandard
|
|
|
53057
53045
|
code,
|
|
53058
53046
|
name,
|
|
53059
53047
|
description,
|
|
53060
|
-
category:
|
|
53048
|
+
category: application
|
|
53061
53049
|
});
|
|
53062
53050
|
}
|
|
53063
53051
|
return permissions;
|
|
@@ -53138,14 +53126,12 @@ function formatPermissionName(navRoute, action) {
|
|
|
53138
53126
|
}
|
|
53139
53127
|
function formatPermissionDescription(navRoute, action) {
|
|
53140
53128
|
const parts = navRoute.split(".");
|
|
53141
|
-
const
|
|
53142
|
-
const
|
|
53143
|
-
const module = parts[2];
|
|
53144
|
-
const contextName = context.charAt(0).toUpperCase() + context.slice(1);
|
|
53129
|
+
const application = parts[0];
|
|
53130
|
+
const module = parts[1];
|
|
53145
53131
|
const applicationName = application.split(/[-_]/).map((word) => word.charAt(0).toUpperCase() + word.slice(1)).join(" ");
|
|
53146
53132
|
const moduleName = module.split(/[-_]/).map((word) => word.charAt(0).toUpperCase() + word.slice(1)).join(" ");
|
|
53147
53133
|
const actionVerb = getActionVerb(action);
|
|
53148
|
-
return `${actionVerb} ${moduleName} in ${
|
|
53134
|
+
return `${actionVerb} ${moduleName} in ${applicationName}`;
|
|
53149
53135
|
}
|
|
53150
53136
|
function getActionVerb(action) {
|
|
53151
53137
|
const verbs = {
|
|
@@ -53200,7 +53186,7 @@ function getUniqueNavRouteCount(permissions) {
|
|
|
53200
53186
|
}
|
|
53201
53187
|
function generateHasDataCode(permissions, navRoute) {
|
|
53202
53188
|
const parts = navRoute.split(".");
|
|
53203
|
-
const moduleName = parts.length >=
|
|
53189
|
+
const moduleName = parts.length >= 2 ? parts[parts.length - 1] : "custom";
|
|
53204
53190
|
const moduleVarName = `${moduleName}ModuleId`;
|
|
53205
53191
|
let code = "```csharp\n";
|
|
53206
53192
|
code += `// 1. Add module ID variable (get from NavigationModuleSeedData.cs)
|
|
@@ -53289,13 +53275,13 @@ IMPORTANT: This tool does NOT generate migrations with raw SQL (forbidden by Sma
|
|
|
53289
53275
|
Instead, it outputs HasData() code that must be manually added to the Configuration file.
|
|
53290
53276
|
|
|
53291
53277
|
Example:
|
|
53292
|
-
navRoute: "
|
|
53278
|
+
navRoute: "administration.entra"
|
|
53293
53279
|
Outputs HasData() code for:
|
|
53294
|
-
-
|
|
53295
|
-
-
|
|
53296
|
-
-
|
|
53297
|
-
-
|
|
53298
|
-
-
|
|
53280
|
+
- administration.entra.*
|
|
53281
|
+
- administration.entra.read
|
|
53282
|
+
- administration.entra.create
|
|
53283
|
+
- administration.entra.update
|
|
53284
|
+
- administration.entra.delete
|
|
53299
53285
|
|
|
53300
53286
|
After adding to PermissionConfiguration.cs, run: dotnet ef migrations add <MigrationName>`,
|
|
53301
53287
|
inputSchema: {
|
|
@@ -53303,7 +53289,7 @@ After adding to PermissionConfiguration.cs, run: dotnet ef migrations add <Migra
|
|
|
53303
53289
|
properties: {
|
|
53304
53290
|
navRoute: {
|
|
53305
53291
|
type: "string",
|
|
53306
|
-
description: 'NavRoute path (e.g., "
|
|
53292
|
+
description: 'NavRoute path (e.g., "administration.entra"). If not provided, scans all controllers.'
|
|
53307
53293
|
},
|
|
53308
53294
|
actions: {
|
|
53309
53295
|
type: "array",
|
|
@@ -53318,7 +53304,7 @@ After adding to PermissionConfiguration.cs, run: dotnet ef migrations add <Migra
|
|
|
53318
53304
|
includeWildcard: {
|
|
53319
53305
|
type: "boolean",
|
|
53320
53306
|
default: true,
|
|
53321
|
-
description: "Include wildcard permission (e.g.,
|
|
53307
|
+
description: "Include wildcard permission (e.g., myspace.tenants.*)"
|
|
53322
53308
|
}
|
|
53323
53309
|
}
|
|
53324
53310
|
}
|
|
@@ -57383,7 +57369,7 @@ Creates:
|
|
|
57383
57369
|
- Integration with navRoutes.generated.ts registry
|
|
57384
57370
|
|
|
57385
57371
|
Example:
|
|
57386
|
-
scaffold_api_client navRoute="
|
|
57372
|
+
scaffold_api_client navRoute="administration.users" name="User"
|
|
57387
57373
|
|
|
57388
57374
|
The generated client automatically resolves the API path from the NavRoute registry,
|
|
57389
57375
|
ensuring frontend routes stay synchronized with backend NavRoute attributes.`,
|
|
@@ -57396,7 +57382,7 @@ ensuring frontend routes stay synchronized with backend NavRoute attributes.`,
|
|
|
57396
57382
|
},
|
|
57397
57383
|
navRoute: {
|
|
57398
57384
|
type: "string",
|
|
57399
|
-
description: 'NavRoute path (e.g., "
|
|
57385
|
+
description: 'NavRoute path (e.g., "administration.users")'
|
|
57400
57386
|
},
|
|
57401
57387
|
name: {
|
|
57402
57388
|
type: "string",
|
|
@@ -57493,9 +57479,9 @@ async function scaffoldRoutes(input, config2) {
|
|
|
57493
57479
|
result.instructions.push("**Detect App.tsx pattern first**, then follow the matching instructions:");
|
|
57494
57480
|
result.instructions.push("");
|
|
57495
57481
|
const routeTree = buildRouteTree(navRoutes);
|
|
57496
|
-
result.instructions.push("### Pattern A: mergeRoutes (
|
|
57482
|
+
result.instructions.push("### Pattern A: mergeRoutes (applicationRoutes pattern)");
|
|
57497
57483
|
result.instructions.push("");
|
|
57498
|
-
result.instructions.push("Add routes to `
|
|
57484
|
+
result.instructions.push("Add routes to `applicationRoutes.{application}[]` with **RELATIVE** paths (no leading `/`):");
|
|
57499
57485
|
result.instructions.push("");
|
|
57500
57486
|
result.instructions.push("**IMPORTANT:** Pages are lazy-loaded. Use `<Suspense fallback={<PageLoader />}>` wrapper.");
|
|
57501
57487
|
result.instructions.push("");
|
|
@@ -57504,9 +57490,9 @@ async function scaffoldRoutes(input, config2) {
|
|
|
57504
57490
|
result.instructions.push("import { PageLoader } from '@/components/ui/PageLoader';");
|
|
57505
57491
|
result.instructions.push("");
|
|
57506
57492
|
const importedComponents = /* @__PURE__ */ new Set();
|
|
57507
|
-
for (const [
|
|
57508
|
-
for (const [,
|
|
57509
|
-
for (const route of
|
|
57493
|
+
for (const [_app, modules] of Object.entries(routeTree)) {
|
|
57494
|
+
for (const [, moduleRoutes] of Object.entries(modules)) {
|
|
57495
|
+
for (const route of moduleRoutes) {
|
|
57510
57496
|
const pageEntry = pageFiles.get(route.navRoute);
|
|
57511
57497
|
if (pageEntry) {
|
|
57512
57498
|
for (const entry of pageEntry) {
|
|
@@ -57528,11 +57514,11 @@ async function scaffoldRoutes(input, config2) {
|
|
|
57528
57514
|
}
|
|
57529
57515
|
}
|
|
57530
57516
|
result.instructions.push("");
|
|
57531
|
-
result.instructions.push("const
|
|
57532
|
-
for (const [
|
|
57533
|
-
result.instructions.push(` ${
|
|
57534
|
-
for (const [,
|
|
57535
|
-
for (const route of
|
|
57517
|
+
result.instructions.push("const applicationRoutes = {");
|
|
57518
|
+
for (const [app, modules] of Object.entries(routeTree)) {
|
|
57519
|
+
result.instructions.push(` '${app}': [`);
|
|
57520
|
+
for (const [, moduleRoutes] of Object.entries(modules)) {
|
|
57521
|
+
for (const route of moduleRoutes) {
|
|
57536
57522
|
const modulePath = route.navRoute.split(".").slice(1).map(toKebabCase).join("/");
|
|
57537
57523
|
const pageEntry = pageFiles.get(route.navRoute);
|
|
57538
57524
|
const component = pageEntry?.[0]?.componentName || `${route.navRoute.split(".").map(capitalize).join("")}Page`;
|
|
@@ -57545,20 +57531,19 @@ async function scaffoldRoutes(input, config2) {
|
|
|
57545
57531
|
result.instructions.push("```");
|
|
57546
57532
|
result.instructions.push("");
|
|
57547
57533
|
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
57534
|
result.instructions.push("");
|
|
57550
|
-
result.instructions.push('### Pattern B: JSX Routes (if App.tsx uses `<Route path="/{
|
|
57535
|
+
result.instructions.push('### Pattern B: JSX Routes (if App.tsx uses `<Route path="/{application}" element={<{Layout} />}>`)');
|
|
57551
57536
|
result.instructions.push("");
|
|
57552
57537
|
result.instructions.push("Insert `<Route>` children INSIDE the appropriate Layout wrapper.");
|
|
57553
57538
|
result.instructions.push("**IMPORTANT:** Use `<Suspense fallback={<PageLoader />}>` for lazy-loaded pages.");
|
|
57554
57539
|
result.instructions.push("");
|
|
57555
|
-
for (const [
|
|
57556
|
-
const layoutName = getLayoutName(
|
|
57557
|
-
result.instructions.push(`#### ${capitalize(
|
|
57540
|
+
for (const [app, modules] of Object.entries(routeTree)) {
|
|
57541
|
+
const layoutName = getLayoutName(app);
|
|
57542
|
+
result.instructions.push(`#### ${capitalize(app)} application (inside \`<Route path="/${app}" element={<${layoutName} />}>\`):`);
|
|
57558
57543
|
result.instructions.push("");
|
|
57559
57544
|
result.instructions.push("```tsx");
|
|
57560
|
-
for (const [,
|
|
57561
|
-
for (const route of
|
|
57545
|
+
for (const [, moduleRoutes] of Object.entries(modules)) {
|
|
57546
|
+
for (const route of moduleRoutes) {
|
|
57562
57547
|
const modulePath = route.navRoute.split(".").slice(1).map(toKebabCase).join("/");
|
|
57563
57548
|
const pageEntry = pageFiles.get(route.navRoute);
|
|
57564
57549
|
const component = pageEntry?.[0]?.componentName || `${route.navRoute.split(".").map(capitalize).join("")}Page`;
|
|
@@ -57580,10 +57565,10 @@ async function scaffoldRoutes(input, config2) {
|
|
|
57580
57565
|
result.files.push({ path: routerFile, content: routerContent, type: "created" });
|
|
57581
57566
|
if (includeLayouts) {
|
|
57582
57567
|
const layoutsPath = path19.join(webPath, "src", "layouts");
|
|
57583
|
-
const
|
|
57584
|
-
for (const
|
|
57585
|
-
const layoutContent = generateLayout(
|
|
57586
|
-
const layoutFile = path19.join(layoutsPath, `${capitalize(
|
|
57568
|
+
const applications = [...new Set(navRoutes.map((r) => r.navRoute.split(".")[0]))];
|
|
57569
|
+
for (const application of applications) {
|
|
57570
|
+
const layoutContent = generateLayout(application);
|
|
57571
|
+
const layoutFile = path19.join(layoutsPath, `${capitalize(application)}Layout.tsx`);
|
|
57587
57572
|
if (!dryRun) {
|
|
57588
57573
|
await ensureDirectory(layoutsPath);
|
|
57589
57574
|
await writeText(layoutFile, layoutContent);
|
|
@@ -57644,8 +57629,8 @@ async function discoverNavRoutes(structure, scope, warnings) {
|
|
|
57644
57629
|
if (navRouteMatch) {
|
|
57645
57630
|
const navRoute = navRouteMatch[1];
|
|
57646
57631
|
const suffix = navRouteMatch[2];
|
|
57647
|
-
const
|
|
57648
|
-
if (scope !== "all" &&
|
|
57632
|
+
const application = navRoute.split(".")[0];
|
|
57633
|
+
if (scope !== "all" && application !== scope) {
|
|
57649
57634
|
continue;
|
|
57650
57635
|
}
|
|
57651
57636
|
const controllerMatch = path19.basename(file).match(/(.+)Controller\.cs$/);
|
|
@@ -57738,28 +57723,28 @@ function generateNavRouteRegistry(routes) {
|
|
|
57738
57723
|
lines.push("}");
|
|
57739
57724
|
lines.push("");
|
|
57740
57725
|
lines.push("/**");
|
|
57741
|
-
lines.push(" * Get all routes for
|
|
57726
|
+
lines.push(" * Get all routes for an application");
|
|
57742
57727
|
lines.push(" */");
|
|
57743
|
-
lines.push("export function
|
|
57744
|
-
lines.push(" return Object.values(ROUTES).filter(r => r.navRoute.startsWith(`${
|
|
57728
|
+
lines.push("export function getRoutesByApplication(application: string): NavRoute[] {");
|
|
57729
|
+
lines.push(" return Object.values(ROUTES).filter(r => r.navRoute.startsWith(`${application}.`));");
|
|
57745
57730
|
lines.push("}");
|
|
57746
57731
|
lines.push("");
|
|
57747
57732
|
lines.push("/**");
|
|
57748
57733
|
lines.push(" * Get web route with optional tenant slug prefix");
|
|
57749
57734
|
lines.push(" *");
|
|
57750
|
-
lines.push(" * @param navRoute - NavRoute path (e.g., '
|
|
57735
|
+
lines.push(" * @param navRoute - NavRoute path (e.g., 'administration.users')");
|
|
57751
57736
|
lines.push(" * @param options - Configuration for URL building");
|
|
57752
57737
|
lines.push(" * @returns Web route path, optionally prefixed with /t/{slug}");
|
|
57753
57738
|
lines.push(" *");
|
|
57754
57739
|
lines.push(" * @example");
|
|
57755
57740
|
lines.push(" * // Single-tenant mode or no tenant");
|
|
57756
|
-
lines.push(" * getWebRoute('
|
|
57757
|
-
lines.push(" * // Returns: '/
|
|
57741
|
+
lines.push(" * getWebRoute('administration.users', { isSingleTenant: true })");
|
|
57742
|
+
lines.push(" * // Returns: '/administration/users'");
|
|
57758
57743
|
lines.push(" *");
|
|
57759
57744
|
lines.push(" * @example");
|
|
57760
57745
|
lines.push(" * // Multi-tenant mode with slug");
|
|
57761
|
-
lines.push(" * getWebRoute('
|
|
57762
|
-
lines.push(" * // Returns: '/t/acme/
|
|
57746
|
+
lines.push(" * getWebRoute('administration.users', { slug: 'acme', isSingleTenant: false })");
|
|
57747
|
+
lines.push(" * // Returns: '/t/acme/administration/users'");
|
|
57763
57748
|
lines.push(" */");
|
|
57764
57749
|
lines.push("export function getWebRoute(");
|
|
57765
57750
|
lines.push(" navRoute: string,");
|
|
@@ -57804,9 +57789,9 @@ function generateRouterConfig(routes, includeGuards) {
|
|
|
57804
57789
|
if (includeGuards) {
|
|
57805
57790
|
lines.push("import { ProtectedRoute, PermissionGuard } from './guards';");
|
|
57806
57791
|
}
|
|
57807
|
-
const
|
|
57808
|
-
for (const
|
|
57809
|
-
const name = `${capitalize(
|
|
57792
|
+
const applications = Object.keys(routeTree);
|
|
57793
|
+
for (const app of applications) {
|
|
57794
|
+
const name = `${capitalize(app)}Layout`;
|
|
57810
57795
|
lines.push(`const ${name} = lazy(() => import('../layouts/${name}').then(m => ({ default: m.${name} })));`);
|
|
57811
57796
|
}
|
|
57812
57797
|
lines.push("");
|
|
@@ -57817,37 +57802,32 @@ function generateRouterConfig(routes, includeGuards) {
|
|
|
57817
57802
|
}
|
|
57818
57803
|
lines.push("");
|
|
57819
57804
|
lines.push("const routes: RouteObject[] = [");
|
|
57820
|
-
for (const [
|
|
57805
|
+
for (const [app, modules] of Object.entries(routeTree)) {
|
|
57821
57806
|
lines.push(" {");
|
|
57822
|
-
lines.push(` path: '${
|
|
57823
|
-
lines.push(` element: <Suspense fallback={<PageLoader />}><${capitalize(
|
|
57807
|
+
lines.push(` path: '${app}',`);
|
|
57808
|
+
lines.push(` element: <Suspense fallback={<PageLoader />}><${capitalize(app)}Layout /></Suspense>,`);
|
|
57824
57809
|
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("/");
|
|
57810
|
+
for (const [, moduleRoutes] of Object.entries(modules)) {
|
|
57811
|
+
for (const route of moduleRoutes) {
|
|
57812
|
+
const modulePath = route.navRoute.split(".").slice(1).map(toKebabCase).join("/");
|
|
57831
57813
|
const pageName = route.navRoute.split(".").map(capitalize).join("");
|
|
57832
57814
|
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("
|
|
57815
|
+
lines.push(" {");
|
|
57816
|
+
lines.push(` path: '${modulePath || ""}',`);
|
|
57817
|
+
lines.push(` element: (`);
|
|
57818
|
+
lines.push(` <PermissionGuard permissions={ROUTES['${route.navRoute}'].permissions}>`);
|
|
57819
|
+
lines.push(` {/* <${pageName}Page /> */}`);
|
|
57820
|
+
lines.push(` <div>TODO: ${pageName}Page</div>`);
|
|
57821
|
+
lines.push(` </PermissionGuard>`);
|
|
57822
|
+
lines.push(` ),`);
|
|
57823
|
+
lines.push(" },");
|
|
57842
57824
|
} else {
|
|
57843
|
-
lines.push("
|
|
57844
|
-
lines.push(`
|
|
57845
|
-
lines.push(`
|
|
57846
|
-
lines.push("
|
|
57825
|
+
lines.push(" {");
|
|
57826
|
+
lines.push(` path: '${modulePath || ""}',`);
|
|
57827
|
+
lines.push(` element: <div>TODO: ${pageName}Page</div>,`);
|
|
57828
|
+
lines.push(" },");
|
|
57847
57829
|
}
|
|
57848
57830
|
}
|
|
57849
|
-
lines.push(" ],");
|
|
57850
|
-
lines.push(" },");
|
|
57851
57831
|
}
|
|
57852
57832
|
lines.push(" ],");
|
|
57853
57833
|
lines.push(" },");
|
|
@@ -57864,44 +57844,44 @@ function buildRouteTree(routes) {
|
|
|
57864
57844
|
const tree = {};
|
|
57865
57845
|
for (const route of routes) {
|
|
57866
57846
|
const parts = route.navRoute.split(".");
|
|
57867
|
-
const
|
|
57868
|
-
const
|
|
57869
|
-
if (!tree[
|
|
57870
|
-
tree[
|
|
57847
|
+
const app = parts[0];
|
|
57848
|
+
const module = parts[1] || "default";
|
|
57849
|
+
if (!tree[app]) {
|
|
57850
|
+
tree[app] = {};
|
|
57871
57851
|
}
|
|
57872
|
-
if (!tree[
|
|
57873
|
-
tree[
|
|
57852
|
+
if (!tree[app][module]) {
|
|
57853
|
+
tree[app][module] = [];
|
|
57874
57854
|
}
|
|
57875
|
-
tree[
|
|
57855
|
+
tree[app][module].push(route);
|
|
57876
57856
|
}
|
|
57877
57857
|
return tree;
|
|
57878
57858
|
}
|
|
57879
|
-
function generateLayout(
|
|
57880
|
-
const
|
|
57859
|
+
function generateLayout(application) {
|
|
57860
|
+
const appCapitalized = capitalize(application);
|
|
57881
57861
|
return `/**
|
|
57882
|
-
* ${
|
|
57862
|
+
* ${appCapitalized} Layout
|
|
57883
57863
|
*
|
|
57884
57864
|
* Auto-generated by SmartStack MCP - Customize as needed
|
|
57885
57865
|
*/
|
|
57886
57866
|
|
|
57887
57867
|
import React from 'react';
|
|
57888
57868
|
import { Outlet, Link, useLocation } from 'react-router-dom';
|
|
57889
|
-
import { ROUTES,
|
|
57869
|
+
import { ROUTES, getRoutesByApplication } from '../routes/navRoutes.generated';
|
|
57890
57870
|
|
|
57891
|
-
export const ${
|
|
57871
|
+
export const ${appCapitalized}Layout: React.FC = () => {
|
|
57892
57872
|
const location = useLocation();
|
|
57893
|
-
const
|
|
57873
|
+
const appRoutes = getRoutesByApplication('${application}');
|
|
57894
57874
|
|
|
57895
57875
|
return (
|
|
57896
57876
|
<div className="flex h-screen bg-gray-100">
|
|
57897
57877
|
{/* Sidebar */}
|
|
57898
57878
|
<aside className="w-64 bg-white shadow-sm">
|
|
57899
57879
|
<div className="p-4 border-b">
|
|
57900
|
-
<h1 className="text-xl font-semibold text-gray-900">${
|
|
57880
|
+
<h1 className="text-xl font-semibold text-gray-900">${appCapitalized}</h1>
|
|
57901
57881
|
</div>
|
|
57902
57882
|
<nav className="p-4">
|
|
57903
57883
|
<ul className="space-y-2">
|
|
57904
|
-
{
|
|
57884
|
+
{appRoutes.map((route) => (
|
|
57905
57885
|
<li key={route.navRoute}>
|
|
57906
57886
|
<Link
|
|
57907
57887
|
to={route.web}
|
|
@@ -57929,7 +57909,7 @@ export const ${contextCapitalized}Layout: React.FC = () => {
|
|
|
57929
57909
|
);
|
|
57930
57910
|
};
|
|
57931
57911
|
|
|
57932
|
-
export default ${
|
|
57912
|
+
export default ${appCapitalized}Layout;
|
|
57933
57913
|
`;
|
|
57934
57914
|
}
|
|
57935
57915
|
function generateRouteGuards() {
|
|
@@ -58011,17 +57991,16 @@ async function discoverPageFiles(webPath, routes) {
|
|
|
58011
57991
|
const pageMap = /* @__PURE__ */ new Map();
|
|
58012
57992
|
for (const route of routes) {
|
|
58013
57993
|
const parts = route.navRoute.split(".");
|
|
58014
|
-
const
|
|
58015
|
-
const
|
|
58016
|
-
const moduleParts = parts.slice(2).map(capitalize);
|
|
57994
|
+
const app = capitalize(parts[0]);
|
|
57995
|
+
const moduleParts = parts.slice(1).map(capitalize);
|
|
58017
57996
|
const moduleDir = moduleParts.join("/");
|
|
58018
|
-
const pagesDir = path19.join(webPath, "src", "pages",
|
|
57997
|
+
const pagesDir = path19.join(webPath, "src", "pages", app, moduleDir || "");
|
|
58019
57998
|
try {
|
|
58020
57999
|
const pageFiles = await glob("*Page.tsx", { cwd: pagesDir, absolute: false });
|
|
58021
58000
|
if (pageFiles.length > 0) {
|
|
58022
58001
|
const entries = pageFiles.map((f) => {
|
|
58023
58002
|
const componentName = f.replace(".tsx", "");
|
|
58024
|
-
const importPath = `@/pages/${
|
|
58003
|
+
const importPath = `@/pages/${app}/${moduleDir ? moduleDir + "/" : ""}${componentName}`;
|
|
58025
58004
|
return { importPath, componentName };
|
|
58026
58005
|
});
|
|
58027
58006
|
pageMap.set(route.navRoute, entries);
|
|
@@ -58042,12 +58021,12 @@ function generateClientRoutesConfig(routes, pageFiles, includeGuards) {
|
|
|
58042
58021
|
' * Run `scaffold_routes` with outputFormat: "clientRoutes" to regenerate.',
|
|
58043
58022
|
" *",
|
|
58044
58023
|
" * These routes must be added INSIDE the appropriate Layout wrapper in App.tsx.",
|
|
58045
|
-
" * They are NOT standalone - they are children of the
|
|
58024
|
+
" * They are NOT standalone - they are children of the application layout routes.",
|
|
58046
58025
|
" * Routes must be added in BOTH the standard block and the tenant-prefixed block.",
|
|
58047
58026
|
" */",
|
|
58048
58027
|
"",
|
|
58049
58028
|
"import type { RouteObject } from 'react-router-dom';",
|
|
58050
|
-
"import type {
|
|
58029
|
+
"import type { ApplicationRouteExtensions } from '@atlashub/smartstack';",
|
|
58051
58030
|
"import { lazy, Suspense } from 'react';",
|
|
58052
58031
|
"import { Navigate } from 'react-router-dom';",
|
|
58053
58032
|
"import { PageLoader } from '@/components/ui/PageLoader';"
|
|
@@ -58078,108 +58057,76 @@ function generateClientRoutesConfig(routes, pageFiles, includeGuards) {
|
|
|
58078
58057
|
}
|
|
58079
58058
|
}
|
|
58080
58059
|
lines.push("");
|
|
58081
|
-
for (const [
|
|
58082
|
-
const
|
|
58083
|
-
const layoutName = getLayoutName(
|
|
58060
|
+
for (const [app, modules] of Object.entries(routeTree)) {
|
|
58061
|
+
const appUpper = capitalize(app);
|
|
58062
|
+
const layoutName = getLayoutName(app);
|
|
58084
58063
|
lines.push("/**");
|
|
58085
|
-
lines.push(` * Routes for ${
|
|
58086
|
-
lines.push(` * Add these as children of <Route path="/${
|
|
58064
|
+
lines.push(` * Routes for ${appUpper} application`);
|
|
58065
|
+
lines.push(` * Add these as children of <Route path="/${app}" element={<${layoutName} />}>`);
|
|
58087
58066
|
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(` },`);
|
|
58067
|
+
lines.push(`export const ${app}Routes: RouteObject[] = [`);
|
|
58068
|
+
for (const [, moduleRoutes] of Object.entries(modules)) {
|
|
58069
|
+
for (const route of moduleRoutes) {
|
|
58070
|
+
const modulePath = route.navRoute.split(".").slice(1).map(toKebabCase).join("/");
|
|
58071
|
+
const pageName = route.navRoute.split(".").map(capitalize).join("") + "Page";
|
|
58072
|
+
const pageEntry = pageFiles.get(route.navRoute);
|
|
58073
|
+
const component = pageEntry?.[0]?.componentName || pageName;
|
|
58074
|
+
const hasRealPage = pageFiles.has(route.navRoute);
|
|
58075
|
+
if (includeGuards && route.permissions.length > 0) {
|
|
58076
|
+
lines.push(` {`);
|
|
58077
|
+
lines.push(` path: '${modulePath}',`);
|
|
58078
|
+
if (hasRealPage) {
|
|
58079
|
+
lines.push(` element: <Suspense fallback={<PageLoader />}><PermissionGuard permissions={ROUTES['${route.navRoute}'].permissions}><${component} /></PermissionGuard></Suspense>,`);
|
|
58136
58080
|
} 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(` },`);
|
|
58081
|
+
lines.push(` element: <PermissionGuard permissions={ROUTES['${route.navRoute}'].permissions}><div>TODO: ${component}</div></PermissionGuard>,`);
|
|
58141
58082
|
}
|
|
58083
|
+
lines.push(` },`);
|
|
58084
|
+
} else {
|
|
58085
|
+
lines.push(` {`);
|
|
58086
|
+
lines.push(` path: '${modulePath}',`);
|
|
58087
|
+
lines.push(` element: ${hasRealPage ? `<Suspense fallback={<PageLoader />}><${component} /></Suspense>` : `<div>TODO: ${component}</div>`},`);
|
|
58088
|
+
lines.push(` },`);
|
|
58142
58089
|
}
|
|
58143
58090
|
}
|
|
58144
58091
|
}
|
|
58145
58092
|
lines.push("];");
|
|
58146
58093
|
lines.push("");
|
|
58147
58094
|
}
|
|
58148
|
-
const
|
|
58149
|
-
lines.push("/** All generated routes grouped by
|
|
58095
|
+
const appKeys = Object.keys(routeTree);
|
|
58096
|
+
lines.push("/** All generated routes grouped by application */");
|
|
58150
58097
|
lines.push("export const generatedRoutes = {");
|
|
58151
|
-
for (const
|
|
58152
|
-
lines.push(` ${
|
|
58098
|
+
for (const app of appKeys) {
|
|
58099
|
+
lines.push(` ${app}: ${app}Routes,`);
|
|
58153
58100
|
}
|
|
58154
58101
|
lines.push("};");
|
|
58155
58102
|
lines.push("");
|
|
58156
58103
|
lines.push("/**");
|
|
58157
|
-
lines.push(" *
|
|
58158
|
-
lines.push(" * Import and spread into your App.tsx
|
|
58104
|
+
lines.push(" * Application route extensions for mergeRoutes() pattern.");
|
|
58105
|
+
lines.push(" * Import and spread into your App.tsx applicationRoutes:");
|
|
58159
58106
|
lines.push(" *");
|
|
58160
58107
|
lines.push(" * ```tsx");
|
|
58161
|
-
lines.push(" * import {
|
|
58162
|
-
lines.push(" * const
|
|
58163
|
-
lines.push(" * ...
|
|
58108
|
+
lines.push(" * import { applicationRouteExtensions } from './routes/clientRoutes.generated';");
|
|
58109
|
+
lines.push(" * const applicationRoutes = {");
|
|
58110
|
+
lines.push(" * ...applicationRouteExtensions,");
|
|
58164
58111
|
lines.push(" * // your additional routes...");
|
|
58165
58112
|
lines.push(" * };");
|
|
58166
58113
|
lines.push(" * ```");
|
|
58167
58114
|
lines.push(" */");
|
|
58168
|
-
lines.push("export const
|
|
58169
|
-
for (const
|
|
58170
|
-
lines.push(` ${
|
|
58115
|
+
lines.push("export const applicationRouteExtensions: Record<string, RouteObject[]> = {");
|
|
58116
|
+
for (const app of appKeys) {
|
|
58117
|
+
lines.push(` ${app}: ${app}Routes,`);
|
|
58171
58118
|
}
|
|
58172
58119
|
lines.push("};");
|
|
58173
58120
|
lines.push("");
|
|
58174
58121
|
return lines.join("\n");
|
|
58175
58122
|
}
|
|
58176
|
-
function getLayoutName(
|
|
58123
|
+
function getLayoutName(application) {
|
|
58177
58124
|
const layoutMap = {
|
|
58178
|
-
|
|
58179
|
-
|
|
58180
|
-
|
|
58125
|
+
administration: "AdminLayout",
|
|
58126
|
+
support: "SupportLayout",
|
|
58127
|
+
myspace: "UserLayout"
|
|
58181
58128
|
};
|
|
58182
|
-
return layoutMap[
|
|
58129
|
+
return layoutMap[application] || `${capitalize(application)}Layout`;
|
|
58183
58130
|
}
|
|
58184
58131
|
function toKebabCase(segment) {
|
|
58185
58132
|
return segment.replace(/([a-z0-9])([A-Z])/g, "$1-$2").toLowerCase();
|
|
@@ -58243,7 +58190,7 @@ Creates:
|
|
|
58243
58190
|
Example:
|
|
58244
58191
|
scaffold_routes source="controllers" scope="all"
|
|
58245
58192
|
|
|
58246
|
-
Scans backend controllers for [NavRoute("
|
|
58193
|
+
Scans backend controllers for [NavRoute("application.module")] attributes
|
|
58247
58194
|
and generates corresponding frontend routing infrastructure.`,
|
|
58248
58195
|
inputSchema: {
|
|
58249
58196
|
type: "object",
|
|
@@ -58260,9 +58207,8 @@ and generates corresponding frontend routing infrastructure.`,
|
|
|
58260
58207
|
},
|
|
58261
58208
|
scope: {
|
|
58262
58209
|
type: "string",
|
|
58263
|
-
enum: ["all", "platform", "business", "extensions"],
|
|
58264
58210
|
default: "all",
|
|
58265
|
-
description:
|
|
58211
|
+
description: 'Scope of routes to generate. Use "all" or a specific application name (e.g., "administration")'
|
|
58266
58212
|
},
|
|
58267
58213
|
options: {
|
|
58268
58214
|
type: "object",
|
|
@@ -58715,7 +58661,7 @@ function formatResult6(result, _input) {
|
|
|
58715
58661
|
lines.push('scaffold_routes source="controllers"');
|
|
58716
58662
|
lines.push("");
|
|
58717
58663
|
lines.push("# Generate API client for a specific NavRoute");
|
|
58718
|
-
lines.push('scaffold_api_client navRoute="
|
|
58664
|
+
lines.push('scaffold_api_client navRoute="administration.users" name="User"');
|
|
58719
58665
|
lines.push("```");
|
|
58720
58666
|
return lines.join("\n");
|
|
58721
58667
|
}
|
|
@@ -58974,7 +58920,7 @@ function generateTypesFile() {
|
|
|
58974
58920
|
export interface ExtensionConfig {
|
|
58975
58921
|
/**
|
|
58976
58922
|
* Page component overrides
|
|
58977
|
-
* Key: NavRoute path (e.g., '
|
|
58923
|
+
* Key: NavRoute path (e.g., 'administration.users')
|
|
58978
58924
|
* Value: React component to render instead of the default page
|
|
58979
58925
|
*/
|
|
58980
58926
|
pages?: Record<string, ComponentType<PageProps>>;
|
|
@@ -59426,28 +59372,28 @@ export function ExtendablePage({ pageKey, defaultComponent: DefaultComponent, pa
|
|
|
59426
59372
|
}
|
|
59427
59373
|
|
|
59428
59374
|
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: '
|
|
59375
|
+
USERS_LIST: 'administration.users',
|
|
59376
|
+
USERS_DETAIL: 'administration.users.detail',
|
|
59377
|
+
ROLES_LIST: 'administration.permissions.roles',
|
|
59378
|
+
ROLES_DETAIL: 'administration.permissions.roles.detail',
|
|
59379
|
+
PERMISSIONS_LIST: 'administration.permissions.permissions',
|
|
59380
|
+
PERMISSIONS_DETAIL: 'administration.permissions.permissions.detail',
|
|
59381
|
+
ASSIGNMENTS: 'administration.permissions.assignments',
|
|
59382
|
+
AI_DASHBOARD: 'administration.ai.dashboard',
|
|
59383
|
+
AI_PROMPTS_LIST: 'administration.ai.prompts',
|
|
59384
|
+
AI_PROMPTS_DETAIL: 'administration.ai.prompts.detail',
|
|
59385
|
+
WORKFLOWS_LIST: 'administration.workflows.list',
|
|
59386
|
+
WORKFLOWS_DETAIL: 'administration.workflows.detail',
|
|
59387
|
+
EMAIL_TEMPLATES_LIST: 'administration.workflows.email-templates',
|
|
59388
|
+
EMAIL_TEMPLATES_DETAIL: 'administration.workflows.email-templates.detail',
|
|
59389
|
+
TICKETS_LIST: 'support.tickets',
|
|
59390
|
+
TICKETS_DETAIL: 'support.tickets.detail',
|
|
59391
|
+
SUPPORT_DASHBOARD: 'support.dashboard',
|
|
59392
|
+
USER_PROFILE: 'myspace.profile',
|
|
59393
|
+
USER_PREFERENCES: 'myspace.preferences',
|
|
59394
|
+
USER_DASHBOARD: 'myspace.dashboard',
|
|
59395
|
+
MY_TICKETS_LIST: 'support-client.my-tickets',
|
|
59396
|
+
MY_TICKETS_DETAIL: 'support-client.my-tickets.detail',
|
|
59451
59397
|
} as const;
|
|
59452
59398
|
|
|
59453
59399
|
export type PageKey = typeof PAGE_KEYS[keyof typeof PAGE_KEYS];
|
|
@@ -60857,7 +60803,7 @@ async function checkAuthorization(structure, result) {
|
|
|
60857
60803
|
message: `Controller ${controllerName} missing authorization attribute`,
|
|
60858
60804
|
file: path23.relative(structure.root, file),
|
|
60859
60805
|
line: lineNumber,
|
|
60860
|
-
suggestion: 'Add [NavRoute("
|
|
60806
|
+
suggestion: 'Add [NavRoute("application.module")] or [Authorize] attribute to the controller class',
|
|
60861
60807
|
cweId: "CWE-862"
|
|
60862
60808
|
});
|
|
60863
60809
|
}
|
|
@@ -64764,7 +64710,7 @@ Tables are organized by domain using prefixes:
|
|
|
64764
64710
|
| Prefix | Domain | Example Tables |
|
|
64765
64711
|
|--------|--------|----------------|
|
|
64766
64712
|
| \`auth_\` | Authorization | auth_Users, auth_Roles, auth_Permissions |
|
|
64767
|
-
| \`nav_\` | Navigation |
|
|
64713
|
+
| \`nav_\` | Navigation | nav_Applications, nav_Modules, nav_Sections |
|
|
64768
64714
|
| \`usr_\` | User profiles | usr_Profiles, usr_Preferences |
|
|
64769
64715
|
| \`ai_\` | AI features | ai_Providers, ai_Models, ai_Prompts |
|
|
64770
64716
|
| \`cfg_\` | Configuration | cfg_Settings |
|
|
@@ -64798,7 +64744,7 @@ In addition to the platform prefixes above, client applications define their own
|
|
|
64798
64744
|
|
|
64799
64745
|
### Navigation Scope System
|
|
64800
64746
|
|
|
64801
|
-
All navigation data (
|
|
64747
|
+
All navigation data (Application, Module, Section) uses a **Scope** field to distinguish system data from client extensions:
|
|
64802
64748
|
|
|
64803
64749
|
| Scope | Usage | Description | Example Code |
|
|
64804
64750
|
|-------|-------|-------------|--------------|
|
|
@@ -64807,19 +64753,18 @@ All navigation data (Context, Application, Module, Section) uses a **Scope** fie
|
|
|
64807
64753
|
| \`${scopeTypes[2]}\` | Partner modules | Provided by certified partners | \`partner_integration\` |
|
|
64808
64754
|
| \`${scopeTypes[3]}\` | Community modules | Open-source community contributions | \`community_tool\` |
|
|
64809
64755
|
|
|
64810
|
-
**Navigation Hierarchy (
|
|
64756
|
+
**Navigation Hierarchy (3 levels):**
|
|
64811
64757
|
|
|
64812
64758
|
\`\`\`
|
|
64813
|
-
|
|
64759
|
+
Application \u2192 Module \u2192 Section
|
|
64814
64760
|
\`\`\`
|
|
64815
64761
|
|
|
64816
64762
|
**Examples for each level:**
|
|
64817
64763
|
|
|
64818
64764
|
| Level | Core Example | Extension Example |
|
|
64819
64765
|
|-------|--------------|-------------------|
|
|
64820
|
-
| Context | Code: \`platform\`<br/>Scope: \`${scopeTypes[0]}\` | Code: \`business\`<br/>Scope: \`${scopeTypes[1]}\` |
|
|
64821
64766
|
| Application | Code: \`administration\`<br/>Scope: \`${scopeTypes[0]}\` | Code: \`custom_app\`<br/>Scope: \`${scopeTypes[1]}\` |
|
|
64822
|
-
| Module | Code: \`users\`<br/>Route: \`
|
|
64767
|
+
| Module | Code: \`users\`<br/>Route: \`administration.users\`<br/>Scope: \`${scopeTypes[0]}\` | Code: \`inventory\`<br/>Route: \`operations.inventory\`<br/>Scope: \`${scopeTypes[1]}\` |
|
|
64823
64768
|
| Section | Code: \`management\`<br/>Scope: \`${scopeTypes[0]}\` | Code: \`reports\`<br/>Scope: \`${scopeTypes[1]}\` |
|
|
64824
64769
|
|
|
64825
64770
|
**Rules:**
|
|
@@ -64844,18 +64789,18 @@ Permissions and Roles also use the **Scope** field for the same categorization:
|
|
|
64844
64789
|
Permissions inherit their Scope from the module's NavRoute:
|
|
64845
64790
|
|
|
64846
64791
|
\`\`\`
|
|
64847
|
-
NavRoute: "
|
|
64792
|
+
NavRoute: "administration.users"
|
|
64848
64793
|
Module Scope: Core
|
|
64849
64794
|
\u2192 Permissions Scope: Core
|
|
64850
|
-
-
|
|
64851
|
-
-
|
|
64852
|
-
-
|
|
64853
|
-
-
|
|
64795
|
+
- administration.users.read (Core)
|
|
64796
|
+
- administration.users.create (Core)
|
|
64797
|
+
- administration.users.update (Core)
|
|
64798
|
+
- administration.users.delete (Core)
|
|
64854
64799
|
\`\`\`
|
|
64855
64800
|
|
|
64856
64801
|
**Detection Rules:**
|
|
64857
|
-
- Routes
|
|
64858
|
-
- Routes
|
|
64802
|
+
- Routes for platform applications (administration, support, etc.) \u2192 \`${scopeTypes[0]}\`
|
|
64803
|
+
- Routes for client/custom applications \u2192 \`${scopeTypes[1]}\`
|
|
64859
64804
|
- Partner-provided routes \u2192 \`${scopeTypes[2]}\`
|
|
64860
64805
|
- Community routes \u2192 \`${scopeTypes[3]}\`
|
|
64861
64806
|
|
|
@@ -65638,7 +65583,7 @@ All tenant-related tables use the \`tenant_\` prefix:
|
|
|
65638
65583
|
|
|
65639
65584
|
### Layout Standards
|
|
65640
65585
|
|
|
65641
|
-
All
|
|
65586
|
+
All application layouts MUST follow the AdminLayout pattern for consistent user experience.
|
|
65642
65587
|
|
|
65643
65588
|
#### Standard Structure
|
|
65644
65589
|
|
|
@@ -65760,8 +65705,8 @@ import { Breadcrumb } from '@/components/ui/Breadcrumb';
|
|
|
65760
65705
|
// In your page component
|
|
65761
65706
|
<Breadcrumb
|
|
65762
65707
|
items={[
|
|
65763
|
-
{ label: t('nav.administration'), href: '/
|
|
65764
|
-
{ label: t('nav.users'), href: '/
|
|
65708
|
+
{ label: t('nav.administration'), href: '/administration' },
|
|
65709
|
+
{ label: t('nav.users'), href: '/administration/users' },
|
|
65765
65710
|
{ label: user.fullName } // Last item = current page (no href)
|
|
65766
65711
|
]}
|
|
65767
65712
|
/>
|
|
@@ -65771,7 +65716,7 @@ import { Breadcrumb } from '@/components/ui/Breadcrumb';
|
|
|
65771
65716
|
|
|
65772
65717
|
| Rule | Description |
|
|
65773
65718
|
|------|-------------|
|
|
65774
|
-
| First item |
|
|
65719
|
+
| First item | Application level with Home icon (auto-added if no icon) |
|
|
65775
65720
|
| Intermediate items | Clickable links to parent pages |
|
|
65776
65721
|
| Last item | Current page name, no href, bold text |
|
|
65777
65722
|
| Labels | Use i18n translation keys from \`navigation.json\` |
|
|
@@ -65780,7 +65725,7 @@ import { Breadcrumb } from '@/components/ui/Breadcrumb';
|
|
|
65780
65725
|
#### Hierarchy Pattern
|
|
65781
65726
|
|
|
65782
65727
|
\`\`\`
|
|
65783
|
-
|
|
65728
|
+
Application > Module > [Section] > [Entity Name]
|
|
65784
65729
|
\`\`\`
|
|
65785
65730
|
|
|
65786
65731
|
**Examples:**
|