UNPKG

@git.zone/cli

Version:

A comprehensive CLI tool for enhancing and managing local development workflows with gitzone utilities, focusing on project setup, version control, code formatting, and template management.

855 lines 79.2 kB
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 { GlobalRegistry } from './classes.globalregistry.js'; import { logger } from '../gitzone.logging.js'; export class ServiceManager { config; docker; enabledServices = null; globalRegistry; constructor() { this.config = new ServiceConfiguration(); this.docker = new DockerContainer(); this.globalRegistry = GlobalRegistry.getInstance(); } /** * Initialize the service manager */ async init() { // 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 */ async loadServiceConfiguration() { const npmextraConfig = new plugins.npmextra.Npmextra(process.cwd()); const gitzoneConfig = npmextraConfig.dataFor('@git.zone/cli', {}); // 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 */ async saveServiceConfiguration(services) { const npmextraPath = plugins.path.join(process.cwd(), 'npmextra.json'); let npmextraData = {}; // 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); } // Update @git.zone/cli.services if (!npmextraData['@git.zone/cli']) { npmextraData['@git.zone/cli'] = {}; } npmextraData['@git.zone/cli'].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 */ isServiceEnabled(service) { if (!this.enabledServices) { return true; // If no configuration, enable all } return this.enabledServices.includes(service); } /** * Register this project with the global registry */ async registerWithGlobalRegistry() { const config = this.config.getConfig(); const containers = this.config.getContainerNames(); await this.globalRegistry.registerProject({ projectPath: process.cwd(), projectName: config.PROJECT_NAME, containers: { mongo: containers.mongo, minio: containers.minio, elasticsearch: containers.elasticsearch, }, ports: { mongo: parseInt(config.MONGODB_PORT), s3: parseInt(config.S3_PORT), s3Console: parseInt(config.S3_CONSOLE_PORT), elasticsearch: parseInt(config.ELASTICSEARCH_PORT), }, enabledServices: this.enabledServices || ['mongodb', 'minio', 'elasticsearch'], }); } /** * Start all enabled services */ async startAll() { 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; } // Register with global registry await this.registerWithGlobalRegistry(); } /** * Stop all enabled services */ async stopAll() { 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 */ async startMongoDB() { 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 */ async startMinIO() { 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 */ async startElasticsearch() { 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 */ async stopMongoDB() { 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 */ async stopMinIO() { 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 */ async stopElasticsearch() { 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 */ async showStatus() { 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 */ async showConfig() { 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 */ async showCompassConnection() { 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 */ async showLogs(service, lines = 20) { 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 */ async removeContainers() { 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'); } // Check if all containers are gone, then unregister from global registry const mongoExists = await this.docker.exists(containers.mongo); const minioExists = await this.docker.exists(containers.minio); const esExists = await this.docker.exists(containers.elasticsearch); if (!mongoExists && !minioExists && !esExists) { await this.globalRegistry.unregisterProject(process.cwd()); } } /** * Clean data directories */ async cleanData() { 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 */ async configureServices() { 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 */ async reconfigure() { 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(); } } } //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiY2xhc3Nlcy5zZXJ2aWNlbWFuYWdlci5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uL3RzL21vZF9zZXJ2aWNlcy9jbGFzc2VzLnNlcnZpY2VtYW5hZ2VyLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBLE9BQU8sS0FBSyxPQUFPLE1BQU0sa0JBQWtCLENBQUM7QUFDNUMsT0FBTyxLQUFLLE9BQU8sTUFBTSxjQUFjLENBQUM7QUFDeEMsT0FBTyxFQUFFLG9CQUFvQixFQUFFLE1BQU0sbUNBQW1DLENBQUM7QUFDekUsT0FBTyxFQUFFLGVBQWUsRUFBRSxNQUFNLDhCQUE4QixDQUFDO0FBQy9ELE9BQU8sRUFBRSxjQUFjLEVBQUUsTUFBTSw2QkFBNkIsQ0FBQztBQUM3RCxPQUFPLEVBQUUsTUFBTSxFQUFFLE1BQU0sdUJBQXVCLENBQUM7QUFFL0MsTUFBTSxPQUFPLGNBQWM7SUFDakIsTUFBTSxDQUF1QjtJQUM3QixNQUFNLENBQWtCO0lBQ3hCLGVBQWUsR0FBb0IsSUFBSSxDQUFDO0lBQ3hDLGNBQWMsQ0FBaUI7SUFFdkM7UUFDRSxJQUFJLENBQUMsTUFBTSxHQUFHLElBQUksb0JBQW9CLEVBQUUsQ0FBQztRQUN6QyxJQUFJLENBQUMsTUFBTSxHQUFHLElBQUksZUFBZSxFQUFFLENBQUM7UUFDcEMsSUFBSSxDQUFDLGNBQWMsR0FBRyxjQUFjLENBQUMsV0FBVyxFQUFFLENBQUM7SUFDckQsQ0FBQztJQUVEOztPQUVHO0lBQ0ksS0FBSyxDQUFDLElBQUk7UUFDZiw0QkFBNEI7UUFDNUIsSUFBSSxDQUFDLENBQUMsTUFBTSxJQUFJLENBQUMsTUFBTSxDQUFDLFdBQVcsRUFBRSxDQUFDLEVBQUUsQ0FBQztZQUN2QyxNQUFNLENBQUMsR0FBRyxDQUFDLE9BQU8sRUFBRSw4REFBOEQsQ0FBQyxDQUFDO1lBQ3BGLE9BQU8sQ0FBQyxJQUFJLENBQUMsQ0FBQyxDQUFDLENBQUM7UUFDbEIsQ0FBQztRQUVELCtCQUErQjtRQUMvQixNQUFNLElB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