webapps/console/scripts/manage.ts
Rama newjitsu · réplica de lectura del repositorio original.
#!/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 <command> --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<void>;
}
const commands: Record<string, Command> = {
"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 <id> --apply",
handler: async args => {
if (typeof args.workspace !== "string" || args.apply !== true)
throw new Error(
"Pass --workspace <id> --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 <command> [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 <command> --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();