@atlashub/smartstack-cli 3.31.0 → 3.32.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (52) hide show
  1. package/.documentation/installation.html +7 -2
  2. package/README.md +7 -1
  3. package/dist/index.js +33 -37
  4. package/dist/index.js.map +1 -1
  5. package/dist/mcp-entry.mjs +547 -97
  6. package/dist/mcp-entry.mjs.map +1 -1
  7. package/package.json +1 -1
  8. package/scripts/health-check.sh +2 -1
  9. package/templates/mcp-scaffolding/controller.cs.hbs +10 -7
  10. package/templates/mcp-scaffolding/entity-extension.cs.hbs +132 -124
  11. package/templates/mcp-scaffolding/frontend/api-client.ts.hbs +4 -4
  12. package/templates/mcp-scaffolding/tests/controller.test.cs.hbs +38 -15
  13. package/templates/mcp-scaffolding/tests/service.test.cs.hbs +20 -8
  14. package/templates/skills/apex/SKILL.md +7 -9
  15. package/templates/skills/apex/_shared.md +9 -2
  16. package/templates/skills/apex/references/code-generation.md +412 -0
  17. package/templates/skills/apex/references/post-checks.md +377 -37
  18. package/templates/skills/apex/references/smartstack-api.md +229 -5
  19. package/templates/skills/apex/references/smartstack-frontend.md +368 -11
  20. package/templates/skills/apex/references/smartstack-layers.md +54 -7
  21. package/templates/skills/apex/steps/step-00-init.md +1 -2
  22. package/templates/skills/apex/steps/step-01-analyze.md +45 -2
  23. package/templates/skills/apex/steps/step-02-plan.md +23 -2
  24. package/templates/skills/apex/steps/step-03-execute.md +195 -5
  25. package/templates/skills/apex/steps/step-04-examine.md +18 -5
  26. package/templates/skills/apex/steps/step-05-deep-review.md +9 -11
  27. package/templates/skills/apex/steps/step-06-resolve.md +5 -9
  28. package/templates/skills/apex/steps/step-07-tests.md +66 -1
  29. package/templates/skills/apex/steps/step-08-run-tests.md +12 -3
  30. package/templates/skills/application/references/provider-template.md +62 -39
  31. package/templates/skills/application/templates-backend.md +3 -3
  32. package/templates/skills/application/templates-frontend.md +12 -12
  33. package/templates/skills/application/templates-seed.md +14 -4
  34. package/templates/skills/business-analyse/SKILL.md +9 -6
  35. package/templates/skills/business-analyse/questionnaire/04-data.md +8 -0
  36. package/templates/skills/business-analyse/references/agent-module-prompt.md +84 -5
  37. package/templates/skills/business-analyse/references/agent-pooling-best-practices.md +83 -19
  38. package/templates/skills/business-analyse/references/consolidation-structural-checks.md +6 -2
  39. package/templates/skills/business-analyse/references/team-orchestration.md +443 -110
  40. package/templates/skills/business-analyse/references/validation-checklist.md +5 -4
  41. package/templates/skills/business-analyse/schemas/sections/analysis-schema.json +44 -0
  42. package/templates/skills/business-analyse/steps/step-03a2-analysis.md +72 -1
  43. package/templates/skills/business-analyse/steps/step-03c-compile.md +93 -7
  44. package/templates/skills/business-analyse/steps/step-03d-validate.md +34 -2
  45. package/templates/skills/business-analyse/steps/step-04b-analyze.md +40 -0
  46. package/templates/skills/controller/references/controller-code-templates.md +2 -2
  47. package/templates/skills/controller/templates.md +12 -12
  48. package/templates/skills/feature-full/steps/step-01-implementation.md +4 -4
  49. package/templates/skills/ralph-loop/references/category-rules.md +44 -2
  50. package/templates/skills/ralph-loop/references/compact-loop.md +37 -0
  51. package/templates/skills/ralph-loop/references/core-seed-data.md +51 -20
  52. package/templates/skills/review-code/references/owasp-api-top10.md +1 -1
@@ -25763,7 +25763,31 @@ var init_zod = __esm({
25763
25763
  });
25764
25764
 
25765
25765
  // src/mcp/types/index.ts
