#!/usr/bin/env node import { Command } from "commander"; import * as chalk from "chalk"; import * as path from "path"; import * as fs from "fs"; import * as inquirer from "inquirer"; import * as ora from "ora"; import { execSync } from "child_process"; const program = new Command(); const packageJson = JSON.parse( fs.readFileSync(path.join(__dirname, "../../package.json"), "utf8"), ); program .name("texagon-nestjs-health") .description( "A CLI to add comprehensive health monitoring to NestJS applications", ) .version(packageJson.version); program .command("install") .description("Install health monitoring module into your NestJS application") .option("--no-deps", "Skip installing dependencies") .option("--path ", "Path to the NestJS application", process.cwd()) .option("--prefix ", "Route prefix for health endpoints", "health") .action(async (options) => { console.log(chalk.blue("🚀 Installing NestJS Health Monitor...")); const targetPath = path.resolve(options.path); console.log(chalk.gray(`Target path: ${targetPath}`)); if (!fs.existsSync(path.join(targetPath, "nest-cli.json"))) { console.error( chalk.red( "❌ This does not appear to be a NestJS project. Please run this command in a NestJS project.", ), ); process.exit(1); } const answers = await inquirer.prompt([ { type: "confirm", name: "enableMemoryMonitoring", message: "Enable memory monitoring?", default: true, }, { type: "confirm", name: "enableDiskMonitoring", message: "Enable disk monitoring?", default: true, }, { type: "input", name: "diskPath", message: "Path to monitor for disk space:", default: "/", when: (answers) => answers.enableDiskMonitoring, }, { type: "number", name: "diskThresholdPercent", message: "Disk usage threshold percentage (0-100):", default: 75, when: (answers) => answers.enableDiskMonitoring, validate: (input) => { const num = parseFloat(input); return num >= 0 && num <= 100 ? true : "Please enter a number between 0 and 100"; }, }, { type: "confirm", name: "passwordProtected", message: "Enable password protection for health endpoints?", default: false, }, { type: "password", name: "password", message: "Enter password for health endpoints:", when: (answers) => answers.passwordProtected, validate: (input) => { return input.length >= 6 ? true : "Password must be at least 6 characters long"; }, }, { type: "password", name: "confirmPassword", message: "Confirm password:", when: (answers) => answers.passwordProtected, validate: (input, answers) => { return input === answers.password ? true : "Passwords do not match"; }, }, ]); if (answers.diskThresholdPercent) { answers.diskThresholdPercent = answers.diskThresholdPercent / 100; } const sourceDir = path.join(__dirname, "../health"); const targetDir = path.join(targetPath, "src/health"); const spinner = ora("Creating health module...").start(); try { if (!fs.existsSync(targetDir)) { fs.mkdirSync(targetDir, { recursive: true }); } const files = fs.readdirSync(sourceDir); files.forEach((file) => { if (file === "test-error.controller.ts") { return; } const source = path.join(sourceDir, file); const target = path.join(targetDir, file); fs.copyFileSync(source, target); }); const appModulePath = path.join(targetPath, "src/app.module.ts"); if (fs.existsSync(appModulePath)) { let appModuleContent = fs.readFileSync(appModulePath, "utf8"); if (!appModuleContent.includes("HealthModule")) { appModuleContent = appModuleContent.replace( /(import.*from.*;\n)/, `$1import { HealthModule } from './health/health.module';\n`, ); appModuleContent = appModuleContent.replace( /(imports\s*:\s*\[)/, `$1\n HealthModule.register({\n enableMemoryMonitoring: ${answers.enableMemoryMonitoring},\n enableDiskMonitoring: ${answers.enableDiskMonitoring},\n ${answers.diskPath ? `diskPath: '${answers.diskPath}',\n ` : ""}${answers.diskThresholdPercent ? `diskThresholdPercent: ${answers.diskThresholdPercent},\n ` : ""}${answers.passwordProtected ? `passwordProtected: true,\n password: '${answers.password}',\n ` : ""}routePrefix: '${options.prefix}',\n }),`, ); fs.writeFileSync(appModulePath, appModuleContent); } } spinner.succeed("Health module created successfully"); if (options.deps) { spinner.text = "Installing dependencies..."; spinner.start(); try { execSync("npm install @nestjs/terminus @nestjs/axios", { cwd: targetPath, stdio: "ignore", }); spinner.succeed("Dependencies installed successfully"); } catch (error) { spinner.fail("Failed to install dependencies"); console.error(chalk.red(`Error: ${error.message}`)); } } console.log( "\n" + chalk.green("==>[SUCCESS] NestJS Health Monitor installed successfully!"), ); console.log("\n" + chalk.yellow("Available endpoints:")); console.log(chalk.cyan(`- GET /${options.prefix}`)); console.log(chalk.cyan(`- GET /${options.prefix}/live`)); console.log(chalk.cyan(`- GET /${options.prefix}/ready`)); console.log(chalk.cyan(`- GET /${options.prefix}/stats`)); } catch (error) { spinner.fail("Failed to create health module"); console.error(chalk.red(`Error: ${error.message}`)); } }); program.parse();