@texagon/nestjs-health
Version:
A comprehensive health monitoring package for NestJS applications with metrics and Kubernetes-ready health checks
184 lines (159 loc) • 6.12 kB
text/typescript
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>", "Path to the NestJS application", process.cwd())
.option("--prefix <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();