import * as plugins from './mod.plugins.js'; import * as helpers from './helpers.js'; import { ServiceConfiguration } from './classes.serviceconfiguration.js'; import { DockerContainer } from './classes.dockercontainer.js'; import { logger } from '../gitzone.logging.js'; export class ServiceManager { private config: ServiceConfiguration; private docker: DockerContainer; private enabledServices: string[] | null = null; constructor() { this.config = new ServiceConfiguration(); this.docker = new DockerContainer(); } /** * Initialize the service manager */ public async init(): Promise { // Check Docker availability if (!(await this.docker.checkDocker())) { logger.log('error', 'Error: Docker is not installed. Please install Docker first.'); process.exit(1); } // Load or create configuration await this.config.loadOrCreate(); logger.log('info', `📋 Project: ${this.config.getConfig().PROJECT_NAME}`); // Load service selection from npmextra.json await this.loadServiceConfiguration(); // Validate and update ports if needed await this.config.validateAndUpdatePorts(); } /** * Load service configuration from npmextra.json */ private async loadServiceConfiguration(): Promise { const npmextraConfig = new plugins.npmextra.Npmextra(process.cwd()); const gitzoneConfig = npmextraConfig.dataFor('gitzone', {}); // Check if services array exists if (!gitzoneConfig.services || !Array.isArray(gitzoneConfig.services) || gitzoneConfig.services.length === 0) { // Prompt user to select services const smartinteract = new plugins.smartinteract.SmartInteract(); const response = await smartinteract.askQuestion({ name: 'services', type: 'checkbox', message: 'Which services do you want to enable for this project?', choices: [ { name: 'MongoDB', value: 'mongodb' }, { name: 'MinIO (S3)', value: 'minio' }, { name: 'Elasticsearch', value: 'elasticsearch' } ], default: ['mongodb', 'minio', 'elasticsearch'] }); this.enabledServices = response.value || ['mongodb', 'minio', 'elasticsearch']; // Save to npmextra.json await this.saveServiceConfiguration(this.enabledServices); } else { this.enabledServices = gitzoneConfig.services; logger.log('info', `🔧 Enabled services: ${this.enabledServices.join(', ')}`); } } /** * Save service configuration to npmextra.json */ private async saveServiceConfiguration(services: string[]): Promise { const npmextraPath = plugins.path.join(process.cwd(), 'npmextra.json'); let npmextraData: any = {}; // Read existing npmextra.json if it exists if (await plugins.smartfs.file(npmextraPath).exists()) { const content = await plugins.smartfs.file(npmextraPath).encoding('utf8').read(); npmextraData = JSON.parse(content as string); } // Update gitzone.services if (!npmextraData.gitzone) { npmextraData.gitzone = {}; } npmextraData.gitzone.services = services; // Write back to npmextra.json await plugins.smartfs .file(npmextraPath) .encoding('utf8') .write(JSON.stringify(npmextraData, null, 2)); logger.log('ok', `✅ Saved service configuration to npmextra.json`); logger.log('info', `🔧 Enabled services: ${services.join(', ')}`); } /** * Check if a service is enabled */ private isServiceEnabled(service: string): boolean { if (!this.enabledServices) { return true; // If no configuration, enable all } return this.enabledServices.includes(service); } /** * Start all enabled services */ public async startAll(): Promise { let first = true; if (this.isServiceEnabled('mongodb')) { if (!first) console.log(); await this.startMongoDB(); first = false; } if (this.isServiceEnabled('minio')) { if (!first) console.log(); await this.startMinIO(); first = false; } if (this.isServiceEnabled('elasticsearch')) { if (!first) console.log(); await this.startElasticsearch(); first = false; } } /** * Stop all enabled services */ public async stopAll(): Promise { let first = true; if (this.isServiceEnabled('mongodb')) { if (!first) console.log(); await this.stopMongoDB(); first = false; } if (this.isServiceEnabled('minio')) { if (!first) console.log(); await this.stopMinIO(); first = false; } if (this.isServiceEnabled('elasticsearch')) { if (!first) console.log(); await this.stopElasticsearch(); first = false; } } /** * Start MongoDB service */ public async startMongoDB(): Promise { logger.log('note', '📦 MongoDB:'); const config = this.config.getConfig(); const containers = this.config.getContainerNames(); const directories = this.config.getDataDirectories(); // Ensure data directory exists await plugins.smartfs.directory(directories.mongo).recursive().create(); const status = await this.docker.getStatus(containers.mongo); switch (status) { case 'running': logger.log('ok', ' Already running ✓'); break; case 'stopped': // Check if port mapping matches config const mongoPortMappings = await this.docker.getPortMappings(containers.mongo); if (mongoPortMappings && mongoPortMappings['27017'] !== config.MONGODB_PORT) { logger.log('note', ' Port configuration changed, recreating container...'); await this.docker.remove(containers.mongo, true); // Fall through to create new container const success = await this.docker.run({ name: containers.mongo, image: 'mongo:7.0', ports: { [`0.0.0.0:${config.MONGODB_PORT}`]: '27017' }, volumes: { [directories.mongo]: '/data/db' }, environment: { MONGO_INITDB_ROOT_USERNAME: config.MONGODB_USER, MONGO_INITDB_ROOT_PASSWORD: config.MONGODB_PASS, MONGO_INITDB_DATABASE: config.MONGODB_NAME }, restart: 'unless-stopped', command: '--bind_ip_all' }); if (success) { logger.log('ok', ' Recreated with new port ✓'); } else { logger.log('error', ' Failed to recreate container'); } } else { // Ports match, just start the container if (await this.docker.start(containers.mongo)) { logger.log('ok', ' Started ✓'); } else { logger.log('error', ' Failed to start'); } } break; case 'not_exists': logger.log('note', ' Creating container...'); const success = await this.docker.run({ name: containers.mongo, image: 'mongo:7.0', ports: { [`0.0.0.0:${config.MONGODB_PORT}`]: '27017' }, volumes: { [directories.mongo]: '/data/db' }, environment: { MONGO_INITDB_ROOT_USERNAME: config.MONGODB_USER, MONGO_INITDB_ROOT_PASSWORD: config.MONGODB_PASS, MONGO_INITDB_DATABASE: config.MONGODB_NAME }, restart: 'unless-stopped', command: '--bind_ip_all' }); if (success) { logger.log('ok', ' Created and started ✓'); } else { logger.log('error', ' Failed to create container'); } break; } logger.log('info', ` Container: ${containers.mongo}`); logger.log('info', ` Port: ${config.MONGODB_PORT}`); logger.log('info', ` Connection: ${this.config.getMongoConnectionString()}`); // Show Compass connection string const networkIp = await helpers.getLocalNetworkIp(); const compassString = `mongodb://${config.MONGODB_USER}:${config.MONGODB_PASS}@${networkIp}:${config.MONGODB_PORT}/${config.MONGODB_NAME}?authSource=admin`; logger.log('ok', ` Compass: ${compassString}`); } /** * Start MinIO service */ public async startMinIO(): Promise { logger.log('note', '📦 S3/MinIO:'); const config = this.config.getConfig(); const containers = this.config.getContainerNames(); const directories = this.config.getDataDirectories(); // Ensure data directory exists await plugins.smartfs.directory(directories.minio).recursive().create(); const status = await this.docker.getStatus(containers.minio); switch (status) { case 'running': logger.log('ok', ' Already running ✓'); break; case 'stopped': // Check if port mapping matches config const minioPortMappings = await this.docker.getPortMappings(containers.minio); if (minioPortMappings && (minioPortMappings['9000'] !== config.S3_PORT || minioPortMappings['9001'] !== config.S3_CONSOLE_PORT)) { logger.log('note', ' Port configuration changed, recreating container...'); await this.docker.remove(containers.minio, true); // Fall through to create new container const success = await this.docker.run({ name: containers.minio, image: 'minio/minio', ports: { [config.S3_PORT]: '9000', [config.S3_CONSOLE_PORT]: '9001' }, volumes: { [directories.minio]: '/data' }, environment: { MINIO_ROOT_USER: config.S3_ACCESSKEY, MINIO_ROOT_PASSWORD: config.S3_SECRETKEY }, restart: 'unless-stopped', command: 'server /data --console-address ":9001"' }); if (success) { logger.log('ok', ' Recreated with new ports ✓'); // Wait for MinIO to be ready await plugins.smartdelay.delayFor(3000); // Create default bucket await this.docker.exec( containers.minio, `mc alias set local http://localhost:9000 ${config.S3_ACCESSKEY} ${config.S3_SECRETKEY}` ); await this.docker.exec( containers.minio, `mc mb local/${config.S3_BUCKET}` ); logger.log('ok', ` Bucket '${config.S3_BUCKET}' created ✓`); } else { logger.log('error', ' Failed to recreate container'); } } else { // Ports match, just start the container if (await this.docker.start(containers.minio)) { logger.log('ok', ' Started ✓'); } else { logger.log('error', ' Failed to start'); } } break; case 'not_exists': logger.log('note', ' Creating container...'); const success = await this.docker.run({ name: containers.minio, image: 'minio/minio', ports: { [config.S3_PORT]: '9000', [config.S3_CONSOLE_PORT]: '9001' }, volumes: { [directories.minio]: '/data' }, environment: { MINIO_ROOT_USER: config.S3_ACCESSKEY, MINIO_ROOT_PASSWORD: config.S3_SECRETKEY }, restart: 'unless-stopped', command: 'server /data --console-address ":9001"' }); if (success) { logger.log('ok', ' Created and started ✓'); // Wait for MinIO to be ready await plugins.smartdelay.delayFor(3000); // Create default bucket await this.docker.exec( containers.minio, `mc alias set local http://localhost:9000 ${config.S3_ACCESSKEY} ${config.S3_SECRETKEY}` ); await this.docker.exec( containers.minio, `mc mb local/${config.S3_BUCKET}` ); logger.log('ok', ` Bucket '${config.S3_BUCKET}' created ✓`); } else { logger.log('error', ' Failed to create container'); } break; } logger.log('info', ` Container: ${containers.minio}`); logger.log('info', ` Port: ${config.S3_PORT}`); logger.log('info', ` Bucket: ${config.S3_BUCKET}`); logger.log('info', ` API: http://${config.S3_HOST}:${config.S3_PORT}`); logger.log('info', ` Console: http://${config.S3_HOST}:${config.S3_CONSOLE_PORT} (login: ${config.S3_ACCESSKEY}/***)`); } /** * Start Elasticsearch service */ public async startElasticsearch(): Promise { logger.log('note', '📦 Elasticsearch:'); const config = this.config.getConfig(); const containers = this.config.getContainerNames(); const directories = this.config.getDataDirectories(); // Ensure data directory exists await plugins.smartfs.directory(directories.elasticsearch).recursive().create(); const status = await this.docker.getStatus(containers.elasticsearch); switch (status) { case 'running': logger.log('ok', ' Already running ✓'); break; case 'stopped': // Check if port mapping matches config const esPortMappings = await this.docker.getPortMappings(containers.elasticsearch); if (esPortMappings && esPortMappings['9200'] !== config.ELASTICSEARCH_PORT) { logger.log('note', ' Port configuration changed, recreating container...'); await this.docker.remove(containers.elasticsearch, true); // Fall through to create new container const success = await this.docker.run({ name: containers.elasticsearch, image: 'elasticsearch:8.11.0', ports: { [`0.0.0.0:${config.ELASTICSEARCH_PORT}`]: '9200' }, volumes: { [directories.elasticsearch]: '/usr/share/elasticsearch/data' }, environment: { 'discovery.type': 'single-node', 'xpack.security.enabled': 'true', 'ELASTIC_PASSWORD': config.ELASTICSEARCH_PASS, 'ES_JAVA_OPTS': '-Xms512m -Xmx512m' }, restart: 'unless-stopped' }); if (success) { logger.log('ok', ' Recreated with new port ✓'); } else { logger.log('error', ' Failed to recreate container'); } } else { // Ports match, just start the container if (await this.docker.start(containers.elasticsearch)) { logger.log('ok', ' Started ✓'); } else { logger.log('error', ' Failed to start'); } } break; case 'not_exists': logger.log('note', ' Creating container...'); const success = await this.docker.run({ name: containers.elasticsearch, image: 'elasticsearch:8.11.0', ports: { [`0.0.0.0:${config.ELASTICSEARCH_PORT}`]: '9200' }, volumes: { [directories.elasticsearch]: '/usr/share/elasticsearch/data' }, environment: { 'discovery.type': 'single-node', 'xpack.security.enabled': 'true', 'ELASTIC_PASSWORD': config.ELASTICSEARCH_PASS, 'ES_JAVA_OPTS': '-Xms512m -Xmx512m' }, restart: 'unless-stopped' }); if (success) { logger.log('ok', ' Created and started ✓'); } else { logger.log('error', ' Failed to create container'); } break; } logger.log('info', ` Container: ${containers.elasticsearch}`); logger.log('info', ` Port: ${config.ELASTICSEARCH_PORT}`); logger.log('info', ` Connection: ${config.ELASTICSEARCH_URL}`); logger.log('info', ` Username: ${config.ELASTICSEARCH_USER}`); logger.log('info', ` Password: ${config.ELASTICSEARCH_PASS}`); } /** * Stop MongoDB service */ public async stopMongoDB(): Promise { logger.log('note', '📦 MongoDB:'); const containers = this.config.getContainerNames(); const status = await this.docker.getStatus(containers.mongo); if (status === 'running') { if (await this.docker.stop(containers.mongo)) { logger.log('ok', ' Stopped ✓'); } else { logger.log('error', ' Failed to stop'); } } else { logger.log('note', ' Not running'); } } /** * Stop MinIO service */ public async stopMinIO(): Promise { logger.log('note', '📦 S3/MinIO:'); const containers = this.config.getContainerNames(); const status = await this.docker.getStatus(containers.minio); if (status === 'running') { if (await this.docker.stop(containers.minio)) { logger.log('ok', ' Stopped ✓'); } else { logger.log('error', ' Failed to stop'); } } else { logger.log('note', ' Not running'); } } /** * Stop Elasticsearch service */ public async stopElasticsearch(): Promise { logger.log('note', '📦 Elasticsearch:'); const containers = this.config.getContainerNames(); const status = await this.docker.getStatus(containers.elasticsearch); if (status === 'running') { if (await this.docker.stop(containers.elasticsearch)) { logger.log('ok', ' Stopped ✓'); } else { logger.log('error', ' Failed to stop'); } } else { logger.log('note', ' Not running'); } } /** * Show service status */ public async showStatus(): Promise { helpers.printHeader('Service Status'); const config = this.config.getConfig(); const containers = this.config.getContainerNames(); logger.log('info', `Project: ${config.PROJECT_NAME}`); console.log(); // MongoDB status const mongoStatus = await this.docker.getStatus(containers.mongo); switch (mongoStatus) { case 'running': logger.log('ok', '📦 MongoDB: 🟢 Running'); logger.log('info', ` ├─ Container: ${containers.mongo}`); logger.log('info', ` ├─ Port: ${config.MONGODB_PORT}`); logger.log('info', ` ├─ Connection: ${this.config.getMongoConnectionString()}`); // Show Compass connection string const networkIp = await helpers.getLocalNetworkIp(); const compassString = `mongodb://${config.MONGODB_USER}:${config.MONGODB_PASS}@${networkIp}:${config.MONGODB_PORT}/${config.MONGODB_NAME}?authSource=admin`; logger.log('ok', ` └─ Compass: ${compassString}`); break; case 'stopped': logger.log('note', '📦 MongoDB: 🟡 Stopped'); logger.log('info', ` ├─ Container: ${containers.mongo}`); logger.log('info', ` └─ Port: ${config.MONGODB_PORT}`); break; case 'not_exists': logger.log('info', '📦 MongoDB: ⚪ Not installed'); // Check port availability const mongoPort = parseInt(config.MONGODB_PORT); const mongoAvailable = await helpers.isPortAvailable(mongoPort); if (!mongoAvailable) { logger.log('error', ` └─ ⚠️ Port ${mongoPort} is in use by another process`); } else { logger.log('info', ` └─ Port ${mongoPort} is available`); } break; } // MinIO status const minioStatus = await this.docker.getStatus(containers.minio); switch (minioStatus) { case 'running': logger.log('ok', '📦 S3/MinIO: 🟢 Running'); logger.log('info', ` ├─ Container: ${containers.minio}`); logger.log('info', ` ├─ API: http://${config.S3_HOST}:${config.S3_PORT}`); logger.log('info', ` ├─ Console: http://${config.S3_HOST}:${config.S3_CONSOLE_PORT}`); logger.log('info', ` └─ Bucket: ${config.S3_BUCKET}`); break; case 'stopped': logger.log('note', '📦 S3/MinIO: 🟡 Stopped'); logger.log('info', ` ├─ Container: ${containers.minio}`); logger.log('info', ` ├─ API Port: ${config.S3_PORT}`); logger.log('info', ` └─ Console Port: ${config.S3_CONSOLE_PORT}`); break; case 'not_exists': logger.log('info', '📦 S3/MinIO: ⚪ Not installed'); // Check port availability const s3Port = parseInt(config.S3_PORT); const s3ConsolePort = parseInt(config.S3_CONSOLE_PORT); const s3Available = await helpers.isPortAvailable(s3Port); const consoleAvailable = await helpers.isPortAvailable(s3ConsolePort); if (!s3Available || !consoleAvailable) { if (!s3Available) { logger.log('error', ` ├─ ⚠️ API Port ${s3Port} is in use`); } else { logger.log('info', ` ├─ API Port ${s3Port} is available`); } if (!consoleAvailable) { logger.log('error', ` └─ ⚠️ Console Port ${s3ConsolePort} is in use`); } else { logger.log('info', ` └─ Console Port ${s3ConsolePort} is available`); } } else { logger.log('info', ` ├─ API Port ${s3Port} is available`); logger.log('info', ` └─ Console Port ${s3ConsolePort} is available`); } break; } // Elasticsearch status const esStatus = await this.docker.getStatus(containers.elasticsearch); switch (esStatus) { case 'running': logger.log('ok', '📦 Elasticsearch: 🟢 Running'); logger.log('info', ` ├─ Container: ${containers.elasticsearch}`); logger.log('info', ` ├─ Port: ${config.ELASTICSEARCH_PORT}`); logger.log('info', ` ├─ Connection: ${config.ELASTICSEARCH_URL}`); logger.log('info', ` └─ Credentials: ${config.ELASTICSEARCH_USER}/${config.ELASTICSEARCH_PASS}`); break; case 'stopped': logger.log('note', '📦 Elasticsearch: 🟡 Stopped'); logger.log('info', ` ├─ Container: ${containers.elasticsearch}`); logger.log('info', ` └─ Port: ${config.ELASTICSEARCH_PORT}`); break; case 'not_exists': logger.log('info', '📦 Elasticsearch: ⚪ Not installed'); // Check port availability const esPort = parseInt(config.ELASTICSEARCH_PORT); const esAvailable = await helpers.isPortAvailable(esPort); if (!esAvailable) { logger.log('error', ` └─ ⚠️ Port ${esPort} is in use by another process`); } else { logger.log('info', ` └─ Port ${esPort} is available`); } break; } } /** * Show configuration */ public async showConfig(): Promise { helpers.printHeader('Current Configuration'); const config = this.config.getConfig(); logger.log('info', `Project: ${config.PROJECT_NAME}`); console.log(); logger.log('note', 'MongoDB:'); logger.log('info', ` Host: ${config.MONGODB_HOST}:${config.MONGODB_PORT}`); logger.log('info', ` Database: ${config.MONGODB_NAME}`); logger.log('info', ` User: ${config.MONGODB_USER}`); logger.log('info', ' Password: ***'); logger.log('info', ` Container: ${this.config.getContainerNames().mongo}`); logger.log('info', ` Data: ${this.config.getDataDirectories().mongo}`); logger.log('info', ` Connection: ${this.config.getMongoConnectionString()}`); console.log(); logger.log('note', 'S3/MinIO:'); logger.log('info', ` Host: ${config.S3_HOST}`); logger.log('info', ` API Port: ${config.S3_PORT}`); logger.log('info', ` Console Port: ${config.S3_CONSOLE_PORT}`); logger.log('info', ` Access Key: ${config.S3_ACCESSKEY}`); logger.log('info', ' Secret Key: ***'); logger.log('info', ` Bucket: ${config.S3_BUCKET}`); logger.log('info', ` Use SSL: ${config.S3_USESSL}`); logger.log('info', ` Container: ${this.config.getContainerNames().minio}`); logger.log('info', ` Data: ${this.config.getDataDirectories().minio}`); logger.log('info', ` Endpoint: ${config.S3_ENDPOINT}`); logger.log('info', ` Console URL: http://${config.S3_HOST}:${config.S3_CONSOLE_PORT}`); console.log(); logger.log('note', 'Elasticsearch:'); logger.log('info', ` Host: ${config.ELASTICSEARCH_HOST}:${config.ELASTICSEARCH_PORT}`); logger.log('info', ` User: ${config.ELASTICSEARCH_USER}`); logger.log('info', ' Password: ***'); logger.log('info', ` Container: ${this.config.getContainerNames().elasticsearch}`); logger.log('info', ` Data: ${this.config.getDataDirectories().elasticsearch}`); logger.log('info', ` Connection: ${config.ELASTICSEARCH_URL}`); } /** * Show MongoDB Compass connection string */ public async showCompassConnection(): Promise { helpers.printHeader('MongoDB Compass Connection'); const config = this.config.getConfig(); const networkIp = await helpers.getLocalNetworkIp(); const connectionString = `mongodb://${config.MONGODB_USER}:${config.MONGODB_PASS}@${networkIp}:${config.MONGODB_PORT}/${config.MONGODB_NAME}?authSource=admin`; logger.log('info', 'MongoDB Compass is a GUI tool for MongoDB. To connect:'); console.log(); logger.log('info', '1. Download MongoDB Compass from:'); logger.log('info', ' https://www.mongodb.com/products/compass'); console.log(); logger.log('info', '2. Open Compass and paste this connection string:'); logger.log('ok', ` ${connectionString}`); console.log(); logger.log('note', 'Connection Details:'); logger.log('info', ` Network IP: ${networkIp}`); logger.log('info', ` Port: ${config.MONGODB_PORT}`); logger.log('info', ` Database: ${config.MONGODB_NAME}`); logger.log('info', ` Username: ${config.MONGODB_USER}`); logger.log('info', ` Auth Source: admin`); } /** * Show logs for a service */ public async showLogs(service: string, lines: number = 20): Promise { const containers = this.config.getContainerNames(); switch (service) { case 'mongo': case 'mongodb': if (await this.docker.isRunning(containers.mongo)) { helpers.printHeader(`MongoDB Logs (last ${lines} lines)`); const logs = await this.docker.logs(containers.mongo, lines); console.log(logs); } else { logger.log('note', 'MongoDB container is not running'); } break; case 'minio': case 's3': if (await this.docker.isRunning(containers.minio)) { helpers.printHeader(`S3/MinIO Logs (last ${lines} lines)`); const logs = await this.docker.logs(containers.minio, lines); console.log(logs); } else { logger.log('note', 'S3/MinIO container is not running'); } break; case 'elasticsearch': case 'es': if (await this.docker.isRunning(containers.elasticsearch)) { helpers.printHeader(`Elasticsearch Logs (last ${lines} lines)`); const logs = await this.docker.logs(containers.elasticsearch, lines); console.log(logs); } else { logger.log('note', 'Elasticsearch container is not running'); } break; case 'all': case '': await this.showLogs('mongo', lines); console.log(); await this.showLogs('minio', lines); console.log(); await this.showLogs('elasticsearch', lines); break; default: logger.log('note', 'Usage: gitzone services logs [mongo|s3|elasticsearch|all] [lines]'); break; } } /** * Remove containers */ public async removeContainers(): Promise { const containers = this.config.getContainerNames(); let removed = false; if (await this.docker.exists(containers.mongo)) { if (await this.docker.remove(containers.mongo, true)) { logger.log('ok', ' MongoDB container removed ✓'); removed = true; } } if (await this.docker.exists(containers.minio)) { if (await this.docker.remove(containers.minio, true)) { logger.log('ok', ' S3/MinIO container removed ✓'); removed = true; } } if (await this.docker.exists(containers.elasticsearch)) { if (await this.docker.remove(containers.elasticsearch, true)) { logger.log('ok', ' Elasticsearch container removed ✓'); removed = true; } } if (!removed) { logger.log('note', ' No containers to remove'); } } /** * Clean data directories */ public async cleanData(): Promise { const directories = this.config.getDataDirectories(); let cleaned = false; if (await plugins.smartfs.directory(directories.mongo).exists()) { await plugins.smartfs.directory(directories.mongo).recursive().delete(); logger.log('ok', ' MongoDB data removed ✓'); cleaned = true; } if (await plugins.smartfs.directory(directories.minio).exists()) { await plugins.smartfs.directory(directories.minio).recursive().delete(); logger.log('ok', ' S3/MinIO data removed ✓'); cleaned = true; } if (await plugins.smartfs.directory(directories.elasticsearch).exists()) { await plugins.smartfs.directory(directories.elasticsearch).recursive().delete(); logger.log('ok', ' Elasticsearch data removed ✓'); cleaned = true; } if (!cleaned) { logger.log('note', ' No data to clean'); } } /** * Configure which services are enabled */ public async configureServices(): Promise { logger.log('note', 'Select which services to enable for this project:'); console.log(); const currentServices = this.enabledServices || ['mongodb', 'minio', 'elasticsearch']; const smartinteract = new plugins.smartinteract.SmartInteract(); const response = await smartinteract.askQuestion({ name: 'services', type: 'checkbox', message: 'Which services do you want to enable?', choices: [ { name: 'MongoDB', value: 'mongodb' }, { name: 'MinIO (S3)', value: 'minio' }, { name: 'Elasticsearch', value: 'elasticsearch' } ], default: currentServices }); this.enabledServices = response.value || ['mongodb', 'minio', 'elasticsearch']; // Save to npmextra.json await this.saveServiceConfiguration(this.enabledServices); logger.log('ok', '✅ Service configuration updated'); } /** * Reconfigure services with new ports */ public async reconfigure(): Promise { helpers.printHeader('Reconfiguring Services'); const containers = this.config.getContainerNames(); // Stop existing containers logger.log('note', '🛑 Stopping existing containers...'); if (await this.docker.exists(containers.mongo)) { await this.docker.stop(containers.mongo); logger.log('ok', ' MongoDB stopped ✓'); } if (await this.docker.exists(containers.minio)) { await this.docker.stop(containers.minio); logger.log('ok', ' S3/MinIO stopped ✓'); } if (await this.docker.exists(containers.elasticsearch)) { await this.docker.stop(containers.elasticsearch); logger.log('ok', ' Elasticsearch stopped ✓'); } // Reconfigure ports await this.config.reconfigurePorts(); // Ask if user wants to restart services const smartinteract = new plugins.smartinteract.SmartInteract(); const response = await smartinteract.askQuestion({ name: 'restart', type: 'confirm', message: 'Do you want to start services with new ports?', default: true }); if (response.value) { console.log(); await this.startAll(); } } }