#!/usr/bin/env node /** * Management CLI for Jitsu Console * * Usage: * pnpm manage seed - Seed demo connections * pnpm manage password-hash [secret] - Generate password hash * pnpm manage help - Show help * pnpm manage --help | -h | -? - Show help * pnpm manage --help - Show command-specific help * * node build/manage.js seed - (In Docker) */ // When running in Docker, env vars should be set via -e flags import minimist from "minimist"; import { seedDemoConnections, seedUserAndWorkspace } from "../lib/server/seed"; import { createHash, getClickhouseConfig, randomId, requireDefined } from "juava"; import { getServerLog } from "../lib/server/log"; import { getServerEnv } from "../lib/server/serverEnv"; import { initEventsLogTables } from "../lib/server/clickhouse-init"; const log = getServerLog("manage"); interface Command { description: string; usage?: string; handler: (args: minimist.ParsedArgs) => Promise; } const commands: Record = { "migrate-reverse-sync-settings": { description: "Move legacy Reverse ETL setup/audience entities to link settings and runtime state (pause and drain first)", usage: "pnpm manage migrate-reverse-sync-settings --workspace --apply", handler: async args => { if (typeof args.workspace !== "string" || args.apply !== true) throw new Error( "Pass --workspace --apply after pausing/draining legacy syncs and backing up the database" ); const { db } = await import("../lib/server/db"); const { migrateReverseSyncSettings } = await import("../lib/server/migrate-reverse-sync-settings"); console.log(await migrateReverseSyncSettings(db.prisma(), args.workspace)); }, }, seed: { description: "Seed demo connections (stream, destination, and link)", usage: "pnpm manage seed", handler: async () => { console.log("🌱 Checking seed conditions..."); await seedUserAndWorkspace(); await seedDemoConnections(); // Note: seedDemoConnections() logs its own status messages }, }, "events-log-init": { description: "Create the events-log ClickHouse objects (database, events_log/task_log/dead_letter, retention machinery)", usage: "pnpm manage events-log-init", handler: async () => { // Same DDL the admin/events-log-init route runs in prod — idempotent // (CREATE ... IF NOT EXISTS), so it's safe to run on every startup. // Import the ClickHouse client lazily so unrelated commands (e.g. seed) // don't construct it when CLICKHOUSE_URL isn't configured. const { clickhouse } = await import("../lib/server/clickhouse"); const serverEnv = getServerEnv(); const chConfig = getClickhouseConfig(serverEnv); log.atInfo().log("Initializing events-log ClickHouse tables..."); await initEventsLogTables({ clickhouse, database: requireDefined(chConfig.database, "ClickHouse database is not configured"), cluster: serverEnv.CLICKHOUSE_METRICS_CLUSTER || serverEnv.CLICKHOUSE_CLUSTER, username: chConfig.username, password: chConfig.password, }); log.atInfo().log("events-log ClickHouse tables ready"); }, }, "password-hash": { description: "Generate password hash", usage: "pnpm manage password-hash [secret]", handler: async args => { // Get the secret from remaining positional args (after the command name) const secret = args._.length > 1 ? args._[1] : randomId({ digits: 32, strongRandom: true }); if (args._.length <= 1) { log.atInfo().log("No secret provided, generating a random one"); } const serverEnv = getServerEnv(); log .atInfo() .log( `Calculating password hash. Using ${ serverEnv.GLOBAL_HASH_SECRET || serverEnv.CONSOLE_TOKEN_SECRET ? "custom token secret" : "default hash secret" }` ); log.atInfo().log(`Hashing ${secret} → ${createHash(secret)}`); }, }, help: { description: "Show this help message", handler: async () => { console.log("Jitsu Console Management CLI\n"); console.log("Usage:"); console.log(" pnpm manage [options]\n"); console.log("Available commands:"); for (const [name, cmd] of Object.entries(commands)) { const usage = cmd.usage || `pnpm manage ${name}`; console.log(` ${name.padEnd(15)} - ${cmd.description}`); console.log(` ${" ".repeat(15)} Usage: ${usage}`); } console.log("\nHelp options:"); console.log(" pnpm manage --help # Show this help"); console.log(" pnpm manage -h # Short form"); console.log(" pnpm manage -? # Alternative"); console.log(" pnpm manage --help # Show help for specific command"); }, }, }; async function main() { const args = minimist(process.argv.slice(2), { alias: { h: "help", "?": "help" }, boolean: ["help"], }); // Get the command from the first positional argument let commandName = args._[0]; // Show general help if no command or if help flags are used without a command if (!commandName || (commandName !== "help" && (args.help || args.h || args["?"]))) { await commands.help.handler(args); process.exit(commandName ? 0 : 1); } // Check if command exists const command = commands[commandName]; if (!command) { console.error(`Unknown command: ${commandName}\n`); await commands.help.handler(args); process.exit(1); } // Show command-specific help if --help or -? is used with a command if (args.help || args.h || args["?"]) { console.log(`\n${commandName}: ${command.description}\n`); console.log(`Usage: ${command.usage || `pnpm manage ${commandName}`}\n`); // Add command-specific examples if needed if (commandName === "password-hash") { console.log("Examples:"); console.log(" pnpm manage password-hash mysecret # Hash a specific password"); console.log(" pnpm manage password-hash # Generate and hash a random password\n"); } process.exit(0); } try { await command.handler(args); } catch (error) { console.error("❌ Command failed:", error); process.exit(1); } } main();