#!/usr/bin/env node import chalk from "chalk"; import { spawn, SpawnOptions } from "child_process"; import { Command } from "commander"; import { readFileSync } from "fs"; import { dirname, join } from "path"; import { fileURLToPath } from "url"; const __filename = fileURLToPath(import.meta.url); const __dirname = dirname(__filename); // Read version from package.json (go up one level if in dist directory) const rootDir = __dirname.endsWith("dist") ? dirname(__dirname) : __dirname; const packageJsonPath = join(rootDir, "package.json"); const packageJson = JSON.parse(readFileSync(packageJsonPath, "utf-8")); const version = packageJson.version; const program = new Command(); // generated by figlet.textSync('Mongoku', { horizontalLayout: 'full' })) // __ __ _ // | \/ | ___ _ __ __ _ ___ | | __ _ _ // | |\/| | / _ \ | '_ \ / _` | / _ \ | |/ / | | | | // | | | | | (_) | | | | | | (_| | | (_) | | < | |_| | // |_| |_| \___/ |_| |_| \__, | \___/ |_|\_\ \__,_| // |___/ const asciiArt = [ "__ __ _ ", "| \\/ | ___ _ __ __ _ ___ | | __ _ _ ", "| |\\/| | / _ \\ | '_ \\ / _` | / _ \\ | |/ / | | | |", "| | | | | (_) | | | | | | (_| | | (_) | | < | |_| |", "|_| |_| \\___/ |_| |_| \\__, | \\___/ |_|\\_\\ \\__,_|", " |___/ ", ].join("\n"); program.name("mongoku").description("MongoDB client for the web").version(version); // Custom error handler for better error messages program.configureOutput({ outputError: (str, write) => { // Check for common mistakes if (str.includes("too many arguments")) { const args = process.argv.slice(2); if (args.includes("pm2")) { write(chalk.red("❌ Error: Did you mean '--pm2' instead of 'pm2'?\n")); return; } } write(chalk.red(str)); }, }); // Start action handler (shared between default and explicit 'start' command) async function startAction(options: { pm2?: boolean; port?: string; readonly?: boolean }) { console.log(chalk.cyan(asciiArt)); const port = options.port || process.env.MONGOKU_SERVER_PORT || "3100"; process.env.MONGOKU_SERVER_PORT = port; if (!process.env.MONGOKU_SERVER_ORIGIN) { process.env.MONGOKU_SERVER_ORIGIN = `http://localhost:${port}`; } // Suppress the default "Listening on..." log since we show our own message process.env.MONGOKU_SUPPRESS_STARTUP_LOG = "true"; // Set read-only mode if specified if (options.readonly) { process.env.MONGOKU_READ_ONLY_MODE = "true"; console.log(chalk.yellow("🔒 Read-only mode enabled")); } if (options.pm2) { console.log(chalk.green(`🚀 Starting Mongoku with PM2 on port ${port}...`)); runCommand("pm2", ["--name", "mongoku", "start", "ecosystem.config.js", "--update-env"], { stdio: "inherit", cwd: rootDir, }); } else { console.log(chalk.green(`🚀 Starting Mongoku on port ${port}...`)); console.log(chalk.cyan(`📊 Open http://localhost:${port} in your browser`)); runCommand("node", ["build"], { stdio: "inherit", cwd: rootDir }); } } program .command("stop") .description("Stop the Mongoku server running with PM2") .action(() => { runCommand("pm2", ["stop", "mongoku"], { stdio: "inherit" }); }); program .command("start") .description("Start the Mongoku server") .option("--pm2", "Run with PM2") .option("-p, --port ", "Port to run on", "3100") .option("--readonly", "Enable read-only mode (prevents modifications)") .allowExcessArguments(false) .action(startAction); program .command("help") .description("Show help") .action(() => { program.outputHelp(); }); // Default action (start the server) program .option("--pm2", "Run with PM2") .option("-p, --port ", "Port to run on", "3100") .option("--readonly", "Enable read-only mode (prevents modifications)") .allowExcessArguments(false) .action(startAction); function runCommand(command: string, args: string[], options: SpawnOptions = {}): Promise { return new Promise((resolve, reject) => { const childProcess = spawn(command, args, { ...options, env: { ...process.env }, }); if (!options.detached && options.stdio !== "inherit") { childProcess.stdout?.on("data", (data) => { console.log(data.toString()); }); childProcess.stderr?.on("data", (data) => { console.error(data.toString()); }); } childProcess.on("error", (error) => { console.error(chalk.red(`Error: ${error.message}`)); reject(error); }); childProcess.on("close", (code) => { if (code !== 0 && !options.detached) { reject(new Error(`Command failed with exit code ${code}`)); } else { resolve(); } }); if (options.detached) { childProcess.unref(); resolve(); } }); } program.parse(process.argv);