feat(cli): add machine-readable CLI help, recommendation, and configuration flows
This commit is contained in:
+550
-183
@@ -1,12 +1,26 @@
|
||||
import * as plugins from './mod.plugins.js';
|
||||
import * as helpers from './helpers.js';
|
||||
import { ServiceManager } from './classes.servicemanager.js';
|
||||
import { GlobalRegistry } from './classes.globalregistry.js';
|
||||
import { logger } from '../gitzone.logging.js';
|
||||
import * as plugins from "./mod.plugins.js";
|
||||
import * as helpers from "./helpers.js";
|
||||
import { ServiceManager } from "./classes.servicemanager.js";
|
||||
import { GlobalRegistry } from "./classes.globalregistry.js";
|
||||
import { logger } from "../gitzone.logging.js";
|
||||
import type { ICliMode } from "../helpers.climode.js";
|
||||
import { getCliMode, printJson } from "../helpers.climode.js";
|
||||
import {
|
||||
getCliConfigValueFromData,
|
||||
readSmartconfigFile,
|
||||
setCliConfigValueInData,
|
||||
writeSmartconfigFile,
|
||||
} from "../helpers.smartconfig.js";
|
||||
|
||||
export const run = async (argvArg: any) => {
|
||||
const mode = await getCliMode(argvArg);
|
||||
const isGlobal = argvArg.g || argvArg.global;
|
||||
const command = argvArg._[1] || 'help';
|
||||
const command = argvArg._[1] || "help";
|
||||
|
||||
if (mode.help || command === "help") {
|
||||
showHelp(mode);
|
||||
return;
|
||||
}
|
||||
|
||||
// Handle global commands first
|
||||
if (isGlobal) {
|
||||
@@ -14,264 +28,597 @@ export const run = async (argvArg: any) => {
|
||||
return;
|
||||
}
|
||||
|
||||
// Local project commands
|
||||
const serviceManager = new ServiceManager();
|
||||
await serviceManager.init();
|
||||
|
||||
const service = argvArg._[2] || 'all';
|
||||
const service = argvArg._[2] || "all";
|
||||
|
||||
switch (command) {
|
||||
case 'start':
|
||||
await handleStart(serviceManager, service);
|
||||
break;
|
||||
|
||||
case 'stop':
|
||||
await handleStop(serviceManager, service);
|
||||
break;
|
||||
|
||||
case 'restart':
|
||||
await handleRestart(serviceManager, service);
|
||||
break;
|
||||
|
||||
case 'status':
|
||||
await serviceManager.showStatus();
|
||||
break;
|
||||
|
||||
case 'config':
|
||||
if (service === 'services' || argvArg._[2] === 'services') {
|
||||
case "config":
|
||||
if (service === "services" || argvArg._[2] === "services") {
|
||||
const serviceManager = new ServiceManager();
|
||||
await serviceManager.init();
|
||||
await handleConfigureServices(serviceManager);
|
||||
} else {
|
||||
await serviceManager.showConfig();
|
||||
await handleShowConfig(mode);
|
||||
}
|
||||
break;
|
||||
|
||||
case 'compass':
|
||||
await serviceManager.showCompassConnection();
|
||||
case "set":
|
||||
await handleSetServices(argvArg._[2], mode);
|
||||
break;
|
||||
|
||||
case 'logs':
|
||||
const lines = parseInt(argvArg._[3]) || 20;
|
||||
await serviceManager.showLogs(service, lines);
|
||||
|
||||
case "enable":
|
||||
await handleEnableServices(argvArg._.slice(2), mode);
|
||||
break;
|
||||
|
||||
case 'remove':
|
||||
await handleRemove(serviceManager);
|
||||
|
||||
case "disable":
|
||||
await handleDisableServices(argvArg._.slice(2), mode);
|
||||
break;
|
||||
|
||||
case 'clean':
|
||||
await handleClean(serviceManager);
|
||||
|
||||
case "start":
|
||||
case "stop":
|
||||
case "restart":
|
||||
case "status":
|
||||
case "compass":
|
||||
case "logs":
|
||||
case "remove":
|
||||
case "clean":
|
||||
case "reconfigure": {
|
||||
const serviceManager = new ServiceManager();
|
||||
await serviceManager.init();
|
||||
|
||||
switch (command) {
|
||||
case "start":
|
||||
await handleStart(serviceManager, service);
|
||||
break;
|
||||
|
||||
case "stop":
|
||||
await handleStop(serviceManager, service);
|
||||
break;
|
||||
|
||||
case "restart":
|
||||
await handleRestart(serviceManager, service);
|
||||
break;
|
||||
|
||||
case "status":
|
||||
await serviceManager.showStatus();
|
||||
break;
|
||||
|
||||
case "compass":
|
||||
await serviceManager.showCompassConnection();
|
||||
break;
|
||||
|
||||
case "logs": {
|
||||
const lines = parseInt(argvArg._[3]) || 20;
|
||||
await serviceManager.showLogs(service, lines);
|
||||
break;
|
||||
}
|
||||
|
||||
case "remove":
|
||||
await handleRemove(serviceManager);
|
||||
break;
|
||||
|
||||
case "clean":
|
||||
await handleClean(serviceManager);
|
||||
break;
|
||||
|
||||
case "reconfigure":
|
||||
await serviceManager.reconfigure();
|
||||
break;
|
||||
}
|
||||
break;
|
||||
|
||||
case 'reconfigure':
|
||||
await serviceManager.reconfigure();
|
||||
break;
|
||||
|
||||
case 'help':
|
||||
}
|
||||
default:
|
||||
showHelp();
|
||||
showHelp(mode);
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
const allowedServices = ["mongodb", "minio", "elasticsearch"];
|
||||
|
||||
const normalizeServiceName = (service: string): string => {
|
||||
switch (service) {
|
||||
case "mongo":
|
||||
case "mongodb":
|
||||
return "mongodb";
|
||||
case "minio":
|
||||
case "s3":
|
||||
return "minio";
|
||||
case "elastic":
|
||||
case "elasticsearch":
|
||||
case "es":
|
||||
return "elasticsearch";
|
||||
default:
|
||||
return service;
|
||||
}
|
||||
};
|
||||
|
||||
async function readServicesConfig(): Promise<{
|
||||
enabledServices: string[];
|
||||
environment: Record<string, any> | null;
|
||||
}> {
|
||||
const smartconfigData = await readSmartconfigFile();
|
||||
const enabledServices = getCliConfigValueFromData(
|
||||
smartconfigData,
|
||||
"services",
|
||||
);
|
||||
let environment: Record<string, any> | null = null;
|
||||
const envPath = plugins.path.join(process.cwd(), ".nogit", "env.json");
|
||||
if (await plugins.smartfs.file(envPath).exists()) {
|
||||
const envContent = (await plugins.smartfs
|
||||
.file(envPath)
|
||||
.encoding("utf8")
|
||||
.read()) as string;
|
||||
environment = JSON.parse(envContent);
|
||||
}
|
||||
|
||||
return {
|
||||
enabledServices: Array.isArray(enabledServices) ? enabledServices : [],
|
||||
environment,
|
||||
};
|
||||
}
|
||||
|
||||
async function updateEnabledServices(services: string[]): Promise<void> {
|
||||
const smartconfigData = await readSmartconfigFile();
|
||||
setCliConfigValueInData(smartconfigData, "services", services);
|
||||
await writeSmartconfigFile(smartconfigData);
|
||||
}
|
||||
|
||||
async function handleShowConfig(mode: ICliMode) {
|
||||
const configData = await readServicesConfig();
|
||||
|
||||
if (mode.json) {
|
||||
printJson(configData);
|
||||
return;
|
||||
}
|
||||
|
||||
helpers.printHeader("Current Services Configuration");
|
||||
logger.log(
|
||||
"info",
|
||||
`Enabled Services: ${configData.enabledServices.length > 0 ? configData.enabledServices.join(", ") : "none configured"}`,
|
||||
);
|
||||
console.log();
|
||||
|
||||
if (!configData.environment) {
|
||||
logger.log(
|
||||
"note",
|
||||
"No .nogit/env.json found yet. Start a service once to create runtime defaults.",
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
const env = configData.environment;
|
||||
logger.log("note", "MongoDB:");
|
||||
logger.log("info", ` Host: ${env.MONGODB_HOST}:${env.MONGODB_PORT}`);
|
||||
logger.log("info", ` Database: ${env.MONGODB_NAME}`);
|
||||
logger.log("info", ` User: ${env.MONGODB_USER}`);
|
||||
logger.log("info", ` Container: ${env.PROJECT_NAME}-mongodb`);
|
||||
logger.log(
|
||||
"info",
|
||||
` Data: ${plugins.path.join(process.cwd(), ".nogit", "mongodata")}`,
|
||||
);
|
||||
logger.log("info", ` Connection: ${env.MONGODB_URL}`);
|
||||
console.log();
|
||||
|
||||
logger.log("note", "S3/MinIO:");
|
||||
logger.log("info", ` Host: ${env.S3_HOST}`);
|
||||
logger.log("info", ` API Port: ${env.S3_PORT}`);
|
||||
logger.log("info", ` Console Port: ${env.S3_CONSOLE_PORT}`);
|
||||
logger.log("info", ` Bucket: ${env.S3_BUCKET}`);
|
||||
logger.log("info", ` Container: ${env.PROJECT_NAME}-minio`);
|
||||
logger.log(
|
||||
"info",
|
||||
` Data: ${plugins.path.join(process.cwd(), ".nogit", "miniodata")}`,
|
||||
);
|
||||
logger.log("info", ` Endpoint: ${env.S3_ENDPOINT}`);
|
||||
console.log();
|
||||
|
||||
logger.log("note", "Elasticsearch:");
|
||||
logger.log(
|
||||
"info",
|
||||
` Host: ${env.ELASTICSEARCH_HOST}:${env.ELASTICSEARCH_PORT}`,
|
||||
);
|
||||
logger.log("info", ` User: ${env.ELASTICSEARCH_USER}`);
|
||||
logger.log("info", ` Container: ${env.PROJECT_NAME}-elasticsearch`);
|
||||
logger.log(
|
||||
"info",
|
||||
` Data: ${plugins.path.join(process.cwd(), ".nogit", "esdata")}`,
|
||||
);
|
||||
logger.log("info", ` Connection: ${env.ELASTICSEARCH_URL}`);
|
||||
}
|
||||
|
||||
async function handleSetServices(rawValue: string | undefined, mode: ICliMode) {
|
||||
if (!rawValue) {
|
||||
throw new Error("Specify a comma-separated list of services");
|
||||
}
|
||||
|
||||
const requestedServices = rawValue
|
||||
.split(",")
|
||||
.map((service) => normalizeServiceName(service.trim()))
|
||||
.filter(Boolean);
|
||||
validateRequestedServices(requestedServices);
|
||||
await updateEnabledServices(requestedServices);
|
||||
|
||||
if (mode.json) {
|
||||
printJson({ ok: true, action: "set", enabledServices: requestedServices });
|
||||
return;
|
||||
}
|
||||
|
||||
logger.log("ok", `Enabled services set to: ${requestedServices.join(", ")}`);
|
||||
}
|
||||
|
||||
async function handleEnableServices(
|
||||
requestedServices: string[],
|
||||
mode: ICliMode,
|
||||
) {
|
||||
const normalizedServices = requestedServices.map((service) =>
|
||||
normalizeServiceName(service),
|
||||
);
|
||||
validateRequestedServices(normalizedServices);
|
||||
|
||||
const configData = await readServicesConfig();
|
||||
const nextServices = Array.from(
|
||||
new Set([...configData.enabledServices, ...normalizedServices]),
|
||||
);
|
||||
await updateEnabledServices(nextServices);
|
||||
|
||||
if (mode.json) {
|
||||
printJson({ ok: true, action: "enable", enabledServices: nextServices });
|
||||
return;
|
||||
}
|
||||
|
||||
logger.log("ok", `Enabled services: ${nextServices.join(", ")}`);
|
||||
}
|
||||
|
||||
async function handleDisableServices(
|
||||
requestedServices: string[],
|
||||
mode: ICliMode,
|
||||
) {
|
||||
const normalizedServices = requestedServices.map((service) =>
|
||||
normalizeServiceName(service),
|
||||
);
|
||||
validateRequestedServices(normalizedServices);
|
||||
|
||||
const configData = await readServicesConfig();
|
||||
const nextServices = configData.enabledServices.filter(
|
||||
(service) => !normalizedServices.includes(service),
|
||||
);
|
||||
await updateEnabledServices(nextServices);
|
||||
|
||||
if (mode.json) {
|
||||
printJson({ ok: true, action: "disable", enabledServices: nextServices });
|
||||
return;
|
||||
}
|
||||
|
||||
logger.log("ok", `Enabled services: ${nextServices.join(", ")}`);
|
||||
}
|
||||
|
||||
function validateRequestedServices(services: string[]): void {
|
||||
if (services.length === 0) {
|
||||
throw new Error("Specify at least one service");
|
||||
}
|
||||
|
||||
const invalidServices = services.filter(
|
||||
(service) => !allowedServices.includes(service),
|
||||
);
|
||||
if (invalidServices.length > 0) {
|
||||
throw new Error(`Unknown service(s): ${invalidServices.join(", ")}`);
|
||||
}
|
||||
}
|
||||
|
||||
async function handleStart(serviceManager: ServiceManager, service: string) {
|
||||
helpers.printHeader('Starting Services');
|
||||
helpers.printHeader("Starting Services");
|
||||
|
||||
switch (service) {
|
||||
case 'mongo':
|
||||
case 'mongodb':
|
||||
case "mongo":
|
||||
case "mongodb":
|
||||
await serviceManager.startMongoDB();
|
||||
break;
|
||||
|
||||
case 'minio':
|
||||
case 's3':
|
||||
case "minio":
|
||||
case "s3":
|
||||
await serviceManager.startMinIO();
|
||||
break;
|
||||
|
||||
case 'elasticsearch':
|
||||
case 'es':
|
||||
case "elasticsearch":
|
||||
case "es":
|
||||
await serviceManager.startElasticsearch();
|
||||
break;
|
||||
|
||||
case 'all':
|
||||
case '':
|
||||
case "all":
|
||||
case "":
|
||||
await serviceManager.startAll();
|
||||
break;
|
||||
|
||||
default:
|
||||
logger.log('error', `Unknown service: ${service}`);
|
||||
logger.log('note', 'Use: mongo, s3, elasticsearch, or all');
|
||||
logger.log("error", `Unknown service: ${service}`);
|
||||
logger.log("note", "Use: mongo, s3, elasticsearch, or all");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
async function handleStop(serviceManager: ServiceManager, service: string) {
|
||||
helpers.printHeader('Stopping Services');
|
||||
helpers.printHeader("Stopping Services");
|
||||
|
||||
switch (service) {
|
||||
case 'mongo':
|
||||
case 'mongodb':
|
||||
case "mongo":
|
||||
case "mongodb":
|
||||
await serviceManager.stopMongoDB();
|
||||
break;
|
||||
|
||||
case 'minio':
|
||||
case 's3':
|
||||
case "minio":
|
||||
case "s3":
|
||||
await serviceManager.stopMinIO();
|
||||
break;
|
||||
|
||||
case 'elasticsearch':
|
||||
case 'es':
|
||||
case "elasticsearch":
|
||||
case "es":
|
||||
await serviceManager.stopElasticsearch();
|
||||
break;
|
||||
|
||||
case 'all':
|
||||
case '':
|
||||
case "all":
|
||||
case "":
|
||||
await serviceManager.stopAll();
|
||||
break;
|
||||
|
||||
default:
|
||||
logger.log('error', `Unknown service: ${service}`);
|
||||
logger.log('note', 'Use: mongo, s3, elasticsearch, or all');
|
||||
logger.log("error", `Unknown service: ${service}`);
|
||||
logger.log("note", "Use: mongo, s3, elasticsearch, or all");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
async function handleRestart(serviceManager: ServiceManager, service: string) {
|
||||
helpers.printHeader('Restarting Services');
|
||||
helpers.printHeader("Restarting Services");
|
||||
|
||||
switch (service) {
|
||||
case 'mongo':
|
||||
case 'mongodb':
|
||||
case "mongo":
|
||||
case "mongodb":
|
||||
await serviceManager.stopMongoDB();
|
||||
await plugins.smartdelay.delayFor(2000);
|
||||
await serviceManager.startMongoDB();
|
||||
break;
|
||||
|
||||
case 'minio':
|
||||
case 's3':
|
||||
case "minio":
|
||||
case "s3":
|
||||
await serviceManager.stopMinIO();
|
||||
await plugins.smartdelay.delayFor(2000);
|
||||
await serviceManager.startMinIO();
|
||||
break;
|
||||
|
||||
case 'elasticsearch':
|
||||
case 'es':
|
||||
case "elasticsearch":
|
||||
case "es":
|
||||
await serviceManager.stopElasticsearch();
|
||||
await plugins.smartdelay.delayFor(2000);
|
||||
await serviceManager.startElasticsearch();
|
||||
break;
|
||||
|
||||
case 'all':
|
||||
case '':
|
||||
case "all":
|
||||
case "":
|
||||
await serviceManager.stopAll();
|
||||
await plugins.smartdelay.delayFor(2000);
|
||||
await serviceManager.startAll();
|
||||
break;
|
||||
|
||||
default:
|
||||
logger.log('error', `Unknown service: ${service}`);
|
||||
logger.log("error", `Unknown service: ${service}`);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
async function handleRemove(serviceManager: ServiceManager) {
|
||||
helpers.printHeader('Removing Containers');
|
||||
logger.log('note', '⚠️ This will remove containers but preserve data');
|
||||
|
||||
const shouldContinue = await plugins.smartinteract.SmartInteract.getCliConfirmation('Continue?', false);
|
||||
|
||||
helpers.printHeader("Removing Containers");
|
||||
logger.log("note", "⚠️ This will remove containers but preserve data");
|
||||
|
||||
const shouldContinue =
|
||||
await plugins.smartinteract.SmartInteract.getCliConfirmation(
|
||||
"Continue?",
|
||||
false,
|
||||
);
|
||||
|
||||
if (shouldContinue) {
|
||||
await serviceManager.removeContainers();
|
||||
} else {
|
||||
logger.log('note', 'Cancelled');
|
||||
logger.log("note", "Cancelled");
|
||||
}
|
||||
}
|
||||
|
||||
async function handleClean(serviceManager: ServiceManager) {
|
||||
helpers.printHeader('Clean All');
|
||||
logger.log('error', '⚠️ WARNING: This will remove all containers and data!');
|
||||
logger.log('error', 'This action cannot be undone!');
|
||||
helpers.printHeader("Clean All");
|
||||
logger.log("error", "⚠️ WARNING: This will remove all containers and data!");
|
||||
logger.log("error", "This action cannot be undone!");
|
||||
|
||||
const smartinteraction = new plugins.smartinteract.SmartInteract();
|
||||
const confirmAnswer = await smartinteraction.askQuestion({
|
||||
name: 'confirm',
|
||||
type: 'input',
|
||||
name: "confirm",
|
||||
type: "input",
|
||||
message: 'Type "yes" to confirm:',
|
||||
default: 'no'
|
||||
default: "no",
|
||||
});
|
||||
|
||||
if (confirmAnswer.value === 'yes') {
|
||||
if (confirmAnswer.value === "yes") {
|
||||
await serviceManager.removeContainers();
|
||||
console.log();
|
||||
await serviceManager.cleanData();
|
||||
logger.log('ok', 'All cleaned ✓');
|
||||
logger.log("ok", "All cleaned ✓");
|
||||
} else {
|
||||
logger.log('note', 'Cancelled');
|
||||
logger.log("note", "Cancelled");
|
||||
}
|
||||
}
|
||||
|
||||
async function handleConfigureServices(serviceManager: ServiceManager) {
|
||||
helpers.printHeader('Configure Services');
|
||||
helpers.printHeader("Configure Services");
|
||||
await serviceManager.configureServices();
|
||||
}
|
||||
|
||||
function showHelp() {
|
||||
helpers.printHeader('GitZone Services Manager');
|
||||
export function showHelp(mode?: ICliMode) {
|
||||
if (mode?.json) {
|
||||
printJson({
|
||||
command: "services",
|
||||
usage: "gitzone services <command> [options]",
|
||||
commands: [
|
||||
{
|
||||
name: "config",
|
||||
description:
|
||||
"Show configured services and any existing runtime env.json data",
|
||||
},
|
||||
{
|
||||
name: "set <csv>",
|
||||
description: "Set the enabled service list without prompts",
|
||||
},
|
||||
{
|
||||
name: "enable <service...>",
|
||||
description: "Enable one or more services without prompts",
|
||||
},
|
||||
{
|
||||
name: "disable <service...>",
|
||||
description: "Disable one or more services without prompts",
|
||||
},
|
||||
{ name: "start [service]", description: "Start services" },
|
||||
{ name: "stop [service]", description: "Stop services" },
|
||||
{ name: "status", description: "Show service status" },
|
||||
],
|
||||
examples: [
|
||||
"gitzone services config --json",
|
||||
"gitzone services set mongodb,minio",
|
||||
"gitzone services enable elasticsearch",
|
||||
],
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
logger.log('ok', 'Usage: gitzone services [command] [options]');
|
||||
helpers.printHeader("GitZone Services Manager");
|
||||
|
||||
logger.log("ok", "Usage: gitzone services [command] [options]");
|
||||
console.log();
|
||||
|
||||
logger.log('note', 'Commands:');
|
||||
logger.log('info', ' start [service] Start services (mongo|s3|elasticsearch|all)');
|
||||
logger.log('info', ' stop [service] Stop services (mongo|s3|elasticsearch|all)');
|
||||
logger.log('info', ' restart [service] Restart services (mongo|s3|elasticsearch|all)');
|
||||
logger.log('info', ' status Show service status');
|
||||
logger.log('info', ' config Show current configuration');
|
||||
logger.log('info', ' config services Configure which services are enabled');
|
||||
logger.log('info', ' compass Show MongoDB Compass connection string');
|
||||
logger.log('info', ' logs [service] Show logs (mongo|s3|elasticsearch|all) [lines]');
|
||||
logger.log('info', ' reconfigure Reassign ports and restart services');
|
||||
logger.log('info', ' remove Remove all containers');
|
||||
logger.log('info', ' clean Remove all containers and data ⚠️');
|
||||
logger.log('info', ' help Show this help message');
|
||||
logger.log("note", "Commands:");
|
||||
logger.log(
|
||||
"info",
|
||||
" start [service] Start services (mongo|s3|elasticsearch|all)",
|
||||
);
|
||||
logger.log(
|
||||
"info",
|
||||
" stop [service] Stop services (mongo|s3|elasticsearch|all)",
|
||||
);
|
||||
logger.log(
|
||||
"info",
|
||||
" restart [service] Restart services (mongo|s3|elasticsearch|all)",
|
||||
);
|
||||
logger.log("info", " status Show service status");
|
||||
logger.log("info", " config Show current configuration");
|
||||
logger.log(
|
||||
"info",
|
||||
" config services Configure which services are enabled",
|
||||
);
|
||||
logger.log(
|
||||
"info",
|
||||
" set <csv> Set enabled services without prompts",
|
||||
);
|
||||
logger.log("info", " enable <svc...> Enable one or more services");
|
||||
logger.log("info", " disable <svc...> Disable one or more services");
|
||||
logger.log(
|
||||
"info",
|
||||
" compass Show MongoDB Compass connection string",
|
||||
);
|
||||
logger.log(
|
||||
"info",
|
||||
" logs [service] Show logs (mongo|s3|elasticsearch|all) [lines]",
|
||||
);
|
||||
logger.log("info", " reconfigure Reassign ports and restart services");
|
||||
logger.log("info", " remove Remove all containers");
|
||||
logger.log("info", " clean Remove all containers and data ⚠️");
|
||||
logger.log("info", " help Show this help message");
|
||||
console.log();
|
||||
|
||||
logger.log('note', 'Available Services:');
|
||||
logger.log('info', ' • MongoDB (mongo) - Document database');
|
||||
logger.log('info', ' • MinIO (s3) - S3-compatible object storage');
|
||||
logger.log('info', ' • Elasticsearch (elasticsearch) - Search and analytics engine');
|
||||
logger.log("note", "Available Services:");
|
||||
logger.log("info", " • MongoDB (mongo) - Document database");
|
||||
logger.log("info", " • MinIO (s3) - S3-compatible object storage");
|
||||
logger.log(
|
||||
"info",
|
||||
" • Elasticsearch (elasticsearch) - Search and analytics engine",
|
||||
);
|
||||
console.log();
|
||||
|
||||
logger.log('note', 'Features:');
|
||||
logger.log('info', ' • Auto-creates .nogit/env.json with smart defaults');
|
||||
logger.log('info', ' • Random ports (20000-30000) for MongoDB/MinIO to avoid conflicts');
|
||||
logger.log('info', ' • Elasticsearch uses standard port 9200');
|
||||
logger.log('info', ' • Project-specific containers for multi-project support');
|
||||
logger.log('info', ' • Preserves custom configuration values');
|
||||
logger.log('info', ' • MongoDB Compass connection support');
|
||||
logger.log("note", "Features:");
|
||||
logger.log("info", " • Auto-creates .nogit/env.json with smart defaults");
|
||||
logger.log(
|
||||
"info",
|
||||
" • Random ports (20000-30000) for MongoDB/MinIO to avoid conflicts",
|
||||
);
|
||||
logger.log("info", " • Elasticsearch uses standard port 9200");
|
||||
logger.log(
|
||||
"info",
|
||||
" • Project-specific containers for multi-project support",
|
||||
);
|
||||
logger.log("info", " • Preserves custom configuration values");
|
||||
logger.log("info", " • MongoDB Compass connection support");
|
||||
console.log();
|
||||
|
||||
logger.log('note', 'Examples:');
|
||||
logger.log('info', ' gitzone services start # Start all services');
|
||||
logger.log('info', ' gitzone services start mongo # Start only MongoDB');
|
||||
logger.log('info', ' gitzone services start elasticsearch # Start only Elasticsearch');
|
||||
logger.log('info', ' gitzone services stop # Stop all services');
|
||||
logger.log('info', ' gitzone services status # Check service status');
|
||||
logger.log('info', ' gitzone services config # Show configuration');
|
||||
logger.log('info', ' gitzone services compass # Get MongoDB Compass connection');
|
||||
logger.log('info', ' gitzone services logs elasticsearch # Show Elasticsearch logs');
|
||||
logger.log("note", "Examples:");
|
||||
logger.log(
|
||||
"info",
|
||||
" gitzone services start # Start all services",
|
||||
);
|
||||
logger.log(
|
||||
"info",
|
||||
" gitzone services start mongo # Start only MongoDB",
|
||||
);
|
||||
logger.log(
|
||||
"info",
|
||||
" gitzone services start elasticsearch # Start only Elasticsearch",
|
||||
);
|
||||
logger.log(
|
||||
"info",
|
||||
" gitzone services stop # Stop all services",
|
||||
);
|
||||
logger.log(
|
||||
"info",
|
||||
" gitzone services status # Check service status",
|
||||
);
|
||||
logger.log(
|
||||
"info",
|
||||
" gitzone services config # Show configuration",
|
||||
);
|
||||
logger.log(
|
||||
"info",
|
||||
" gitzone services config --json # Show configuration as JSON",
|
||||
);
|
||||
logger.log(
|
||||
"info",
|
||||
" gitzone services set mongodb,minio # Configure services without prompts",
|
||||
);
|
||||
logger.log(
|
||||
"info",
|
||||
" gitzone services compass # Get MongoDB Compass connection",
|
||||
);
|
||||
logger.log(
|
||||
"info",
|
||||
" gitzone services logs elasticsearch # Show Elasticsearch logs",
|
||||
);
|
||||
console.log();
|
||||
|
||||
logger.log('note', 'Global Commands (-g/--global):');
|
||||
logger.log('info', ' list -g List all registered projects');
|
||||
logger.log('info', ' status -g Show status across all projects');
|
||||
logger.log('info', ' stop -g Stop all containers across all projects');
|
||||
logger.log('info', ' cleanup -g Remove stale registry entries');
|
||||
logger.log("note", "Global Commands (-g/--global):");
|
||||
logger.log("info", " list -g List all registered projects");
|
||||
logger.log("info", " status -g Show status across all projects");
|
||||
logger.log(
|
||||
"info",
|
||||
" stop -g Stop all containers across all projects",
|
||||
);
|
||||
logger.log("info", " cleanup -g Remove stale registry entries");
|
||||
console.log();
|
||||
|
||||
logger.log('note', 'Global Examples:');
|
||||
logger.log('info', ' gitzone services list -g # List all registered projects');
|
||||
logger.log('info', ' gitzone services status -g # Show global container status');
|
||||
logger.log('info', ' gitzone services stop -g # Stop all (prompts for confirmation)');
|
||||
logger.log("note", "Global Examples:");
|
||||
logger.log(
|
||||
"info",
|
||||
" gitzone services list -g # List all registered projects",
|
||||
);
|
||||
logger.log(
|
||||
"info",
|
||||
" gitzone services status -g # Show global container status",
|
||||
);
|
||||
logger.log(
|
||||
"info",
|
||||
" gitzone services stop -g # Stop all (prompts for confirmation)",
|
||||
);
|
||||
}
|
||||
|
||||
// ==================== Global Command Handlers ====================
|
||||
@@ -280,23 +627,23 @@ async function handleGlobalCommand(command: string) {
|
||||
const globalRegistry = GlobalRegistry.getInstance();
|
||||
|
||||
switch (command) {
|
||||
case 'list':
|
||||
case "list":
|
||||
await handleGlobalList(globalRegistry);
|
||||
break;
|
||||
|
||||
case 'status':
|
||||
case "status":
|
||||
await handleGlobalStatus(globalRegistry);
|
||||
break;
|
||||
|
||||
case 'stop':
|
||||
case "stop":
|
||||
await handleGlobalStop(globalRegistry);
|
||||
break;
|
||||
|
||||
case 'cleanup':
|
||||
case "cleanup":
|
||||
await handleGlobalCleanup(globalRegistry);
|
||||
break;
|
||||
|
||||
case 'help':
|
||||
case "help":
|
||||
default:
|
||||
showHelp();
|
||||
break;
|
||||
@@ -304,13 +651,13 @@ async function handleGlobalCommand(command: string) {
|
||||
}
|
||||
|
||||
async function handleGlobalList(globalRegistry: GlobalRegistry) {
|
||||
helpers.printHeader('Registered Projects (Global)');
|
||||
helpers.printHeader("Registered Projects (Global)");
|
||||
|
||||
const projects = await globalRegistry.getAllProjects();
|
||||
const projectPaths = Object.keys(projects);
|
||||
|
||||
if (projectPaths.length === 0) {
|
||||
logger.log('note', 'No projects registered');
|
||||
logger.log("note", "No projects registered");
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -319,20 +666,20 @@ async function handleGlobalList(globalRegistry: GlobalRegistry) {
|
||||
const lastActive = new Date(project.lastActive).toLocaleString();
|
||||
|
||||
console.log();
|
||||
logger.log('ok', `📁 ${project.projectName}`);
|
||||
logger.log('info', ` Path: ${project.projectPath}`);
|
||||
logger.log('info', ` Services: ${project.enabledServices.join(', ')}`);
|
||||
logger.log('info', ` Last Active: ${lastActive}`);
|
||||
logger.log("ok", `📁 ${project.projectName}`);
|
||||
logger.log("info", ` Path: ${project.projectPath}`);
|
||||
logger.log("info", ` Services: ${project.enabledServices.join(", ")}`);
|
||||
logger.log("info", ` Last Active: ${lastActive}`);
|
||||
}
|
||||
}
|
||||
|
||||
async function handleGlobalStatus(globalRegistry: GlobalRegistry) {
|
||||
helpers.printHeader('Global Service Status');
|
||||
helpers.printHeader("Global Service Status");
|
||||
|
||||
const statuses = await globalRegistry.getGlobalStatus();
|
||||
|
||||
if (statuses.length === 0) {
|
||||
logger.log('note', 'No projects registered');
|
||||
logger.log("note", "No projects registered");
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -341,28 +688,39 @@ async function handleGlobalStatus(globalRegistry: GlobalRegistry) {
|
||||
|
||||
for (const project of statuses) {
|
||||
console.log();
|
||||
logger.log('ok', `📁 ${project.projectName}`);
|
||||
logger.log('info', ` Path: ${project.projectPath}`);
|
||||
logger.log("ok", `📁 ${project.projectName}`);
|
||||
logger.log("info", ` Path: ${project.projectPath}`);
|
||||
|
||||
if (project.containers.length === 0) {
|
||||
logger.log('note', ' No containers configured');
|
||||
logger.log("note", " No containers configured");
|
||||
continue;
|
||||
}
|
||||
|
||||
for (const container of project.containers) {
|
||||
totalContainers++;
|
||||
const statusIcon = container.status === 'running' ? '🟢' : container.status === 'exited' ? '🟡' : '⚪';
|
||||
if (container.status === 'running') runningCount++;
|
||||
logger.log('info', ` ${statusIcon} ${container.name}: ${container.status}`);
|
||||
const statusIcon =
|
||||
container.status === "running"
|
||||
? "🟢"
|
||||
: container.status === "exited"
|
||||
? "🟡"
|
||||
: "⚪";
|
||||
if (container.status === "running") runningCount++;
|
||||
logger.log(
|
||||
"info",
|
||||
` ${statusIcon} ${container.name}: ${container.status}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
console.log();
|
||||
logger.log('note', `Summary: ${runningCount}/${totalContainers} containers running across ${statuses.length} project(s)`);
|
||||
logger.log(
|
||||
"note",
|
||||
`Summary: ${runningCount}/${totalContainers} containers running across ${statuses.length} project(s)`,
|
||||
);
|
||||
}
|
||||
|
||||
async function handleGlobalStop(globalRegistry: GlobalRegistry) {
|
||||
helpers.printHeader('Stop All Containers (Global)');
|
||||
helpers.printHeader("Stop All Containers (Global)");
|
||||
|
||||
const statuses = await globalRegistry.getGlobalStatus();
|
||||
|
||||
@@ -370,64 +728,73 @@ async function handleGlobalStop(globalRegistry: GlobalRegistry) {
|
||||
let runningCount = 0;
|
||||
for (const project of statuses) {
|
||||
for (const container of project.containers) {
|
||||
if (container.status === 'running') runningCount++;
|
||||
if (container.status === "running") runningCount++;
|
||||
}
|
||||
}
|
||||
|
||||
if (runningCount === 0) {
|
||||
logger.log('note', 'No running containers found');
|
||||
logger.log("note", "No running containers found");
|
||||
return;
|
||||
}
|
||||
|
||||
logger.log('note', `Found ${runningCount} running container(s) across ${statuses.length} project(s)`);
|
||||
logger.log(
|
||||
"note",
|
||||
`Found ${runningCount} running container(s) across ${statuses.length} project(s)`,
|
||||
);
|
||||
console.log();
|
||||
|
||||
// Show what will be stopped
|
||||
for (const project of statuses) {
|
||||
const runningContainers = project.containers.filter(c => c.status === 'running');
|
||||
const runningContainers = project.containers.filter(
|
||||
(c) => c.status === "running",
|
||||
);
|
||||
if (runningContainers.length > 0) {
|
||||
logger.log('info', `${project.projectName}:`);
|
||||
logger.log("info", `${project.projectName}:`);
|
||||
for (const container of runningContainers) {
|
||||
logger.log('info', ` • ${container.name}`);
|
||||
logger.log("info", ` • ${container.name}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
console.log();
|
||||
const shouldContinue = await plugins.smartinteract.SmartInteract.getCliConfirmation(
|
||||
'Stop all containers?',
|
||||
false
|
||||
);
|
||||
const shouldContinue =
|
||||
await plugins.smartinteract.SmartInteract.getCliConfirmation(
|
||||
"Stop all containers?",
|
||||
false,
|
||||
);
|
||||
|
||||
if (!shouldContinue) {
|
||||
logger.log('note', 'Cancelled');
|
||||
logger.log("note", "Cancelled");
|
||||
return;
|
||||
}
|
||||
|
||||
logger.log('note', 'Stopping all containers...');
|
||||
logger.log("note", "Stopping all containers...");
|
||||
const result = await globalRegistry.stopAll();
|
||||
|
||||
if (result.stopped.length > 0) {
|
||||
logger.log('ok', `Stopped: ${result.stopped.join(', ')}`);
|
||||
logger.log("ok", `Stopped: ${result.stopped.join(", ")}`);
|
||||
}
|
||||
if (result.failed.length > 0) {
|
||||
logger.log('error', `Failed to stop: ${result.failed.join(', ')}`);
|
||||
logger.log("error", `Failed to stop: ${result.failed.join(", ")}`);
|
||||
}
|
||||
}
|
||||
|
||||
async function handleGlobalCleanup(globalRegistry: GlobalRegistry) {
|
||||
helpers.printHeader('Cleanup Registry (Global)');
|
||||
helpers.printHeader("Cleanup Registry (Global)");
|
||||
|
||||
logger.log('note', 'Checking for stale registry entries...');
|
||||
logger.log("note", "Checking for stale registry entries...");
|
||||
const removed = await globalRegistry.cleanup();
|
||||
|
||||
if (removed.length === 0) {
|
||||
logger.log('ok', 'No stale entries found');
|
||||
logger.log("ok", "No stale entries found");
|
||||
return;
|
||||
}
|
||||
|
||||
logger.log('ok', `Removed ${removed.length} stale entr${removed.length === 1 ? 'y' : 'ies'}:`);
|
||||
logger.log(
|
||||
"ok",
|
||||
`Removed ${removed.length} stale entr${removed.length === 1 ? "y" : "ies"}:`,
|
||||
);
|
||||
for (const path of removed) {
|
||||
logger.log('info', ` • ${path}`);
|
||||
logger.log("info", ` • ${path}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user