25766
- var ProjectConfigSchema, SmartStackConfigSchema, ConventionsConfigSchema, EfCoreContextSchema, EfCoreConfigSchema, ScaffoldingConfigSchema, ConfigSchema, ValidateConventionsInputSchema, CheckMigrationsInputSchema, EntityPropertySchema, ScaffoldExtensionInputSchema, ApiDocsInputSchema, SuggestMigrationInputSchema, GeneratePermissionsInputSchema, TestTypeSchema, TestTargetSchema, ScaffoldTestsInputSchema, AnalyzeTestCoverageInputSchema, ValidateTestConventionsInputSchema, SuggestTestScenariosInputSchema, SecurityCheckSchema, ValidateSecurityInputSchema, QualityMetricSchema, AnalyzeCodeQualityInputSchema, ScaffoldApiClientInputSchema, ScaffoldRoutesInputSchema, ValidateFrontendRoutesInputSchema, SlotDefinitionSchema, ScaffoldFrontendExtensionInputSchema, AnalyzeExtensionPointsInputSchema, AnalyzeHierarchyPatternsInputSchema, ReviewCodeCheckSchema, ReviewCodeInputSchema;
25766
+ function resolveTenantMode(options) {
25767
+ if (options?.tenantMode) return options.tenantMode;
25768
+ if (options?.isSystemEntity === true) return "none";
25769
+ return "strict";
25770
+ }
25771
+ function tenantModeToTemplateFlags(mode) {
25772
+ return {
25773
+ // Backward compat — existing templates use {{#unless isSystemEntity}}
25774
+ isSystemEntity: mode === "none",
25775
+ // Mode-specific flags
25776
+ isStrict: mode === "strict",
25777
+ isOptional: mode === "optional",
25778
+ isScoped: mode === "scoped",
25779
+ isNone: mode === "none",
25780
+ // Derived flags for cleaner templates
25781
+ hasTenantId: mode !== "none",
25782
+ // strict, optional, scoped all have TenantId
25783
+ isNullableTenant: mode === "optional" || mode === "scoped",
25784
+ // Guid? vs Guid
25785
+ hasScope: mode === "scoped",
25786
+ // only scoped has EntityScope
25787
+ tenantMode: mode
25788
+ };
25789
+ }
25790
+ var ProjectConfigSchema, SmartStackConfigSchema, ConventionsConfigSchema, EfCoreContextSchema, EfCoreConfigSchema, ScaffoldingConfigSchema, ConfigSchema, TenantModeSchema, ValidateConventionsInputSchema, CheckMigrationsInputSchema, EntityPropertySchema, ScaffoldExtensionInputSchema, ApiDocsInputSchema, SuggestMigrationInputSchema, GeneratePermissionsInputSchema, TestTypeSchema, TestTargetSchema, ScaffoldTestsInputSchema, AnalyzeTestCoverageInputSchema, ValidateTestConventionsInputSchema, SuggestTestScenariosInputSchema, SecurityCheckSchema, ValidateSecurityInputSchema, QualityMetricSchema, AnalyzeCodeQualityInputSchema, ScaffoldApiClientInputSchema, ScaffoldRoutesInputSchema, ValidateFrontendRoutesInputSchema, SlotDefinitionSchema, ScaffoldFrontendExtensionInputSchema, AnalyzeExtensionPointsInputSchema, AnalyzeHierarchyPatternsInputSchema, ReviewCodeCheckSchema, ReviewCodeInputSchema;
25767
25791
  var init_types3 = __esm({
25768
25792
  "src/mcp/types/index.ts"() {
25769
25793
  "use strict";
@@ -25854,6 +25878,7 @@ var init_types3 = __esm({
25854
25878
  // Resolved DbContext to use (from projectConfig or default)
25855
25879
  defaultDbContext: external_exports.enum(["core", "extensions"]).default("core")
25856
25880
  });
25881
+ TenantModeSchema = external_exports.enum(["strict", "optional", "scoped", "none"]).default("strict");
25857
25882
  ValidateConventionsInputSchema = external_exports.object({
25858
25883
  path: external_exports.string().optional().describe("Project path to validate (default: SmartStack.app path)"),
25859
25884
  checks: external_exports.array(external_exports.enum(["tables", "migrations", "services", "namespaces", "entities", "tenants", "controllers", "layouts", "tabs", "hierarchies", "protected-actions", "permissions", "frontend-routes", "feature-json", "all"])).default(["all"]).describe("Types of checks to perform")
@@ -25877,7 +25902,8 @@ var init_types3 = __esm({
25877
25902
  baseEntity: external_exports.string().optional().describe("Base entity to extend (for entity type)"),
25878
25903
  methods: external_exports.array(external_exports.string()).optional().describe("Methods to generate (for service type)"),
25879
25904
  outputPath: external_exports.string().optional().describe("Custom output path"),
25880
- isSystemEntity: external_exports.boolean().optional().describe("If true, creates a system entity without TenantId"),
25905
+ isSystemEntity: external_exports.boolean().optional().describe("[DEPRECATED: use tenantMode] If true, creates a system entity without TenantId"),
25906
+ tenantMode: TenantModeSchema.optional().describe("Tenant isolation mode: strict (default), optional (cross-tenant), scoped (with EntityScope), none (no tenant)"),
25881
25907
  tablePrefix: external_exports.string().optional().describe('Domain prefix for table name (e.g., "auth_", "nav_", "cfg_")'),
25882
25908
  schema: external_exports.enum(["core", "extensions"]).optional().describe("Database schema (default: core)"),
25883
25909
  dryRun: external_exports.boolean().optional().describe("If true, preview generated code without writing files"),
@@ -25928,7 +25954,8 @@ var init_types3 = __esm({
25928
25954
  includeAuthorization: external_exports.boolean().default(false).describe("Include authorization tests"),
25929
25955
  includePerformance: external_exports.boolean().default(false).describe("Include performance tests"),
25930
25956
  entityProperties: external_exports.array(EntityPropertySchema).optional().describe("Entity properties for test generation"),
25931
- isSystemEntity: external_exports.boolean().default(false).describe("If true, entity has no TenantId"),
25957
+ isSystemEntity: external_exports.boolean().default(false).describe("[DEPRECATED: use tenantMode] If true, entity has no TenantId"),
25958
+ tenantMode: TenantModeSchema.optional().describe("Tenant isolation mode: strict (default), optional (cross-tenant), scoped (with EntityScope), none (no tenant)"),
25932
25959
  dryRun: external_exports.boolean().default(false).describe("Preview without writing files")
25933
25960
  }).optional()
25934
25961
  });
@@ -26668,7 +26695,7 @@ import path8 from "path";
26668
26695
  async function handleValidateConventions(args, config2) {
26669
26696
  const input = ValidateConventionsInputSchema.parse(args);
26670
26697
  const projectPath = input.path || config2.smartstack.projectPath;
26671
- const checks = input.checks.includes("all") ? ["tables", "migrations", "services", "namespaces", "entities", "tenants", "controllers", "layouts", "tabs", "hierarchies", "protected-actions", "permissions", "frontend-routes", "feature-json"] : input.checks;
26698
+ const checks = input.checks.includes("all") ? ["tables", "migrations", "services", "namespaces", "entities", "tenants", "controllers", "layouts", "tabs", "hierarchies", "protected-actions", "permissions", "frontend-routes", "feature-json", "code-patterns"] : input.checks;
26672
26699
  logger.info("Validating conventions", { projectPath, checks });
26673
26700
  const result = {
26674
26701
  valid: true,
@@ -26719,6 +26746,9 @@ async function handleValidateConventions(args, config2) {
26719
26746
  if (checks.includes("feature-json")) {
26720
26747
  await validateFeatureJson(structure, config2, result);
26721
26748
  }
26749
+ if (checks.includes("code-patterns")) {
26750
+ await validateCodePatterns(structure, config2, result);
26751
+ }
26722
26752
  result.valid = result.errors.length === 0;
26723
26753
  result.summary = generateSummary(result, checks);
26724
26754
  return formatResult(result);
@@ -26901,8 +26931,6 @@ async function validateNamespaces(structure, config2, result) {
26901
26931
  if (namespaceMatch) {
26902
26932
  const namespace = namespaceMatch[1];
26903
26933
  if (!namespace.startsWith(layer.expected)) {
26904
- const lastDot = namespace.lastIndexOf(".");
26905
- const namespaceSuffix = lastDot > 0 ? namespace.substring(lastDot + 1) : "";
26906
26934
  const isValidLayerPattern = ["Domain", "Application", "Infrastructure", "Api"].some((l) => namespace.includes(`.${l}`) || namespace.endsWith(`.${l}`));
26907
26935
  const severity = isValidLayerPattern ? "warning" : "error";
26908
26936
  result.errors.push({
@@ -26940,16 +26968,19 @@ async function validateEntities(structure, _config, result) {
26940
26968
  const hasBaseEntity = inheritance.includes("BaseEntity");
26941
26969
  const hasSystemEntity = inheritance.includes("SystemEntity");
26942
26970
  const hasITenantEntity = inheritance.includes("ITenantEntity");
26971
+ const hasIOptionalTenantEntity = inheritance.includes("IOptionalTenantEntity");
26972
+ const hasIScopedTenantEntity = inheritance.includes("IScopedTenantEntity");
26973
+ const hasAnyTenantInterface = hasITenantEntity || hasIOptionalTenantEntity || hasIScopedTenantEntity;
26943
26974
  if (!hasBaseEntity && !hasSystemEntity) {
26944
26975
  continue;
26945
26976
  }
26946
- if (hasBaseEntity && !hasSystemEntity && !hasITenantEntity) {
26977
+ if (hasBaseEntity && !hasSystemEntity && !hasAnyTenantInterface) {
26947
26978
  result.warnings.push({
26948
26979
  type: "warning",
26949
26980
  category: "entities",
26950
- message: `Entity "${entityName}" inherits BaseEntity but doesn't implement ITenantEntity`,
26981
+ message: `Entity "${entityName}" inherits BaseEntity but doesn't implement any tenant interface`,
26951
26982
  file: path8.relative(structure.root, file),
26952
- suggestion: "Add ITenantEntity interface for multi-tenant support, or use SystemEntity for platform-level entities"
26983
+ suggestion: "Add ITenantEntity (strict), IOptionalTenantEntity (cross-tenant), or IScopedTenantEntity (with scope) for multi-tenant support"
26953
26984
  });
26954
26985
  }
26955
26986
  if (!content.includes(`private ${entityName}()`)) {
@@ -26982,7 +27013,9 @@ async function validateTenantAwareness(structure, _config, result) {
26982
27013
  return;
26983
27014
  }
26984
27015
  const entityFiles = await findFiles("**/*.cs", { cwd: structure.domain });
26985
- let tenantAwareCount = 0;
27016
+ let strictCount = 0;
27017
+ let optionalCount = 0;
27018
+ let scopedCount = 0;
26986
27019
  let systemEntityCount = 0;
26987
27020
  let ambiguousCount = 0;
26988
27021
  for (const file of entityFiles) {
@@ -26991,15 +27024,19 @@ async function validateTenantAwareness(structure, _config, result) {
26991
27024
  if (!classMatch) continue;
26992
27025
  const entityName = classMatch[1];
26993
27026
  const inheritance = classMatch[2]?.trim() || "";
26994
- if (!inheritance.includes("Entity") && !inheritance.includes("ITenant")) {
27027
+ if (!inheritance.includes("Entity") && !inheritance.includes("ITenant") && !inheritance.includes("IOptional") && !inheritance.includes("IScoped")) {
26995
27028
  continue;
26996
27029
  }
26997
- const hasITenantEntity = inheritance.includes("ITenantEntity");
27030
+ const hasIScopedTenantEntity = inheritance.includes("IScopedTenantEntity");
27031
+ const hasIOptionalTenantEntity = inheritance.includes("IOptionalTenantEntity") && !hasIScopedTenantEntity;
27032
+ const hasITenantEntity = inheritance.includes("ITenantEntity") && !hasIOptionalTenantEntity && !hasIScopedTenantEntity;
26998
27033
  const hasSystemEntity = inheritance.includes("SystemEntity");
27034
+ const hasAnyTenantInterface = hasITenantEntity || hasIOptionalTenantEntity || hasIScopedTenantEntity;
26999
27035
  const hasTenantId = content.includes("TenantId");
27000
27036
  const hasTenantIdProperty = content.includes("public Guid TenantId") || content.includes("public required Guid TenantId");
27037
+ const hasNullableTenantIdProperty = content.includes("public Guid? TenantId");
27001
27038
  if (hasITenantEntity) {
27002
- tenantAwareCount++;
27039
+ strictCount++;
27003
27040
  if (!hasTenantIdProperty) {
27004
27041
  result.errors.push({
27005
27042
  type: "error",
@@ -27020,6 +27057,39 @@ async function validateTenantAwareness(structure, _config, result) {
27020
27057
  });
27021
27058
  }
27022
27059
  }
27060
+ if (hasIOptionalTenantEntity) {
27061
+ optionalCount++;
27062
+ if (!hasNullableTenantIdProperty) {
27063
+ result.errors.push({
27064
+ type: "error",
27065
+ category: "tenants",
27066
+ message: `Entity "${entityName}" implements IOptionalTenantEntity but TenantId is not Guid?`,
27067
+ file: path8.relative(structure.root, file),
27068
+ suggestion: "TenantId must be nullable for IOptionalTenantEntity: public Guid? TenantId { get; private set; }"
27069
+ });
27070
+ }
27071
+ }
27072
+ if (hasIScopedTenantEntity) {
27073
+ scopedCount++;
27074
+ if (!hasNullableTenantIdProperty) {
27075
+ result.errors.push({
27076
+ type: "error",
27077
+ category: "tenants",
27078
+ message: `Entity "${entityName}" implements IScopedTenantEntity but TenantId is not Guid?`,
27079
+ file: path8.relative(structure.root, file),
27080
+ suggestion: "TenantId must be nullable for IScopedTenantEntity: public Guid? TenantId { get; private set; }"
27081
+ });
27082
+ }
27083
+ if (!content.includes("EntityScope") && !content.includes("Scope")) {
27084
+ result.errors.push({
27085
+ type: "error",
27086
+ category: "tenants",
27087
+ message: `Entity "${entityName}" implements IScopedTenantEntity but is missing Scope property`,
27088
+ file: path8.relative(structure.root, file),
27089
+ suggestion: "Add: public EntityScope Scope { get; private set; }"
27090
+ });
27091
+ }
27092
+ }
27023
27093
  if (hasSystemEntity) {
27024
27094
  systemEntityCount++;
27025
27095
  if (hasTenantId) {
@@ -27032,22 +27102,23 @@ async function validateTenantAwareness(structure, _config, result) {
27032
27102
  });
27033
27103
  }
27034
27104
  }
27035
- if (!hasITenantEntity && !hasSystemEntity && hasTenantId) {
27105
+ if (!hasAnyTenantInterface && !hasSystemEntity && hasTenantId) {
27036
27106
  ambiguousCount++;
27037
27107
  result.warnings.push({
27038
27108
  type: "warning",
27039
27109
  category: "tenants",
27040
- message: `Entity "${entityName}" has TenantId but doesn't implement ITenantEntity`,
27110
+ message: `Entity "${entityName}" has TenantId but doesn't implement any tenant interface`,
27041
27111
  file: path8.relative(structure.root, file),
27042
- suggestion: "Add ITenantEntity interface for explicit tenant-awareness"
27112
+ suggestion: "Add ITenantEntity (strict), IOptionalTenantEntity (cross-tenant), or IScopedTenantEntity (scoped)"
27043
27113
  });
27044
27114
  }
27045
27115
  }
27046
- if (tenantAwareCount + systemEntityCount > 0) {
27116
+ const totalTenant = strictCount + optionalCount + scopedCount;
27117
+ if (totalTenant + systemEntityCount > 0) {
27047
27118
  result.warnings.push({
27048
27119
  type: "warning",
27049
27120
  category: "tenants",
27050
- message: `Tenant summary: ${tenantAwareCount} tenant-aware, ${systemEntityCount} system, ${ambiguousCount} ambiguous`
27121
+ message: `Tenant summary: ${strictCount} strict, ${optionalCount} optional, ${scopedCount} scoped, ${systemEntityCount} system, ${ambiguousCount} ambiguous`
27051
27122
  });
27052
27123
  }
27053
27124
  }
@@ -27437,7 +27508,7 @@ async function validateHierarchies(structure, _config, result) {
27437
27508
  const hasSystemEntity = inheritance.includes("SystemEntity");
27438
27509
  if (!hasBaseEntity && !hasSystemEntity) continue;
27439
27510
  entityNames.add(entityName);
27440
- const hasITenantEntity = inheritance.includes("ITenantEntity");
27511
+ const hasITenantEntity = inheritance.includes("ITenantEntity") || inheritance.includes("IOptionalTenantEntity") || inheritance.includes("IScopedTenantEntity");
27441
27512
  const selfRefMatch = content.match(
27442
27513
  new RegExp(
27443
27514
  `public\\s+(?:Guid\\??|${entityName}\\??)\\s+(Parent(?:Id)?|Parent${entityName}(?:Id)?)\\s*\\{`,
@@ -27520,7 +27591,7 @@ async function validateHierarchies(structure, _config, result) {
27520
27591
  category: "hierarchies",
27521
27592
  message: `Entity "${h.name}" has tenant-aware parent "${h.parentEntity}" but is not tenant-aware itself`,
27522
27593
  file: h.file,
27523
- suggestion: `Add ITenantEntity interface to ${h.name} for consistent tenant isolation.`
27594
+ suggestion: `Add a tenant interface (ITenantEntity, IOptionalTenantEntity, or IScopedTenantEntity) to ${h.name} for consistent tenant isolation.`
27524
27595
  });
27525
27596
  }
27526
27597
  }
@@ -27991,6 +28062,60 @@ async function validateFeatureJson(structure, _config, result) {
27991
28062
  message: `Feature.json summary: ${featureFiles.length} file(s) validated`
27992
28063
  });
27993
28064
  }
28065
+ async function validateCodePatterns(structure, _config, result) {
28066
+ const projectPath = structure.root || "";
28067
+ if (!projectPath) return;
28068
+ const validatorFiles = await findFiles("**/*Validator.cs", { cwd: path8.join(projectPath, "src") });
28069
+ for (const file of validatorFiles) {
28070
+ const fullPath = path8.join(projectPath, "src", file);
28071
+ try {
28072
+ const content = await readText(fullPath);
28073
+ if (content.includes("^[a-z0-9_]+$") && !content.includes("^[a-z0-9_-]+$")) {
28074
+ result.errors.push({
28075
+ type: "error",
28076
+ category: "code-patterns",
28077
+ message: `Validator uses old Code regex without hyphen support`,
28078
+ file,
28079
+ suggestion: "Update regex to ^[a-z0-9_-]+$ to support auto-generated codes with hyphens (e.g., acme-emp-00001)"
28080
+ });
28081
+ }
28082
+ } catch {
28083
+ }
28084
+ }
28085
+ const serviceFiles = (await findFiles("**/*Service.cs", { cwd: path8.join(projectPath, "src") })).filter((f) => !path8.basename(f).startsWith("I"));
28086
+ for (const file of serviceFiles) {
28087
+ const fullPath = path8.join(projectPath, "src", file);
28088
+ try {
28089
+ const content = await readText(fullPath);
28090
+ if (content.includes("ICodeGenerator")) {
28091
+ const entityName = path8.basename(file).replace("Service.cs", "");
28092
+ const dtoFiles = await findFiles(`**/Create${entityName}Dto.cs`, { cwd: path8.join(projectPath, "src") });
28093
+ for (const dtoFile of dtoFiles) {
28094
+ const dtoFullPath = path8.join(projectPath, "src", dtoFile);
28095
+ try {
28096
+ const dtoContent = await readText(dtoFullPath);
28097
+ if (dtoContent.includes("public string Code")) {
28098
+ result.warnings.push({
28099
+ type: "warning",
28100
+ category: "code-patterns",
28101
+ message: `Create${entityName}Dto has Code property but service uses ICodeGenerator (code is auto-generated)`,
28102
+ file: dtoFile,
28103
+ suggestion: `Remove Code from Create${entityName}Dto \u2014 it is auto-generated by ICodeGenerator<${entityName}>`
28104
+ });
28105
+ }
28106
+ } catch {
28107
+ }
28108
+ }
28109
+ }
28110
+ } catch {
28111
+ }
28112
+ }
28113
+ result.warnings.push({
28114
+ type: "warning",
28115
+ category: "code-patterns",
28116
+ message: `Code patterns check completed: ${validatorFiles.length} validators, ${serviceFiles.length} services scanned`
28117
+ });
28118
+ }
27994
28119
  function generateSummary(result, checks) {
27995
28120
  const parts = [];
27996
28121
  parts.push(`Checks performed: ${checks.join(", ")}`);
@@ -28059,7 +28184,7 @@ var init_validate_conventions = __esm({
28059
28184
  type: "array",
28060
28185
  items: {
28061
28186
  type: "string",
28062
- enum: ["tables", "migrations", "services", "namespaces", "entities", "tenants", "controllers", "layouts", "tabs", "hierarchies", "protected-actions", "permissions", "frontend-routes", "feature-json", "all"]
28187
+ enum: ["tables", "migrations", "services", "namespaces", "entities", "tenants", "controllers", "layouts", "tabs", "hierarchies", "protected-actions", "permissions", "frontend-routes", "feature-json", "code-patterns", "all"]
28063
28188
  },
28064
28189
  description: "Types of checks to perform",
28065
28190
  default: ["all"]
@@ -34474,6 +34599,8 @@ async function scaffoldFeature(name, options, structure, config2, result, dryRun
34474
34599
  }
34475
34600
  async function scaffoldService(name, options, structure, config2, result, dryRun = false) {
34476
34601
  const hierarchy = resolveHierarchy(options?.navRoute);
34602
+ const tenantMode = resolveTenantMode(options);
34603
+ const tmFlags = tenantModeToTemplateFlags(tenantMode);
34477
34604
  const interfaceNamespace = options?.namespace || `${config2.conventions.namespaces.application}.Common.Interfaces`;
34478
34605
  const implNamespace = hierarchy.infraPath ? `${config2.conventions.namespaces.infrastructure}.Services.${hierarchy.infraPath.replace(/[\\/]/g, ".")}` : `${config2.conventions.namespaces.infrastructure}.Services`;
34479
34606
  const methods = options?.methods || ["GetByIdAsync", "GetAllAsync", "CreateAsync", "UpdateAsync", "DeleteAsync"];
@@ -34504,32 +34631,46 @@ using System.Linq;
34504
34631
  using Microsoft.EntityFrameworkCore;
34505
34632
  using Microsoft.Extensions.Logging;
34506
34633
  using SmartStack.Application.Common.Interfaces.Identity;
34634
+ {{#unless isNone}}
34507
34635
  using SmartStack.Application.Common.Interfaces.Tenants;
34636
+ {{/unless}}
34508
34637
  using SmartStack.Application.Common.Interfaces.Persistence;
34509
34638
 
34510
34639
  namespace {{implNamespace}};
34511
34640
 
34512
34641
  /// <summary>
34513
34642
  /// Service implementation for {{name}} operations.
34643
+ {{#if isStrict}}
34514
34644
  /// IMPORTANT: All queries MUST filter by tenant ID for multi-tenant isolation.
34645
+ {{else if isOptional}}
34646
+ /// Cross-tenant entity: EF global query filter includes shared (TenantId=null) + current tenant data.
34647
+ {{else if isScoped}}
34648
+ /// Scoped entity: EF global query filter includes shared + current tenant data. Scope controls visibility.
34649
+ {{/if}}
34515
34650
  /// IMPORTANT: GetAllAsync MUST support search parameter for frontend EntityLookup component.
34516
34651
  /// </summary>
34517
34652
  public class {{name}}Service : I{{name}}Service
34518
34653
  {
34519
34654
  private readonly IExtensionsDbContext _db;
34520
34655
  private readonly ICurrentUserService _currentUser;
34656
+ {{#unless isNone}}
34521
34657
  private readonly ICurrentTenantService _currentTenant;
34658
+ {{/unless}}
34522
34659
  private readonly ILogger<{{name}}Service> _logger;
34523
34660
 
34524
34661
  public {{name}}Service(
34525
34662
  IExtensionsDbContext db,
34526
34663
  ICurrentUserService currentUser,
34664
+ {{#unless isNone}}
34527
34665
  ICurrentTenantService currentTenant,
34666
+ {{/unless}}
34528
34667
  ILogger<{{name}}Service> logger)
34529
34668
  {
34530
34669
  _db = db;
34531
34670
  _currentUser = currentUser;
34671
+ {{#unless isNone}}
34532
34672
  _currentTenant = currentTenant;
34673
+ {{/unless}}
34533
34674
  _logger = logger;
34534
34675
  }
34535
34676
 
@@ -34537,14 +34678,28 @@ public class {{name}}Service : I{{name}}Service
34537
34678
  /// <inheritdoc />
34538
34679
  public async Task<object> {{this}}(CancellationToken cancellationToken = default)
34539
34680
  {
34681
+ {{#if ../isStrict}}
34540
34682
  var tenantId = _currentTenant.TenantId
34541
- ?? throw new UnauthorizedAccessException("Tenant context is required");
34683
+ ?? throw new TenantContextRequiredException();
34542
34684
  _logger.LogInformation("Executing {{this}} for tenant {TenantId}", tenantId);
34543
34685
  // TODO: Implement {{this}} \u2014 ALL queries must filter by tenantId
34686
+ {{else if ../isOptional}}
34687
+ // Cross-tenant: tenantId is nullable. null = shared data, Guid = tenant-specific.
34688
+ // EF global filter automatically includes shared + current tenant data in queries.
34689
+ var tenantId = _currentTenant.TenantId; // nullable \u2014 null means creating shared data
34690
+ _logger.LogInformation("Executing {{this}} (tenantId: {TenantId})", tenantId?.ToString() ?? "shared");
34691
+ // TODO: Implement {{this}}
34692
+ {{else if ../isScoped}}
34693
+ // Scoped: tenantId is nullable. Scope controls visibility (Tenant/Shared/Platform).
34694
+ var tenantId = _currentTenant.TenantId;
34695
+ _logger.LogInformation("Executing {{this}} (tenantId: {TenantId})", tenantId?.ToString() ?? "shared");
34696
+ // TODO: Implement {{this}}
34697
+ {{else}}
34698
+ _logger.LogInformation("Executing {{this}}");
34699
+ // TODO: Implement {{this}}
34700
+ {{/if}}
34544
34701
  // IMPORTANT: GetAllAsync MUST accept (string? search, int page, int pageSize) parameters
34545
- // to enable EntityLookup search on the frontend. Example:
34546
- // if (!string.IsNullOrWhiteSpace(search))
34547
- // query = query.Where(x => x.Name.Contains(search) || x.Code.Contains(search));
34702
+ // to enable EntityLookup search on the frontend.
34548
34703
  await Task.CompletedTask;
34549
34704
  throw new NotImplementedException();
34550
34705
  }
@@ -34555,7 +34710,7 @@ public class {{name}}Service : I{{name}}Service
34555
34710
  const diTemplate = `// Add to DependencyInjection.cs or ServiceCollectionExtensions.cs:
34556
34711
  services.AddScoped<I{{name}}Service, {{name}}Service>();
34557
34712
  `;
34558
- const context = { interfaceNamespace, implNamespace, name, methods };
34713
+ const context = { interfaceNamespace, implNamespace, name, methods, ...tmFlags };
34559
34714
  const interfaceContent = import_handlebars.default.compile(interfaceTemplate)(context);
34560
34715
  const implementationContent = import_handlebars.default.compile(implementationTemplate)(context);
34561
34716
  const diContent = import_handlebars.default.compile(diTemplate)(context);
@@ -34583,7 +34738,9 @@ async function scaffoldEntity(name, options, structure, config2, result, dryRun
34583
34738
  const hierarchy = resolveHierarchy(options?.navRoute);
34584
34739
  const namespace = options?.namespace || (hierarchy.domainPath ? `${config2.conventions.namespaces.domain}.${hierarchy.domainPath.replace(/[\\/]/g, ".")}` : config2.conventions.namespaces.domain);
34585
34740
  const baseEntity = options?.baseEntity;
34586
- const isSystemEntity = options?.isSystemEntity || false;
34741
+ const tenantMode = resolveTenantMode(options);
34742
+ const tmFlags = tenantModeToTemplateFlags(tenantMode);
34743
+ const isSystemEntity = tmFlags.isSystemEntity;
34587
34744
  const tablePrefix = options?.tablePrefix || "ref_";
34588
34745
  const schema = options?.schema || config2.conventions.schemas.platform;
34589
34746
  const entityTemplate = `using System;
@@ -34593,23 +34750,49 @@ namespace {{namespace}};
34593
34750
 
34594
34751
  /// <summary>
34595
34752
  /// {{name}} entity{{#if baseEntity}} extending {{baseEntity}}{{/if}}
34596
- {{#unless isSystemEntity}}
34597
- /// Tenant-scoped: data is isolated per tenant
34753
+ {{#if isStrict}}
34754
+ /// Tenant-scoped: data is isolated per tenant (ITenantEntity)
34755
+ {{else if isOptional}}
34756
+ /// Cross-tenant: data can be shared across tenants or tenant-specific (IOptionalTenantEntity)
34757
+ {{else if isScoped}}
34758
+ /// Scoped: data visibility controlled by EntityScope (IScopedTenantEntity)
34598
34759
  {{else}}
34599
34760
  /// Platform-level entity: no tenant isolation
34600
- {{/unless}}
34761
+ {{/if}}
34601
34762
  /// </summary>
34602
- public class {{name}} : BaseEntity{{#unless isSystemEntity}}, ITenantEntity, IAuditableEntity{{else}}, IAuditableEntity{{/unless}}
34763
+ {{#if isStrict}}
34764
+ public class {{name}} : BaseEntity, ITenantEntity, IAuditableEntity
34765
+ {{else if isOptional}}
34766
+ public class {{name}} : BaseEntity, IOptionalTenantEntity, IAuditableEntity
34767
+ {{else if isScoped}}
34768
+ public class {{name}} : BaseEntity, IScopedTenantEntity, IAuditableEntity
34769
+ {{else}}
34770
+ public class {{name}} : BaseEntity, IAuditableEntity
34771
+ {{/if}}
34603
34772
  {
34604
- {{#unless isSystemEntity}}
34773
+ {{#if hasTenantId}}
34605
34774
  // === MULTI-TENANT ===
34606
34775
 
34776
+ {{#if isNullableTenant}}
34777
+ /// <summary>
34778
+ /// Tenant identifier \u2014 null means shared across all tenants
34779
+ /// </summary>
34780
+ public Guid? TenantId { get; private set; }
34781
+ {{else}}
34607
34782
  /// <summary>
34608
34783
  /// Tenant identifier for multi-tenant isolation (required)
34609
34784
  /// </summary>
34610
34785
  public Guid TenantId { get; private set; }
34786
+ {{/if}}
34611
34787
 
34612
- {{/unless}}
34788
+ {{/if}}
34789
+ {{#if hasScope}}
34790
+ /// <summary>
34791
+ /// Visibility scope: Tenant (tenant-specific), Shared (all tenants), Platform (admin only)
34792
+ /// </summary>
34793
+ public EntityScope Scope { get; private set; }
34794
+
34795
+ {{/if}}
34613
34796
  // === AUDIT ===
34614
34797
 
34615
34798
  /// <summary>User who created this entity</summary>
@@ -34643,7 +34826,7 @@ public class {{name}} : BaseEntity{{#unless isSystemEntity}}, ITenantEntity, IAu
34643
34826
  /// <summary>
34644
34827
  /// Factory method to create a new {{name}}
34645
34828
  /// </summary>
34646
- {{#unless isSystemEntity}}
34829
+ {{#if isStrict}}
34647
34830
  /// <param name="tenantId">Required tenant identifier</param>
34648
34831
  public static {{name}} Create(
34649
34832
  Guid tenantId)
@@ -34658,6 +34841,37 @@ public class {{name}} : BaseEntity{{#unless isSystemEntity}}, ITenantEntity, IAu
34658
34841
  CreatedAt = DateTime.UtcNow
34659
34842
  };
34660
34843
  }
34844
+ {{else if isOptional}}
34845
+ /// <param name="tenantId">Tenant identifier \u2014 null for shared (cross-tenant) data</param>
34846
+ public static {{name}} Create(
34847
+ Guid? tenantId = null)
34848
+ {
34849
+ return new {{name}}
34850
+ {
34851
+ Id = Guid.NewGuid(),
34852
+ TenantId = tenantId,
34853
+ CreatedAt = DateTime.UtcNow
34854
+ };
34855
+ }
34856
+ {{else if isScoped}}
34857
+ /// <param name="tenantId">Tenant identifier \u2014 null for shared/platform data</param>
34858
+ /// <param name="scope">Visibility scope (default: Tenant)</param>
34859
+ public static {{name}} Create(
34860
+ Guid? tenantId = null,
34861
+ EntityScope scope = EntityScope.Tenant)
34862
+ {
34863
+ // Validate scope-tenantId consistency
34864
+ if (scope == EntityScope.Tenant && tenantId == null)
34865
+ throw new ArgumentException("TenantId is required when scope is Tenant", nameof(tenantId));
34866
+
34867
+ return new {{name}}
34868
+ {
34869
+ Id = Guid.NewGuid(),
34870
+ TenantId = tenantId,
34871
+ Scope = scope,
34872
+ CreatedAt = DateTime.UtcNow
34873
+ };
34874
+ }
34661
34875
  {{else}}
34662
34876
  public static {{name}} Create()
34663
34877
  {
@@ -34667,7 +34881,7 @@ public class {{name}} : BaseEntity{{#unless isSystemEntity}}, ITenantEntity, IAu
34667
34881
  CreatedAt = DateTime.UtcNow
34668
34882
  };
34669
34883
  }
34670
- {{/unless}}
34884
+ {{/if}}
34671
34885
 
34672
34886
  /// <summary>
34673
34887
  /// Update the entity
@@ -34695,13 +34909,32 @@ public class {{name}}Configuration : IEntityTypeConfiguration<{{name}}>
34695
34909
 
34696
34910
  builder.HasKey(e => e.Id);
34697
34911
 
34698
- {{#unless isSystemEntity}}
34699
- // Multi-tenant
34912
+ {{#if isStrict}}
34913
+ // Multi-tenant (strict: TenantId required)
34700
34914
  builder.Property(e => e.TenantId).IsRequired();
34701
34915
  builder.HasIndex(e => e.TenantId)
34702
34916
  .HasDatabaseName("IX_{{tablePrefix}}{{name}}s_TenantId");
34703
34917
 
34704
- {{/unless}}
34918
+ {{else if isOptional}}
34919
+ // Multi-tenant (optional: TenantId nullable \u2014 null = shared across tenants)
34920
+ builder.Property(e => e.TenantId).IsRequired(false);
34921
+ builder.HasIndex(e => e.TenantId)
34922
+ .HasDatabaseName("IX_{{tablePrefix}}{{name}}s_TenantId");
34923
+
34924
+ {{else if isScoped}}
34925
+ // Multi-tenant (scoped: TenantId nullable + EntityScope)
34926
+ builder.Property(e => e.TenantId).IsRequired(false);
34927
+ builder.HasIndex(e => e.TenantId)
34928
+ .HasDatabaseName("IX_{{tablePrefix}}{{name}}s_TenantId");
34929
+
34930
+ builder.Property(e => e.Scope)
34931
+ .IsRequired()
34932
+ .HasConversion<string>()
34933
+ .HasMaxLength(20);
34934
+ builder.HasIndex(e => new { e.TenantId, e.Scope })
34935
+ .HasDatabaseName("IX_{{tablePrefix}}{{name}}s_TenantId_Scope");
34936
+
34937
+ {{/if}}
34705
34938
  // Audit fields (from IAuditableEntity)
34706
34939
  builder.Property(e => e.CreatedBy).HasMaxLength(256);
34707
34940
  builder.Property(e => e.UpdatedBy).HasMaxLength(256);
@@ -34725,7 +34958,7 @@ public class {{name}}Configuration : IEntityTypeConfiguration<{{name}}>
34725
34958
  namespace,
34726
34959
  name,
34727
34960
  baseEntity,
34728
- isSystemEntity,
34961
+ ...tmFlags,
34729
34962
  tablePrefix,
34730
34963
  schema,
34731
34964
  infrastructureNamespace: config2.conventions.namespaces.infrastructure,
@@ -34765,7 +34998,9 @@ public class {{name}}Configuration : IEntityTypeConfiguration<{{name}}>
34765
34998
  result.instructions.push("");
34766
34999
  result.instructions.push("BaseEntity fields (inherited):");
34767
35000
  result.instructions.push(`- Id (Guid), CreatedAt (DateTime), UpdatedAt (DateTime?)`);
34768
- result.instructions.push(`${isSystemEntity ? "" : "- TenantId (Guid) \u2014 from ITenantEntity"}`);
35001
+ if (tenantMode === "strict") result.instructions.push("- TenantId (Guid) \u2014 from ITenantEntity");
35002
+ else if (tenantMode === "optional") result.instructions.push("- TenantId (Guid?) \u2014 from IOptionalTenantEntity (null = shared)");
35003
+ else if (tenantMode === "scoped") result.instructions.push("- TenantId (Guid?) + Scope (EntityScope) \u2014 from IScopedTenantEntity");
34769
35004
  result.instructions.push("- CreatedBy, UpdatedBy (string?) \u2014 from IAuditableEntity");
34770
35005
  result.instructions.push("- Add your own business properties (Code, Name, etc.) as needed");
34771
35006
  if (options?.withSeedData) {
@@ -34794,7 +35029,8 @@ async function scaffoldSeedData(name, options, structure, config2, result, dryRu
34794
35029
  "loc_": "Localization"
34795
35030
  };
34796
35031
  const domain = options?.seedDataDomain || domainMap[tablePrefix] || "Reference";
34797
- const isSystemEntity = options?.isSystemEntity || false;
35032
+ const tenantMode = resolveTenantMode(options);
35033
+ const tmFlags = tenantModeToTemplateFlags(tenantMode);
34798
35034
  const seedDataTemplate = `using SmartStack.Domain.{{domainNamespace}};
34799
35035
 
34800
35036
  namespace SmartStack.Infrastructure.Persistence.Seeding.Data.{{domain}};
@@ -34836,9 +35072,14 @@ public static class {{name}}SeedData
34836
35072
  new
34837
35073
  {
34838
35074
  Id = ExampleId,
34839
- {{#unless isSystemEntity}}
34840
- TenantId = (Guid?)null, // Seed data systeme sans tenant
34841
- {{/unless}}
35075
+ {{#if isStrict}}
35076
+ TenantId = SeedConstants.DefaultTenantId, // Seed data tenant-specific
35077
+ {{else if isOptional}}
35078
+ TenantId = (Guid?)null, // Shared cross-tenant seed data
35079
+ {{else if isScoped}}
35080
+ TenantId = (Guid?)null, // Shared seed data
35081
+ Scope = EntityScope.Shared,
35082
+ {{/if}}
34842
35083
  // TODO: Ajouter les proprietes specifiques
34843
35084
  CreatedAt = seedDate
34844
35085
  }
@@ -34855,7 +35096,7 @@ public static class {{name}}SeedData
34855
35096
  name,
34856
35097
  domain,
34857
35098
  domainNamespace: domain,
34858
- isSystemEntity,
35099
+ ...tmFlags,
34859
35100
  exampleGuid
34860
35101
  };
34861
35102
  const seedDataContent = import_handlebars.default.compile(seedDataTemplate)(context);
@@ -35380,7 +35621,9 @@ export function use{{name}}(options: Use{{name}}Options = {}) {
35380
35621
  result.instructions.push("```");
35381
35622
  }
35382
35623
  async function scaffoldTest(name, options, structure, config2, result, dryRun = false) {
35383
- const isSystemEntity = options?.isSystemEntity || false;
35624
+ const tenantMode = resolveTenantMode(options);
35625
+ const tmFlags = tenantModeToTemplateFlags(tenantMode);
35626
+ const isSystemEntity = tmFlags.isSystemEntity;
35384
35627
  const serviceTestTemplate2 = `using System;
35385
35628
  using System.Threading;
35386
35629
  using System.Threading.Tasks;
@@ -35486,7 +35729,7 @@ public class {{name}}ServiceTests
35486
35729
  `;
35487
35730
  const context = {
35488
35731
  name,
35489
- isSystemEntity
35732
+ ...tmFlags
35490
35733
  };
35491
35734
  const testContent = import_handlebars.default.compile(serviceTestTemplate2)(context);
35492
35735
  const testsPath = structure.application ? path10.join(path10.dirname(structure.application), `${path10.basename(structure.application)}.Tests`, "Services") : path10.join(config2.smartstack.projectPath, "Application.Tests", "Services");
@@ -35507,7 +35750,10 @@ public class {{name}}ServiceTests
35507
35750
  async function scaffoldDtos(name, options, structure, config2, result, dryRun = false) {
35508
35751
  const hierarchy = resolveHierarchy(options?.navRoute);
35509
35752
  const namespace = options?.namespace || (hierarchy.domainPath ? `${config2.conventions.namespaces.application}.${hierarchy.domainPath.replace(/[\\/]/g, ".")}.DTOs` : `${config2.conventions.namespaces.application}.DTOs`);
35510
- const isSystemEntity = options?.isSystemEntity || false;
35753
+ const tenantMode = resolveTenantMode(options);
35754
+ const tmFlags = tenantModeToTemplateFlags(tenantMode);
35755
+ const codePattern = options?.codePattern;
35756
+ const isAutoCode = codePattern && codePattern.strategy && codePattern.strategy !== "manual";
35511
35757
  const properties = options?.entityProperties || [
35512
35758
  { name: "Name", type: "string", required: true, maxLength: 200 },
35513
35759
  { name: "Description", type: "string?", required: false, maxLength: 500 }
@@ -35524,11 +35770,22 @@ public record {{name}}ResponseDto
35524
35770
  /// <summary>Unique identifier</summary>
35525
35771
  public Guid Id { get; init; }
35526
35772
 
35527
- {{#unless isSystemEntity}}
35773
+ {{#if isStrict}}
35528
35774
  /// <summary>Tenant identifier</summary>
35529
35775
  public Guid TenantId { get; init; }
35530
35776
 
35531
- {{/unless}}
35777
+ {{else if isOptional}}
35778
+ /// <summary>Tenant identifier \u2014 null for shared (cross-tenant) data</summary>
35779
+ public Guid? TenantId { get; init; }
35780
+
35781
+ {{else if isScoped}}
35782
+ /// <summary>Tenant identifier \u2014 null for shared/platform data</summary>
35783
+ public Guid? TenantId { get; init; }
35784
+
35785
+ /// <summary>Visibility scope</summary>
35786
+ public string Scope { get; init; } = "Tenant";
35787
+
35788
+ {{/if}}
35532
35789
  /// <summary>Unique code</summary>
35533
35790
  public string Code { get; init; } = string.Empty;
35534
35791
 
@@ -35547,7 +35804,37 @@ public record {{name}}ResponseDto
35547
35804
  public string? CreatedBy { get; init; }
35548
35805
  }
35549
35806
  `;
35550
- const createDtoTemplate = `using System;
35807
+ const createDtoTemplate = isAutoCode ? `using System;
35808
+ using System.ComponentModel.DataAnnotations;
35809
+
35810
+ namespace {{namespace}};
35811
+
35812
+ /// <summary>
35813
+ /// DTO for creating a new {{name}}.
35814
+ /// Code is auto-generated by ICodeGenerator&lt;{{name}}&gt; \u2014 not user-provided.
35815
+ /// </summary>
35816
+ public record Create{{name}}Dto
35817
+ {
35818
+ {{#each properties}}
35819
+ {{#if required}}
35820
+ /// <summary>{{name}} (required)</summary>
35821
+ [Required]
35822
+ {{#if maxLength}}
35823
+ [MaxLength({{maxLength}})]
35824
+ {{/if}}
35825
+ public {{type}} {{name}} { get; init; }{{#if (eq type "string")}} = string.Empty;{{/if}}
35826
+
35827
+ {{else}}
35828
+ /// <summary>{{name}} (optional)</summary>
35829
+ {{#if maxLength}}
35830
+ [MaxLength({{maxLength}})]
35831
+ {{/if}}
35832
+ public {{type}} {{name}} { get; init; }
35833
+
35834
+ {{/if}}
35835
+ {{/each}}
35836
+ }
35837
+ ` : `using System;
35551
35838
  using System.ComponentModel.DataAnnotations;
35552
35839
 
35553
35840
  namespace {{namespace}};
@@ -35616,7 +35903,7 @@ public record Update{{name}}Dto
35616
35903
  const context = {
35617
35904
  namespace,
35618
35905
  name,
35619
- isSystemEntity,
35906
+ ...tmFlags,
35620
35907
  properties
35621
35908
  };
35622
35909
  const responseContent = import_handlebars.default.compile(responseDtoTemplate)(context);
@@ -35640,14 +35927,49 @@ public record Update{{name}}Dto
35640
35927
  result.instructions.push(`- ${name}ResponseDto: For API responses`);
35641
35928
  result.instructions.push(`- Create${name}Dto: For POST requests`);
35642
35929
  result.instructions.push(`- Update${name}Dto: For PUT requests`);
35930
+ if (isAutoCode) {
35931
+ const cp2 = codePattern;
35932
+ const strategy = cp2.strategy || "sequential";
35933
+ const prefix = cp2.prefix || name.toLowerCase().substring(0, 3);
35934
+ const digits = cp2.digits || Math.max(4, Math.ceil(Math.log10((cp2.estimatedVolume || 1e3) * 10)));
35935
+ const separator = cp2.separator || "-";
35936
+ const includeTenant = cp2.includeTenantSlug !== false;
35937
+ const example = includeTenant ? `{tenant}${separator}${prefix}${separator}${"0".repeat(digits - 1)}1` : `${prefix}${separator}${"0".repeat(digits - 1)}1`;
35938
+ result.instructions.push("");
35939
+ result.instructions.push(`## Code Auto-Generation (strategy: ${strategy})`);
35940
+ result.instructions.push(`Code is auto-generated for ${name} \u2014 removed from Create${name}Dto.`);
35941
+ result.instructions.push(`Example: ${example}`);
35942
+ result.instructions.push("");
35943
+ result.instructions.push("Required implementation (see references/code-generation.md):");
35944
+ result.instructions.push(`1. Register ICodeGenerator<${name}> in DependencyInjection.cs:`);
35945
+ result.instructions.push(` services.AddScoped<ICodeGenerator<${name}>>(sp =>`);
35946
+ result.instructions.push(` new CodeGenerator<${name}>(`);
35947
+ result.instructions.push(` sp.GetRequiredService<IExtensionsDbContext>(),`);
35948
+ result.instructions.push(` sp.GetRequiredService<ICurrentTenantService>(),`);
35949
+ result.instructions.push(` new CodePatternConfig(CodeStrategy.${strategy.split("-").map((s) => s.charAt(0).toUpperCase() + s.slice(1)).join("")}, "${prefix}", ${includeTenant}, "${separator}", ${cp2.estimatedVolume || 1e3}, ${digits}),`);
35950
+ result.instructions.push(` sp.GetRequiredService<ILogger<CodeGenerator<${name}>>>()));`);
35951
+ result.instructions.push("");
35952
+ result.instructions.push(`2. Inject ICodeGenerator<${name}> in ${name}Service constructor`);
35953
+ result.instructions.push(`3. Call _codeGenerator.NextCodeAsync(ct) in CreateAsync BEFORE entity creation`);
35954
+ result.instructions.push(`4. Code field is NOT in Create${name}Dto \u2014 auto-generated, not user-provided`);
35955
+ }
35643
35956
  }
35644
35957
  async function scaffoldValidator(name, options, structure, config2, result, dryRun = false) {
35645
35958
  const hierarchy = resolveHierarchy(options?.navRoute);
35646
35959
  const namespace = options?.namespace || (hierarchy.domainPath ? `${config2.conventions.namespaces.application}.${hierarchy.domainPath.replace(/[\\/]/g, ".")}.Validators` : `${config2.conventions.namespaces.application}.Validators`);
35960
+ const codePatternValidator = options?.codePattern;
35961
+ const isAutoCodeValidator = codePatternValidator && codePatternValidator.strategy && codePatternValidator.strategy !== "manual";
35647
35962
  const properties = options?.entityProperties || [
35648
35963
  { name: "Name", type: "string", required: true, maxLength: 200 },
35649
35964
  { name: "Description", type: "string?", required: false, maxLength: 500 }
35650
35965
  ];
35966
+ const codeValidationRules = isAutoCodeValidator ? ` // Code is auto-generated by ICodeGenerator<{{name}}> \u2014 no validation needed in CreateDto
35967
+ ` : ` RuleFor(x => x.Code)
35968
+ .NotEmpty().WithMessage("Code is required")
35969
+ .MaximumLength(100).WithMessage("Code must not exceed 100 characters")
35970
+ .Matches("^[a-z0-9_-]+$").WithMessage("Code must be lowercase alphanumeric with underscores and hyphens");
35971
+
35972
+ `;
35651
35973
  const createValidatorTemplate = `using FluentValidation;
35652
35974
  using ${config2.conventions.namespaces.application}.DTOs;
35653
35975
 
@@ -35660,11 +35982,7 @@ public class Create{{name}}DtoValidator : AbstractValidator<Create{{name}}Dto>
35660
35982
  {
35661
35983
  public Create{{name}}DtoValidator()
35662
35984
  {
35663
- RuleFor(x => x.Code)
35664
- .NotEmpty().WithMessage("Code is required")
35665
- .MaximumLength(100).WithMessage("Code must not exceed 100 characters")
35666
- .Matches("^[a-z0-9_]+$").WithMessage("Code must be lowercase alphanumeric with underscores");
35667
-
35985
+ ${codeValidationRules}
35668
35986
  {{#each properties}}
35669
35987
  {{#if required}}
35670
35988
  RuleFor(x => x.{{name}})
@@ -35738,7 +36056,8 @@ public class Update{{name}}DtoValidator : AbstractValidator<Update{{name}}Dto>
35738
36056
  }
35739
36057
  async function scaffoldRepository(name, options, structure, config2, result, dryRun = false) {
35740
36058
  const hierarchy = resolveHierarchy(options?.navRoute);
35741
- const isSystemEntity = options?.isSystemEntity || false;
36059
+ const tenantMode = resolveTenantMode(options);
36060
+ const tmFlags = tenantModeToTemplateFlags(tenantMode);
35742
36061
  const schema = options?.schema || config2.conventions.schemas.platform;
35743
36062
  const dbContextName = schema === "extensions" ? "ExtensionsDbContext" : "CoreDbContext";
35744
36063
  const interfaceTemplate = `using System;
@@ -35757,14 +36076,43 @@ public interface I{{name}}Repository
35757
36076
  /// <summary>Get entity by ID</summary>
35758
36077
  Task<{{name}}?> GetByIdAsync(Guid id, CancellationToken ct = default);
35759
36078
 
36079
+ {{#if isStrict}}
36080
+ /// <summary>Get entity by code (tenant-scoped)</summary>
36081
+ Task<{{name}}?> GetByCodeAsync(Guid tenantId, string code, CancellationToken ct = default);
36082
+
36083
+ /// <summary>Get all entities for tenant</summary>
36084
+ Task<IReadOnlyList<{{name}}>> GetAllAsync(Guid tenantId, CancellationToken ct = default);
36085
+
36086
+ /// <summary>Check if code exists in tenant</summary>
36087
+ Task<bool> ExistsAsync(Guid tenantId, string code, CancellationToken ct = default);
36088
+ {{else if isOptional}}
36089
+ /// <summary>Get entity by code (tenant-scoped or shared)</summary>
36090
+ Task<{{name}}?> GetByCodeAsync(Guid? tenantId, string code, CancellationToken ct = default);
36091
+
36092
+ /// <summary>Get all entities (includes shared + current tenant via EF global filter)</summary>
36093
+ Task<IReadOnlyList<{{name}}>> GetAllAsync(CancellationToken ct = default);
36094
+
36095
+ /// <summary>Check if code exists (tenant-scoped or shared)</summary>
36096
+ Task<bool> ExistsAsync(Guid? tenantId, string code, CancellationToken ct = default);
36097
+ {{else if isScoped}}
36098
+ /// <summary>Get entity by code (scoped)</summary>
36099
+ Task<{{name}}?> GetByCodeAsync(Guid? tenantId, string code, CancellationToken ct = default);
36100
+
36101
+ /// <summary>Get all entities (includes shared + current tenant via EF global filter)</summary>
36102
+ Task<IReadOnlyList<{{name}}>> GetAllAsync(CancellationToken ct = default);
36103
+
36104
+ /// <summary>Check if code exists (scoped)</summary>
36105
+ Task<bool> ExistsAsync(Guid? tenantId, string code, CancellationToken ct = default);
36106
+ {{else}}
35760
36107
  /// <summary>Get entity by code</summary>
35761
- Task<{{name}}?> GetByCodeAsync({{#unless isSystemEntity}}Guid tenantId, {{/unless}}string code, CancellationToken ct = default);
36108
+ Task<{{name}}?> GetByCodeAsync(string code, CancellationToken ct = default);
35762
36109
 
35763
- /// <summary>Get all entities{{#unless isSystemEntity}} for tenant{{/unless}}</summary>
35764
- Task<IReadOnlyList<{{name}}>> GetAllAsync({{#unless isSystemEntity}}Guid tenantId, {{/unless}}CancellationToken ct = default);
36110
+ /// <summary>Get all entities</summary>
36111
+ Task<IReadOnlyList<{{name}}>> GetAllAsync(CancellationToken ct = default);
35765
36112
 
35766
- /// <summary>Check if code exists{{#unless isSystemEntity}} in tenant{{/unless}}</summary>
35767
- Task<bool> ExistsAsync({{#unless isSystemEntity}}Guid tenantId, {{/unless}}string code, CancellationToken ct = default);
36113
+ /// <summary>Check if code exists</summary>
36114
+ Task<bool> ExistsAsync(string code, CancellationToken ct = default);
36115
+ {{/if}}
35768
36116
 
35769
36117
  /// <summary>Add new entity</summary>
35770
36118
  Task<{{name}}> AddAsync({{name}} entity, CancellationToken ct = default);
@@ -35806,29 +36154,108 @@ public class {{name}}Repository : I{{name}}Repository
35806
36154
  .FirstOrDefaultAsync(e => e.Id == id, ct);
35807
36155
  }
35808
36156
 
36157
+ {{#if isStrict}}
35809
36158
  /// <inheritdoc />
35810
- public async Task<{{name}}?> GetByCodeAsync({{#unless isSystemEntity}}Guid tenantId, {{/unless}}string code, CancellationToken ct = default)
36159
+ public async Task<{{name}}?> GetByCodeAsync(Guid tenantId, string code, CancellationToken ct = default)
35811
36160
  {
35812
36161
  return await _context.{{name}}s
35813
- .FirstOrDefaultAsync(e => {{#unless isSystemEntity}}e.TenantId == tenantId && {{/unless}}e.Code == code.ToLowerInvariant(), ct);
36162
+ .FirstOrDefaultAsync(e => e.TenantId == tenantId && e.Code == code.ToLowerInvariant(), ct);
35814
36163
  }
35815
36164
 
35816
36165
  /// <inheritdoc />
35817
- public async Task<IReadOnlyList<{{name}}>> GetAllAsync({{#unless isSystemEntity}}Guid tenantId, {{/unless}}CancellationToken ct = default)
36166
+ public async Task<IReadOnlyList<{{name}}>> GetAllAsync(Guid tenantId, CancellationToken ct = default)
35818
36167
  {
35819
36168
  return await _context.{{name}}s
35820
- {{#unless isSystemEntity}}.Where(e => e.TenantId == tenantId){{/unless}}
36169
+ .Where(e => e.TenantId == tenantId)
35821
36170
  .OrderBy(e => e.Code)
35822
36171
  .ToListAsync(ct);
35823
36172
  }
35824
36173
 
35825
36174
  /// <inheritdoc />
35826
- public async Task<bool> ExistsAsync({{#unless isSystemEntity}}Guid tenantId, {{/unless}}string code, CancellationToken ct = default)
36175
+ public async Task<bool> ExistsAsync(Guid tenantId, string code, CancellationToken ct = default)
35827
36176
  {
35828
36177
  return await _context.{{name}}s
35829
- .AnyAsync(e => {{#unless isSystemEntity}}e.TenantId == tenantId && {{/unless}}e.Code == code.ToLowerInvariant(), ct);
36178
+ .AnyAsync(e => e.TenantId == tenantId && e.Code == code.ToLowerInvariant(), ct);
36179
+ }
36180
+ {{else if isOptional}}
36181
+ /// <inheritdoc />
36182
+ public async Task<{{name}}?> GetByCodeAsync(Guid? tenantId, string code, CancellationToken ct = default)
36183
+ {
36184
+ // For optional tenant: match exact tenantId (null = shared, Guid = tenant-specific)
36185
+ return await _context.{{name}}s
36186
+ .FirstOrDefaultAsync(e =>
36187
+ (tenantId == null ? e.TenantId == null : e.TenantId == tenantId)
36188
+ && e.Code == code.ToLowerInvariant(), ct);
35830
36189
  }
35831
36190
 
36191
+ /// <inheritdoc />
36192
+ public async Task<IReadOnlyList<{{name}}>> GetAllAsync(CancellationToken ct = default)
36193
+ {
36194
+ // EF global query filter automatically includes shared (null) + current tenant
36195
+ return await _context.{{name}}s
36196
+ .OrderBy(e => e.Code)
36197
+ .ToListAsync(ct);
36198
+ }
36199
+
36200
+ /// <inheritdoc />
36201
+ public async Task<bool> ExistsAsync(Guid? tenantId, string code, CancellationToken ct = default)
36202
+ {
36203
+ return await _context.{{name}}s
36204
+ .AnyAsync(e =>
36205
+ (tenantId == null ? e.TenantId == null : e.TenantId == tenantId)
36206
+ && e.Code == code.ToLowerInvariant(), ct);
36207
+ }
36208
+ {{else if isScoped}}
36209
+ /// <inheritdoc />
36210
+ public async Task<{{name}}?> GetByCodeAsync(Guid? tenantId, string code, CancellationToken ct = default)
36211
+ {
36212
+ return await _context.{{name}}s
36213
+ .FirstOrDefaultAsync(e =>
36214
+ (tenantId == null ? e.TenantId == null : e.TenantId == tenantId)
36215
+ && e.Code == code.ToLowerInvariant(), ct);
36216
+ }
36217
+
36218
+ /// <inheritdoc />
36219
+ public async Task<IReadOnlyList<{{name}}>> GetAllAsync(CancellationToken ct = default)
36220
+ {
36221
+ // EF global query filter automatically includes shared (null) + current tenant
36222
+ return await _context.{{name}}s
36223
+ .OrderBy(e => e.Code)
36224
+ .ToListAsync(ct);
36225
+ }
36226
+
36227
+ /// <inheritdoc />
36228
+ public async Task<bool> ExistsAsync(Guid? tenantId, string code, CancellationToken ct = default)
36229
+ {
36230
+ return await _context.{{name}}s
36231
+ .AnyAsync(e =>
36232
+ (tenantId == null ? e.TenantId == null : e.TenantId == tenantId)
36233
+ && e.Code == code.ToLowerInvariant(), ct);
36234
+ }
36235
+ {{else}}
36236
+ /// <inheritdoc />
36237
+ public async Task<{{name}}?> GetByCodeAsync(string code, CancellationToken ct = default)
36238
+ {
36239
+ return await _context.{{name}}s
36240
+ .FirstOrDefaultAsync(e => e.Code == code.ToLowerInvariant(), ct);
36241
+ }
36242
+
36243
+ /// <inheritdoc />
36244
+ public async Task<IReadOnlyList<{{name}}>> GetAllAsync(CancellationToken ct = default)
36245
+ {
36246
+ return await _context.{{name}}s
36247
+ .OrderBy(e => e.Code)
36248
+ .ToListAsync(ct);
36249
+ }
36250
+
36251
+ /// <inheritdoc />
36252
+ public async Task<bool> ExistsAsync(string code, CancellationToken ct = default)
36253
+ {
36254
+ return await _context.{{name}}s
36255
+ .AnyAsync(e => e.Code == code.ToLowerInvariant(), ct);
36256
+ }
36257
+ {{/if}}
36258
+
35832
36259
  /// <inheritdoc />
35833
36260
  public async Task<{{name}}> AddAsync({{name}} entity, CancellationToken ct = default)
35834
36261
  {
@@ -35854,7 +36281,7 @@ public class {{name}}Repository : I{{name}}Repository
35854
36281
  `;
35855
36282
  const context = {
35856
36283
  name,
35857
- isSystemEntity,
36284
+ ...tmFlags,
35858
36285
  dbContextName
35859
36286
  };
35860
36287
  const interfaceContent = import_handlebars.default.compile(interfaceTemplate)(context);
@@ -35981,7 +36408,12 @@ var init_scaffold_extension = __esm({
35981
36408
  },
35982
36409
  isSystemEntity: {
35983
36410
  type: "boolean",
35984
- description: "If true, creates a system entity without TenantId (for entity type)"
36411
+ description: "[DEPRECATED: use tenantMode] If true, creates a system entity without TenantId"
36412
+ },
36413
+ tenantMode: {
36414
+ type: "string",
36415
+ enum: ["strict", "optional", "scoped", "none"],
36416
+ description: "Tenant isolation mode: strict (ITenantEntity, default), optional (IOptionalTenantEntity, cross-tenant), scoped (IScopedTenantEntity with EntityScope), none (no tenant)"
35985
36417
  },
35986
36418
  tablePrefix: {
35987
36419
  type: "string",
@@ -36061,6 +36493,22 @@ var init_scaffold_extension = __esm({
36061
36493
  type: "string",
36062
36494
  enum: ["ancestors", "descendants", "both"],
36063
36495
  description: "Direction for hierarchy traversal function (default: both)"
36496
+ },
36497
+ codePattern: {
36498
+ type: "object",
36499
+ description: 'Code auto-generation pattern for this entity. When strategy != "manual", Code is auto-generated and removed from CreateDto.',
36500
+ properties: {
36501
+ strategy: {
36502
+ type: "string",
36503
+ enum: ["sequential", "timestamp-daily", "timestamp-minute", "year-sequential", "uuid-short", "manual"],
36504
+ description: "Code generation strategy"
36505
+ },
36506
+ prefix: { type: "string", description: "Entity prefix (2-6 lowercase letters, e.g., emp, inv)" },
36507
+ includeTenantSlug: { type: "boolean", description: "Include tenant slug in code (default: true)" },
36508
+ separator: { type: "string", enum: ["-", "_"], description: "Segment separator (default: -)" },
36509
+ estimatedVolume: { type: "number", description: "Estimated records per tenant (used for digit calculation via x10 rule)" },
36510
+ digits: { type: "number", description: "Digit count for sequential part (default: auto-calculated from volume)" }
36511
+ }
36064
36512
  }
36065
36513
  }
36066
36514
  }
@@ -52883,6 +53331,8 @@ async function handleScaffoldTests(args, config2) {
52883
53331
  files: [],
52884
53332
  instructions: []
52885
53333
  };
53334
+ const tenantMode = resolveTenantMode(input.options);
53335
+ const tmFlags = tenantModeToTemplateFlags(tenantMode);
52886
53336
  const options = {
52887
53337
  includeEdgeCases: input.options?.includeEdgeCases ?? true,
52888
53338
  includeTenantIsolation: input.options?.includeTenantIsolation ?? true,
@@ -52890,7 +53340,8 @@ async function handleScaffoldTests(args, config2) {
52890
53340
  includeAudit: input.options?.includeAudit ?? true,
52891
53341
  includeValidation: input.options?.includeValidation ?? true,
52892
53342
  includeAuthorization: input.options?.includeAuthorization ?? false,
52893
- isSystemEntity: input.options?.isSystemEntity ?? false
53343
+ isSystemEntity: tmFlags.isSystemEntity,
53344
+ ...tmFlags
52894
53345
  };
52895
53346
  const testTypes = input.testTypes || ["unit"];
52896
53347
  try {
@@ -53249,7 +53700,12 @@ var init_scaffold_tests = __esm({
53249
53700
  isSystemEntity: {
53250
53701
  type: "boolean",
53251
53702
  default: false,
53252
- description: "If true, entity has no TenantId"
53703
+ description: "[DEPRECATED: use tenantMode] If true, entity has no TenantId"
53704
+ },
53705
+ tenantMode: {
53706
+ type: "string",
53707
+ enum: ["strict", "optional", "scoped", "none"],
53708
+ description: "Tenant isolation mode: strict (default), optional (cross-tenant), scoped (with EntityScope), none (no tenant)"
53253
53709
  },
53254
53710
  dryRun: {
53255
53711
  type: "boolean",
@@ -56595,9 +57051,8 @@ import type {
56595
57051
  ${name},
56596
57052
  ${name}CreateRequest,
56597
57053
  ${name}UpdateRequest,
56598
- ${name}ListResponse,
56599
- PaginatedRequest,
56600
- PaginatedResponse
57054
+ PaginationParams,
57055
+ PaginatedResult
56601
57056
  } from '../types/${nameLower}';
56602
57057
 
56603
57058
  const ROUTE = getRoute('${navRoute}');
@@ -56606,8 +57061,8 @@ export const ${nameLower}Api = {
56606
57061
  ${methods.includes("getAll") ? ` /**
56607
57062
  * Get all ${name}s with pagination
56608
57063
  */
56609
- async getAll(params?: PaginatedRequest): Promise<PaginatedResponse<${name}>> {
56610
- const response = await apiClient.get<${name}ListResponse>(ROUTE.api, { params });
57064
+ async getAll(params?: PaginationParams): Promise<PaginatedResult<${name}>> {
57065
+ const response = await apiClient.get<PaginatedResult<${name}>>(ROUTE.api, { params });
56611
57066
  return response.data;
56612
57067
  },
56613
57068
  ` : ""}
@@ -56645,8 +57100,8 @@ ${methods.includes("delete") ? ` /**
56645
57100
  ${methods.includes("search") ? ` /**
56646
57101
  * Search ${name}s
56647
57102
  */
56648
- async search(query: string, params?: PaginatedRequest): Promise<PaginatedResponse<${name}>> {
56649
- const response = await apiClient.get<${name}ListResponse>(\`\${ROUTE.api}/search\`, {
57103
+ async search(query: string, params?: PaginationParams): Promise<PaginatedResult<${name}>> {
57104
+ const response = await apiClient.get<PaginatedResult<${name}>>(\`\${ROUTE.api}/search\`, {
56650
57105
  params: { q: query, ...params }
56651
57106
  });
56652
57107
  return response.data;
@@ -56707,28 +57162,23 @@ export interface ${name}UpdateRequest {
56707
57162
  isActive?: boolean;
56708
57163
  }
56709
57164
 
56710
- export interface ${name}ListResponse {
56711
- items: ${name}[];
56712
- totalCount: number;
56713
- pageSize: number;
56714
- currentPage: number;
56715
- totalPages: number;
56716
- }
56717
-
56718
- export interface PaginatedRequest {
57165
+ export interface PaginationParams {
56719
57166
  page?: number;
56720
57167
  pageSize?: number;
56721
57168
  sortBy?: string;
56722
57169
  sortDirection?: 'asc' | 'desc';
57170
+ search?: string;
56723
57171
  filter?: string;
56724
57172
  }
56725
57173
 
56726
- export interface PaginatedResponse<T> {
57174
+ export interface PaginatedResult<T> {
56727
57175
  items: T[];
56728
57176
  totalCount: number;
56729
57177
  pageSize: number;
56730
- currentPage: number;
57178
+ page: number;
56731
57179
  totalPages: number;
57180
+ hasPreviousPage: boolean;
57181
+ hasNextPage: boolean;
56732
57182
  }
56733
57183
  `;
56734
57184
  }
@@ -56741,7 +57191,7 @@ function generateHook(name, nameLower, methods) {
56741
57191
 
56742
57192
  import { useQuery, useMutation, useQueryClient, UseQueryOptions, UseMutationOptions } from '@tanstack/react-query';
56743
57193
  import { ${nameLower}Api } from '../services/api/${nameLower}';
56744
- import type { ${name}, ${name}CreateRequest, ${name}UpdateRequest, PaginatedRequest, PaginatedResponse } from '../types/${nameLower}';
57194
+ import type { ${name}, ${name}CreateRequest, ${name}UpdateRequest, PaginationParams, PaginatedResult } from '../types/${nameLower}';
56745
57195
 
56746
57196
  const QUERY_KEY = '${nameLower}s';
56747
57197
 
@@ -56749,8 +57199,8 @@ ${methods.includes("getAll") ? `/**
56749
57199
  * Hook to fetch paginated ${name} list
56750
57200
  */
56751
57201
  export function use${name}List(
56752
- params?: PaginatedRequest,
56753
- options?: Omit<UseQueryOptions<PaginatedResponse<${name}>>, 'queryKey' | 'queryFn'>
57202
+ params?: PaginationParams,
57203
+ options?: Omit<UseQueryOptions<PaginatedResult<${name}>>, 'queryKey' | 'queryFn'>
56754
57204
  ) {
56755
57205
  return useQuery({
56756
57206
  queryKey: [QUERY_KEY, 'list', params],
@@ -56831,8 +57281,8 @@ ${methods.includes("search") ? `/**
56831
57281
  */
56832
57282
  export function use${name}Search(
56833
57283
  query: string,
56834
- params?: PaginatedRequest,
56835
- options?: Omit<UseQueryOptions<PaginatedResponse<${name}>>, 'queryKey' | 'queryFn'>
57284
+ params?: PaginationParams,
57285
+ options?: Omit<UseQueryOptions<PaginatedResult<${name}>>, 'queryKey' | 'queryFn'>
56836
57286
  ) {
56837
57287
  return useQuery({
56838
57288
  queryKey: [QUERY_KEY, 'search', query, params],
@@ -57023,7 +57473,7 @@ async function scaffoldRoutes(input, config2) {
57023
57473
  result.instructions.push("import { PageLoader } from '@/components/ui/PageLoader';");
57024
57474
  result.instructions.push("");
57025
57475
  const importedComponents = /* @__PURE__ */ new Set();
57026
- for (const [context, applications] of Object.entries(routeTree)) {
57476
+ for (const [_context, applications] of Object.entries(routeTree)) {
57027
57477
  for (const [, modules] of Object.entries(applications)) {
57028
57478
  for (const route of modules) {
57029
57479
  const pageEntry = pageFiles.get(route.navRoute);