UNPKG

ruch

Version:

Revolutionary React TypeScript CLI with hexagonal architecture & AI-powered development assistance. Create maintainable, scalable applications with domain-driven design and integrated AI tooling.

170 lines (143 loc) 5.06 kB
import path from 'path'; import type { FileSystem } from './file-operations'; import { validateFileExists, readFile } from './file-operations'; import type { DomainInfo } from '../templates/context'; /** * Pure function to capitalize first letter of a string */ export const capitalizeFirstLetter = (str: string): string => str.charAt(0).toUpperCase() + str.slice(1); /** * Pure function to create domain info from domain name */ export const createDomainInfo = (domainName: string): DomainInfo => ({ name: domainName, capitalizedName: capitalizeFirstLetter(domainName) }); /** * Pure function to build domains directory path */ export const buildDomainsPath = (projectRoot: string = process.cwd()): string => path.join(projectRoot, 'src', 'domains'); /** * Pure function to build context directory path */ export const buildContextPath = (projectRoot: string = process.cwd()): string => path.join(projectRoot, 'src', 'context'); /** * Pure function to build service context file path */ export const buildServiceContextFilePath = (projectRoot: string = process.cwd()): string => path.join(buildContextPath(projectRoot), 'ServiceContext.tsx'); /** * Pure function to build service context test file path */ export const buildServiceContextTestFilePath = (projectRoot: string = process.cwd()): string => path.join(buildContextPath(projectRoot), 'ServiceContext.test.tsx'); /** * Pure function to validate if a domain has required structure */ export const validateDomainStructure = async ( fileSystem: FileSystem, domainPath: string, domainName: string ): Promise<boolean> => { const capitalizedName = capitalizeFirstLetter(domainName); const requiredFiles = [ path.join(domainPath, 'services', `${capitalizedName}Service.ts`), path.join(domainPath, 'adapters', `${capitalizedName}Adapter.ts`) ]; const validationResults = await Promise.all( requiredFiles.map(filePath => validateFileExists(fileSystem, filePath)) ); return validationResults.every(exists => exists); }; /** * Pure function to filter valid domains from directory entries */ export const filterValidDomains = async ( fileSystem: FileSystem, domainsPath: string, directoryEntries: string[] ): Promise<string[]> => { const validationPromises = directoryEntries.map(async (entry) => { const domainPath = path.join(domainsPath, entry); const isDirectory = await isDirectoryEntry(fileSystem, domainPath); if (!isDirectory) return null; const hasValidStructure = await validateDomainStructure(fileSystem, domainPath, entry); return hasValidStructure ? entry : null; }); const validationResults = await Promise.all(validationPromises); return validationResults.filter((entry): entry is string => entry !== null); }; /** * Pure function to check if path is a directory */ export const isDirectoryEntry = async ( fileSystem: FileSystem, entryPath: string ): Promise<boolean> => { try { const stats = await fileSystem.stat(entryPath); return stats.isDirectory(); } catch { return false; } }; /** * Get all valid domains from the domains directory */ export const getExistingDomains = async ( fileSystem: FileSystem, projectRoot: string = process.cwd() ): Promise<DomainInfo[]> => { const domainsPath = buildDomainsPath(projectRoot); const domainsExist = await validateFileExists(fileSystem, domainsPath); if (!domainsExist) { return []; } try { const entries = await fileSystem.readdir(domainsPath); const validDomainNames = await filterValidDomains(fileSystem, domainsPath, entries); return validDomainNames.map(createDomainInfo); } catch { return []; } }; /** * Parse existing service context to extract current domains */ export const parseExistingServiceContext = async ( fileSystem: FileSystem, projectRoot: string = process.cwd() ): Promise<DomainInfo[]> => { const contextFilePath = buildServiceContextFilePath(projectRoot); const contextExists = await validateFileExists(fileSystem, contextFilePath); if (!contextExists) { return []; } try { const content = await readFile(fileSystem, contextFilePath, 'utf8'); const serviceMatches = content.match(/(\w+)Service:\s*\w+Service/g) || []; const domainNames = serviceMatches .map((match: string) => match.replace(/Service:\s*\w+Service/, '')) .filter((name: string) => name.length > 0); return domainNames.map(createDomainInfo); } catch { return []; } }; /** * Find domains that are not yet in the service context */ export const findMissingDomains = async ( fileSystem: FileSystem, projectRoot: string = process.cwd() ): Promise<DomainInfo[]> => { const [existingDomains, contextDomains] = await Promise.all([ getExistingDomains(fileSystem, projectRoot), parseExistingServiceContext(fileSystem, projectRoot) ]); const contextDomainNames = new Set(contextDomains.map(d => d.name)); return existingDomains.filter(domain => !contextDomainNames.has(domain.name)); };