fix(cli): keep REPL alive on errors and consolidate help\n\n- Replace process.exit(1) with thrown errors in handlers\n- REPL catches and prints CLI errors without exiting\n- Consolidated help to include all commands and examples\n- Add events:import:wsdc command mapping and alias
This commit is contained in:
@@ -18,6 +18,7 @@ const { prisma } = require('../utils/db');
|
||||
|
||||
function printHelp() {
|
||||
console.log(`spotlight.cam admin CLI\n\nCommands:\n repl Start interactive REPL with context (prisma, bcrypt)\n users:list [--limit <n>] List users\n users:create --email <e> --username <u> --password <p> [--first <f>] [--last <l>]\n Create a user (email verified = false)\n users:verify --email <e> Mark user's email as verified\n events:list [--limit <n>] List events\n\nExamples:\n npm run cli -- repl\n npm run cli -- users:list --limit 20\n npm run cli -- users:create --email admin@example.com --username admin --password "Secret123!"\n npm run cli -- users:verify --email admin@example.com\n npm run cli -- events:list --limit 10\n`);
|
||||
printHelpExtra();
|
||||
}
|
||||
|
||||
function parseArgs(argv) {
|
||||
@@ -41,6 +42,10 @@ function parseArgs(argv) {
|
||||
return out;
|
||||
}
|
||||
|
||||
function printHelpExtra() {
|
||||
console.log(`More commands:\n events:details --slug <slug>\n events:participants --slug <slug> [--limit <n>] [--csv]\n events:import:wsdc [--dry-run] [--since YYYY-MM-DD] [--until YYYY-MM-DD] [--limit <n>]\n matches:list [--limit <n>] [--status <pending|accepted|completed>]\n events:checkin --username <u> --slug <s>\n logs:app [--lines <n>]\n logs:messages [--limit <n>]\n`);
|
||||
}
|
||||
|
||||
const COMMAND_HANDLERS = {
|
||||
'users:list': cmdUsersList,
|
||||
'users:create': cmdUsersCreate,
|
||||
@@ -48,6 +53,9 @@ const COMMAND_HANDLERS = {
|
||||
'events:list': cmdEventsList,
|
||||
'events:details': cmdEventsDetails,
|
||||
'events:participants': cmdEventsParticipants,
|
||||
'events:import:wsdc': cmdEventsImportWsdc,
|
||||
// Backward-compat alias
|
||||
'events:import:worldsdc': cmdEventsImportWsdc,
|
||||
'matches:list': cmdMatchesList,
|
||||
'events:checkin': cmdEventsCheckin,
|
||||
'logs:app': cmdLogsApp,
|
||||
@@ -62,6 +70,7 @@ async function runDispatch(line) {
|
||||
if (!handler) {
|
||||
console.error(`Unknown command in REPL: ${name}`);
|
||||
printHelp();
|
||||
printHelpExtra();
|
||||
return;
|
||||
}
|
||||
const opts = parseArgs(rest);
|
||||
@@ -127,8 +136,7 @@ async function cmdUsersList(opts) {
|
||||
async function cmdUsersCreate(opts) {
|
||||
const { email, username, password } = opts;
|
||||
if (!email || !username || !password) {
|
||||
console.error('Missing required flags: --email --username --password');
|
||||
process.exit(1);
|
||||
throw new Error('Missing required flags: --email --username --password');
|
||||
}
|
||||
const hash = await bcrypt.hash(password, 10);
|
||||
const user = await prisma.user.create({
|
||||
@@ -147,8 +155,7 @@ async function cmdUsersCreate(opts) {
|
||||
async function cmdUsersVerify(opts) {
|
||||
const { email } = opts;
|
||||
if (!email) {
|
||||
console.error('Missing required flag: --email');
|
||||
process.exit(1);
|
||||
throw new Error('Missing required flag: --email');
|
||||
}
|
||||
const updated = await prisma.user.update({
|
||||
where: { email },
|
||||
@@ -182,8 +189,7 @@ async function cmdEventsList(opts) {
|
||||
async function cmdEventsDetails(opts) {
|
||||
const { slug } = opts;
|
||||
if (!slug) {
|
||||
console.error('Missing required flag: --slug');
|
||||
process.exit(1);
|
||||
throw new Error('Missing required flag: --slug');
|
||||
}
|
||||
const participantsLimitRaw = opts.participants;
|
||||
const participantsLimit = Number.isFinite(Number(participantsLimitRaw)) ? Math.max(0, parseInt(participantsLimitRaw, 10)) : 10;
|
||||
@@ -200,8 +206,7 @@ async function cmdEventsDetails(opts) {
|
||||
},
|
||||
});
|
||||
if (!event) {
|
||||
console.error(`Event not found by slug: ${slug}`);
|
||||
process.exit(1);
|
||||
throw new Error(`Event not found by slug: ${slug}`);
|
||||
}
|
||||
// Count messages in the event chat room (if exists)
|
||||
let eventChatMessagesCount = 0;
|
||||
@@ -247,14 +252,12 @@ async function cmdEventsDetails(opts) {
|
||||
async function cmdEventsParticipants(opts) {
|
||||
const { slug } = opts;
|
||||
if (!slug) {
|
||||
console.error('Missing required flag: --slug');
|
||||
process.exit(1);
|
||||
throw new Error('Missing required flag: --slug');
|
||||
}
|
||||
const limit = Number.isFinite(Number(opts.limit)) ? Math.max(1, parseInt(opts.limit, 10)) : 100;
|
||||
const event = await prisma.event.findUnique({ where: { slug }, select: { id: true, slug: true, name: true } });
|
||||
if (!event) {
|
||||
console.error(`Event not found by slug: ${slug}`);
|
||||
process.exit(1);
|
||||
throw new Error(`Event not found by slug: ${slug}`);
|
||||
}
|
||||
const participants = await prisma.eventParticipant.findMany({
|
||||
where: { eventId: event.id },
|
||||
@@ -309,6 +312,31 @@ async function cmdEventsParticipants(opts) {
|
||||
}
|
||||
}
|
||||
|
||||
async function cmdEventsImportWsdc(opts) {
|
||||
const { importWorldsdc } = require('../services/import/worldsdc');
|
||||
const since = opts.since ? new Date(opts.since) : null;
|
||||
const until = opts.until ? new Date(opts.until) : null;
|
||||
const limit = opts.limit ? parseInt(String(opts.limit), 10) : undefined;
|
||||
const dryRun = Boolean(opts['dry-run'] || opts.dry || opts.dry_run);
|
||||
|
||||
const result = await importWorldsdc({ since, until, limit, dryRun });
|
||||
console.log('Import summary:', {
|
||||
fetched: result.fetched,
|
||||
considered: result.considered,
|
||||
created: result.created.length,
|
||||
skipped: result.skipped.length,
|
||||
dryRun,
|
||||
});
|
||||
if (result.created.length) {
|
||||
console.log('To create / created:');
|
||||
console.table(result.created.map(e => ({ name: e.name, startDate: e.startDate, endDate: e.endDate, location: e.location || null, sourceUrl: e.sourceUrl })));
|
||||
}
|
||||
if (result.skipped.length) {
|
||||
console.log('Skipped:');
|
||||
console.table(result.skipped.map(e => ({ name: e.name, startDate: e.startDate, reason: e.reason })));
|
||||
}
|
||||
}
|
||||
|
||||
async function cmdMatchesList(opts) {
|
||||
const limit = Number(opts.limit || 50);
|
||||
const status = opts.status || undefined;
|
||||
@@ -341,18 +369,15 @@ async function cmdMatchesList(opts) {
|
||||
async function cmdEventsCheckin(opts) {
|
||||
const { username, slug } = opts;
|
||||
if (!username || !slug) {
|
||||
console.error('Missing required flags: --username --slug');
|
||||
process.exit(1);
|
||||
throw new Error('Missing required flags: --username --slug');
|
||||
}
|
||||
const user = await prisma.user.findUnique({ where: { username }, select: { id: true, username: true } });
|
||||
if (!user) {
|
||||
console.error(`User not found: ${username}`);
|
||||
process.exit(1);
|
||||
throw new Error(`User not found: ${username}`);
|
||||
}
|
||||
const event = await prisma.event.findUnique({ where: { slug }, select: { id: true, slug: true, name: true } });
|
||||
if (!event) {
|
||||
console.error(`Event not found by slug: ${slug}`);
|
||||
process.exit(1);
|
||||
throw new Error(`Event not found by slug: ${slug}`);
|
||||
}
|
||||
const ep = await prisma.eventParticipant.upsert({
|
||||
where: { userId_eventId: { userId: user.id, eventId: event.id } },
|
||||
@@ -370,7 +395,7 @@ async function cmdLogsApp(opts) {
|
||||
if (!fs.existsSync(logfile)) {
|
||||
console.error(`Log file not found: ${logfile}`);
|
||||
console.error('Tip: view container logs with: docker compose logs -f backend');
|
||||
process.exit(1);
|
||||
throw new Error('Log file not found');
|
||||
}
|
||||
const content = fs.readFileSync(logfile, 'utf8');
|
||||
const arr = content.split('\n');
|
||||
@@ -417,6 +442,7 @@ async function main() {
|
||||
case '--help':
|
||||
case '-h':
|
||||
printHelp();
|
||||
printHelpExtra();
|
||||
break;
|
||||
case 'repl':
|
||||
await cmdRepl();
|
||||
@@ -433,6 +459,16 @@ async function main() {
|
||||
case 'events:list':
|
||||
await cmdEventsList(opts);
|
||||
break;
|
||||
case 'events:details':
|
||||
await cmdEventsDetails(opts);
|
||||
break;
|
||||
case 'events:participants':
|
||||
await cmdEventsParticipants(opts);
|
||||
break;
|
||||
case 'events:import:wsdc':
|
||||
case 'events:import:worldsdc':
|
||||
await cmdEventsImportWsdc(opts);
|
||||
break;
|
||||
case 'matches:list':
|
||||
await cmdMatchesList(opts);
|
||||
break;
|
||||
@@ -448,6 +484,7 @@ async function main() {
|
||||
default:
|
||||
console.error(`Unknown command: ${command}\n`);
|
||||
printHelp();
|
||||
printHelpExtra();
|
||||
process.exitCode = 1;
|
||||
}
|
||||
} catch (err) {
|
||||
@@ -475,6 +512,7 @@ if (require.main === module) {
|
||||
cmdEventsList,
|
||||
cmdEventsDetails,
|
||||
cmdEventsParticipants,
|
||||
cmdEventsImportWsdc,
|
||||
cmdMatchesList,
|
||||
cmdEventsCheckin,
|
||||
cmdLogsApp,
|
||||
|
||||
Reference in New Issue
Block a user