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.

279 lines (237 loc) 8.65 kB
import fs from 'fs-extra'; import path from 'path'; import readline from 'readline'; import type { FileSystem } from '../utils/file-operations'; import { validateFileExists, createDirectory, writeFile, readFile } from '../utils/file-operations'; import type { Logger } from '../utils/logging'; import { logError, logSuccess, logInfo, logWarning } from '../utils/logging'; import { buildContextPath, buildServiceContextFilePath, buildServiceContextTestFilePath, getExistingDomains, parseExistingServiceContext, findMissingDomains } from '../utils/domain-analyzer'; import { getEmptyServiceContextTemplate, getServiceContextTemplate, getServiceContextTestTemplate } from '../templates/context'; import type { DomainInfo } from '../templates/context'; /** * Pure function to validate context creation preconditions */ const validateContextCreationPreconditions = async ( fileSystem: FileSystem, contextFilePath: string ): Promise<{ canCreate: boolean; reason?: string }> => { const contextExists = await fileSystem.exists(contextFilePath); if (contextExists) { return { canCreate: false, reason: 'Service context already exists' }; } return { canCreate: true }; }; /** * Pure function to validate context update preconditions */ const validateContextUpdatePreconditions = async ( fileSystem: FileSystem, contextFilePath: string ): Promise<{ canUpdate: boolean; reason?: string }> => { const contextExists = await fileSystem.exists(contextFilePath); if (!contextExists) { return { canUpdate: false, reason: 'Service context does not exist. Use "context init" or "context generate" first.' }; } return { canUpdate: true }; }; /** * Pure function to create context files */ const createContextFiles = async ( fileSystem: FileSystem, contextPath: string, contextFilePath: string, testFilePath: string, contextContent: string, testContent: string ): Promise<void> => { await fileSystem.ensureDir(contextPath); await fileSystem.writeFile(contextFilePath, contextContent, 'utf8'); await fileSystem.writeFile(testFilePath, testContent, 'utf8'); }; /** * Pure function to log context creation success */ const logContextCreationSuccess = ( log: Logger, contextFilePath: string, domains: DomainInfo[] ): void => { logSuccess(log, 'Service context created successfully!'); logInfo(log, `Location: ${contextFilePath}`); if (domains.length > 0) { logInfo(log, `Integrated ${domains.length} domain(s):`); domains.forEach(domain => logInfo(log, ` - ${domain.name}`)); } logInfo(log, '💡 Import with: import { useServices } from \'./context/ServiceContext\''); }; /** * Pure function to ask for user confirmation */ const askForConfirmation = (question: string): Promise<boolean> => { return new Promise((resolve) => { const rl = readline.createInterface({ input: process.stdin, output: process.stdout }); rl.question(`${question} (y/N): `, (answer) => { rl.close(); resolve(answer.toLowerCase() === 'y' || answer.toLowerCase() === 'yes'); }); }); }; /** * Initialize empty service context */ export const initServiceContext = async ( fileSystem: FileSystem = fs, log: Logger = require('../utils/logger').logger, projectRoot: string = process.cwd() ): Promise<void> => { const contextPath = buildContextPath(projectRoot); const contextFilePath = buildServiceContextFilePath(projectRoot); const testFilePath = buildServiceContextTestFilePath(projectRoot); try { const validation = await validateContextCreationPreconditions(fileSystem, contextFilePath); if (!validation.canCreate) { logWarning(log, validation.reason || 'Cannot create service context'); return; } logInfo(log, 'Initializing empty service context...'); const contextContent = getEmptyServiceContextTemplate(); const testContent = getServiceContextTestTemplate([]); await createContextFiles( fileSystem, contextPath, contextFilePath, testFilePath, contextContent, testContent ); logContextCreationSuccess(log, contextFilePath, []); } catch (error) { logError(log, `Error initializing service context: ${error instanceof Error ? error.message : 'Unknown error'}`); } }; /** * Generate service context with all existing domains */ export const generateServiceContext = async ( fileSystem: FileSystem = fs, log: Logger = require('../utils/logger').logger, projectRoot: string = process.cwd() ): Promise<void> => { const contextPath = buildContextPath(projectRoot); const contextFilePath = buildServiceContextFilePath(projectRoot); const testFilePath = buildServiceContextTestFilePath(projectRoot); try { const contextExists = await fileSystem.exists(contextFilePath); if (contextExists) { logWarning(log, 'Service context already exists. Use "context update" to add new domains.'); return; } logInfo(log, 'Generating service context with existing domains...'); const domains = await getExistingDomains(fileSystem, projectRoot); if (domains.length === 0) { logWarning(log, 'No domains found. Creating empty service context.'); await initServiceContext(fileSystem, log, projectRoot); return; } const contextContent = getServiceContextTemplate(domains); const testContent = getServiceContextTestTemplate(domains); await createContextFiles( fileSystem, contextPath, contextFilePath, testFilePath, contextContent, testContent ); logContextCreationSuccess(log, contextFilePath, domains); } catch (error) { logError(log, `Error generating service context: ${error instanceof Error ? error.message : 'Unknown error'}`); } }; /** * Update service context with missing domains */ export const updateServiceContext = async ( fileSystem: FileSystem = fs, log: Logger = require('../utils/logger').logger, projectRoot: string = process.cwd(), skipConfirmation: boolean = false ): Promise<void> => { const contextFilePath = buildServiceContextFilePath(projectRoot); const testFilePath = buildServiceContextTestFilePath(projectRoot); try { const validation = await validateContextUpdatePreconditions(fileSystem, contextFilePath); if (!validation.canUpdate) { logError(log, validation.reason || 'Cannot update service context'); return; } logInfo(log, 'Checking for missing domains...'); const missingDomains = await findMissingDomains(fileSystem, projectRoot); if (missingDomains.length === 0) { logInfo(log, 'Service context is already up to date. No missing domains found.'); return; } logInfo(log, `Found ${missingDomains.length} missing domain(s):`); missingDomains.forEach(domain => logInfo(log, ` - ${domain.name}`)); // Create backup before overwriting const backupPath = `${contextFilePath}.backup.${Date.now()}`; const existingContent = await fileSystem.readFile(contextFilePath, 'utf8'); await fileSystem.writeFile(backupPath, existingContent, 'utf8'); logWarning(log, '⚠️ WARNING: This will overwrite your existing ServiceContext.tsx'); logInfo(log, `📁 Backup created at: ${backupPath}`); logInfo(log, '💡 Review the backup if you had custom modifications'); // Ask for confirmation unless skipped (for tests) if (!skipConfirmation) { const confirmed = await askForConfirmation('Do you want to continue?'); if (!confirmed) { logInfo(log, '❌ Operation cancelled by user'); // Remove the backup since we're not proceeding await fileSystem.remove(backupPath); return; } } // Get all domains (existing + missing) for regeneration const allDomains = await getExistingDomains(fileSystem, projectRoot); const contextContent = getServiceContextTemplate(allDomains); const testContent = getServiceContextTestTemplate(allDomains); await fileSystem.writeFile(contextFilePath, contextContent, 'utf8'); await fileSystem.writeFile(testFilePath, testContent, 'utf8'); logSuccess(log, 'Service context updated successfully!'); logInfo(log, `Added ${missingDomains.length} domain(s) to service context`); logInfo(log, `Total domains: ${allDomains.length}`); logInfo(log, `🔄 If you had custom code, restore it from: ${backupPath}`); } catch (error) { logError(log, `Error updating service context: ${error instanceof Error ? error.message : 'Unknown error'}`); } };