/** * lib/app-classification.ts — Single source of truth for the App / Module / * Section FOLDER + NAMESPACE classification of the artifacts generated by * `/ba-develop` (backend + tests). * * Imported by `scaffold-entity`, `scaffold-business`, `scaffold-controller`, * `scaffold-screen-controller` and the test scaffolders so that the folder a * file is written to, the C# namespace it declares, and the `using` another * emitter writes to import it ALL derive from the same builders — cross-emitter * drift becomes impossible (the same guarantee `url-conventions.ts` gives for * the API URLs). * * The business application is a first-class classification segment sitting just * before ``, mirroring the seeding layer (`Applications//`). The * .NET project roots and root namespaces (`.Domain`, `.Api`, …) are * NEVER touched — only the suffix UNDER each project changes. * * ── Domain exception (folder moves, namespace stays flat) ─────────────────── * Entities / events / EF-configurations MOVE into `//` folders but * KEEP the flat `.Domain.Entities` / `.Infrastructure.Persistence. * Configurations` namespaces. A cross-module FK configuration emits * `builder.HasOne()` and must resolve EVERY entity type through a * single `using .Domain.Entities;` — the scaffolder does not know a * cross-module target's module, so one flat Domain namespace is required. * Application / Services / Api carry the full `.` namespace (no * cross-module type references exist in the generated code there). * * Segments are PascalCase via `toPascalCase` (kebab `applicationCode`/`module`/ * `section` → `Crm`/`HumanResources`/`TypesAffaire`). This unifies the historical * `capitalize()` (scaffold-business/controller) vs `toPascalCase()` * (scaffold-screen-controller) split — identical for single-token codes, correct * for multi-word ones. */ import { toPascalCase } from './string-utils.js' // ─── Segments ─── /** Business-application segment (PascalCase). `applicationCode` is kebab (e.g. `crm` → `Crm`). */ export function appSegment(applicationCode: string): string { return toPascalCase(applicationCode) } /** Module segment (PascalCase). Unifies the historical capitalize()/toPascalCase() split. */ export function moduleSegment(module: string): string { return toPascalCase(module) } /** Section segment (PascalCase) — used where a section is a real folder/namespace. */ export function sectionSegment(section: string): string { return toPascalCase(section) } // ─── Namespaces ─── /** Domain entities + events — FLAT (see "Domain exception" above). */ export function domainEntitiesNs(ns: string): string { return `${ns}.Domain.Entities` } /** EF configurations — FLAT (see "Domain exception" above). */ export function configurationsNs(ns: string): string { return `${ns}.Infrastructure.Persistence.Configurations` } /** Application CQRS root — carries `.`. Callers append `.DTOs`, `.Commands`, … */ export function applicationNs(ns: string, applicationCode: string, module: string): string { return `${ns}.Application.${appSegment(applicationCode)}.${moduleSegment(module)}` } /** Infrastructure service implementations — carries `.`. */ export function servicesNs(ns: string, applicationCode: string, module: string): string { return `${ns}.Infrastructure.Services.${appSegment(applicationCode)}.${moduleSegment(module)}` } /** Integration controller namespace — carries `.` (entity-grained). */ export function controllersNs(ns: string, applicationCode: string, module: string): string { return `${ns}.Api.Controllers.${appSegment(applicationCode)}.${moduleSegment(module)}` } /** Screen-driven controller namespace — carries `..
` (section-grained). */ export function screenControllersNs(ns: string, applicationCode: string, module: string, section: string): string { return `${controllersNs(ns, applicationCode, module)}.${sectionSegment(section)}` } /** Permissions namespace — carries `.` (section stays the inner class + filename). */ export function permissionsNs(ns: string, applicationCode: string, module: string): string { return `${ns}.Api.Permissions.${appSegment(applicationCode)}.${moduleSegment(module)}` } // ─── Folders (relative to the .NET project root; backend `src/…`) ─── /** Domain entities/events folder — `src/.Domain/Entities//`. */ export function domainEntitiesDir(ns: string, applicationCode: string, module: string): string { return `src/${ns}.Domain/Entities/${appSegment(applicationCode)}/${moduleSegment(module)}` } /** EF configurations folder — `src/.Infrastructure/Persistence/Configurations//`. */ export function configurationsDir(ns: string, applicationCode: string, module: string): string { return `src/${ns}.Infrastructure/Persistence/Configurations/${appSegment(applicationCode)}/${moduleSegment(module)}` } /** Application CQRS folder — `src/.Application//`. Callers append `/DTOs`, `/Commands`, … */ export function applicationDir(ns: string, applicationCode: string, module: string): string { return `src/${ns}.Application/${appSegment(applicationCode)}/${moduleSegment(module)}` } /** Service implementations folder — `src/.Infrastructure/Services//`. */ export function servicesDir(ns: string, applicationCode: string, module: string): string { return `src/${ns}.Infrastructure/Services/${appSegment(applicationCode)}/${moduleSegment(module)}` } /** Integration controllers folder — `src/.Api/Controllers//`. */ export function controllersDir(ns: string, applicationCode: string, module: string): string { return `src/${ns}.Api/Controllers/${appSegment(applicationCode)}/${moduleSegment(module)}` } /** Screen controllers folder — `src/.Api/Controllers///
`. */ export function screenControllersDir(ns: string, applicationCode: string, module: string, section: string): string { return `${controllersDir(ns, applicationCode, module)}/${sectionSegment(section)}` } /** Permissions folder — `src/.Api/Permissions//`. */ export function permissionsDir(ns: string, applicationCode: string, module: string): string { return `src/${ns}.Api/Permissions/${appSegment(applicationCode)}/${moduleSegment(module)}` } // ─── Tests (backend xUnit project; folder == `Tests///…`) ─── /** Per-module test folder — `Tests//`. Callers append `/Domain`, `/Api`, … */ export function testsModuleDir(applicationCode: string, module: string): string { return `Tests/${appSegment(applicationCode)}/${moduleSegment(module)}` } /** Acceptance-test folder — `Tests///
` (section-grained). */ export function acceptanceTestsDir(applicationCode: string, module: string, section: string): string { return `${testsModuleDir(applicationCode, module)}/${sectionSegment(section)}` } // ─── Frontend (lowercase). ONE web app per PROJECT (web/-web), hosting EVERY // business application — which is precisely why the paths below carry the app segment. ─── /** API-client feature folder — `src/features///` (entity-grained). */ export function featureDir(applicationCode: string, module: string, entityLower: string): string { return `src/features/${applicationCode.toLowerCase()}/${module.toLowerCase()}/${entityLower}` } /** Frontend unit-test folder — `tests///`. */ export function frontendTestDir(applicationCode: string, module: string, entityLower: string): string { return `tests/${applicationCode.toLowerCase()}/${module.toLowerCase()}/${entityLower}` } /** * App-scoped identity for a module's generated front-end extension files * (`src/extensions/-Registry.ts` + `…Routes.ts`). * * Two applications may legitimately reuse a module CODE (e.g. both RH and * CLIENTS ship a `configuration` module). Keying these files by module code * ALONE made the second app OVERWRITE the first — the nav menu + PageRegistry * merged (BUG A: `Absolute route path "/rh/configuration/…" nested under * "/…/clients"`). Prefixing with the app code makes both coexist. * * SINGLE source of truth: `scaffold-routes` (the emitter of the two files) and * `scaffold-component` (which imports `@/extensions/Routes`) BOTH call this, * so the filename and the import path can never drift. */ export function extensionsModuleId(applicationCode: string, module: string): string { return `${applicationCode.toLowerCase()}-${module.toLowerCase()}` } // ─── Legacy (pre-classification) locations — for the idempotent relocate ─── // Each returns the OLD directory where the same artifact used to live, so an // emitter can delete the stale file when it writes the new classified one // (re-running `/ba-develop` MOVES files instead of orphaning them — avoiding the // duplicate `IEntityTypeConfiguration`/handler that breaks the app at boot). /** OLD flat entities/events folder — `src/.Domain/Entities`. */ export function legacyDomainEntitiesDir(ns: string): string { return `src/${ns}.Domain/Entities` } /** OLD flat EF configurations folder — `src/.Infrastructure/Persistence/Configurations`. */ export function legacyConfigurationsDir(ns: string): string { return `src/${ns}.Infrastructure/Persistence/Configurations` } /** OLD module-only Application folder — `src/.Application/`. */ export function legacyApplicationDir(ns: string, module: string): string { return `src/${ns}.Application/${moduleSegment(module)}` } /** OLD module-only services folder — `src/.Infrastructure/Services/`. */ export function legacyServicesDir(ns: string, module: string): string { return `src/${ns}.Infrastructure/Services/${moduleSegment(module)}` } /** OLD module-only controllers folder — `src/.Api/Controllers/`. */ export function legacyControllersDir(ns: string, module: string): string { return `src/${ns}.Api/Controllers/${moduleSegment(module)}` } /** OLD screen-controller bucket — `src/.Api/Controllers//Screens`. */ export function legacyScreenControllersDir(ns: string, module: string): string { return `src/${ns}.Api/Controllers/${moduleSegment(module)}/Screens` } /** OLD module-only permissions folder — `src/.Api/Permissions/`. */ export function legacyPermissionsDir(ns: string, module: string): string { return `src/${ns}.Api/Permissions/${moduleSegment(module)}` } /** OLD module-only test folder — `Tests/`. Callers append `/Domain`, `/Acceptance`, … */ export function legacyTestsModuleDir(module: string): string { return `Tests/${moduleSegment(module)}` } /** OLD module-only feature folder — `src/features//`. */ export function legacyFeatureDir(module: string, entityLower: string): string { return `src/features/${module.toLowerCase()}/${entityLower}` } /** OLD module-only frontend-test folder — `tests//`. */ export function legacyFrontendTestDir(module: string, entityLower: string): string { return `tests/${module.toLowerCase()}/${entityLower}` }