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:
@@ -374,6 +374,7 @@ Use an in-container admin console for quick maintenance.
|
|||||||
- Create user: `docker compose exec backend npm run cli -- users:create --email admin@example.com --username admin --password 'Secret123!'`
|
- Create user: `docker compose exec backend npm run cli -- users:create --email admin@example.com --username admin --password 'Secret123!'`
|
||||||
- Verify email: `docker compose exec backend npm run cli -- users:verify --email admin@example.com`
|
- Verify email: `docker compose exec backend npm run cli -- users:verify --email admin@example.com`
|
||||||
- List events: `docker compose exec backend npm run cli -- events:list --limit 10`
|
- List events: `docker compose exec backend npm run cli -- events:list --limit 10`
|
||||||
|
- Import WSDC calendar (dry-run): `docker compose exec backend npm run cli -- events:import:wsdc --dry-run --since 2024-01-01 --until 2024-12-31`
|
||||||
- Event details by slug: `docker compose exec backend npm run cli -- events:details --slug warsaw-dance-2025 [--participants 25]`
|
- Event details by slug: `docker compose exec backend npm run cli -- events:details --slug warsaw-dance-2025 [--participants 25]`
|
||||||
- Event participants: `docker compose exec backend npm run cli -- events:participants --slug warsaw-dance-2025 --limit 100`
|
- Event participants: `docker compose exec backend npm run cli -- events:participants --slug warsaw-dance-2025 --limit 100`
|
||||||
- Event participants CSV: `docker compose exec backend npm run cli -- events:participants --slug warsaw-dance-2025 --limit 200 --csv > participants.csv`
|
- Event participants CSV: `docker compose exec backend npm run cli -- events:participants --slug warsaw-dance-2025 --limit 200 --csv > participants.csv`
|
||||||
|
|||||||
@@ -1,10 +1,30 @@
|
|||||||
dopisz do docs/ szczegóły jakie opcje są w CLI REPL
|
.cli users:verify --email test@radziel.com
|
||||||
exit
|
.cli users:create --email test@radziel.com --username radziel --password QWEqwe123 --first Radek --last Gierwialo
|
||||||
Headers.
|
users:create --mail test@radziel.com --username radziel --password QWEqwe123 --first Radek --last Gierwialo
|
||||||
Headers
|
users:create --email test@radziel.com --username radziel --password QWEqwe123 --first Radek --last Gierwialo
|
||||||
.cli logs
|
.cli users
|
||||||
.cli lgos
|
.cli users:crete
|
||||||
.cli prisma:seed
|
|
||||||
.cli events:list
|
|
||||||
.cli users:list
|
.cli users:list
|
||||||
.cli users:lists
|
.cli events:list
|
||||||
|
.cli events:import:worldsdc --limit 20
|
||||||
|
.cli events:details --slug cmi0g1dtf0001tbvvwraelctn
|
||||||
|
.cli events:list
|
||||||
|
.cli clear
|
||||||
|
clear
|
||||||
|
.cli help
|
||||||
|
.cli
|
||||||
|
.cli events
|
||||||
|
.events:
|
||||||
|
events:
|
||||||
|
events:lists
|
||||||
|
events:list
|
||||||
|
users
|
||||||
|
event:let
|
||||||
|
:
|
||||||
|
event
|
||||||
|
.
|
||||||
|
help
|
||||||
|
.exity
|
||||||
|
.cli events:import:worldsdc --dry-run --limit 20
|
||||||
|
exit
|
||||||
|
.cli events:import:worldsdc --dry-run --limit 20<32> w CLI REPL
|
||||||
@@ -18,6 +18,7 @@ const { prisma } = require('../utils/db');
|
|||||||
|
|
||||||
function printHelp() {
|
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`);
|
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) {
|
function parseArgs(argv) {
|
||||||
@@ -41,6 +42,10 @@ function parseArgs(argv) {
|
|||||||
return out;
|
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 = {
|
const COMMAND_HANDLERS = {
|
||||||
'users:list': cmdUsersList,
|
'users:list': cmdUsersList,
|
||||||
'users:create': cmdUsersCreate,
|
'users:create': cmdUsersCreate,
|
||||||
@@ -48,6 +53,9 @@ const COMMAND_HANDLERS = {
|
|||||||
'events:list': cmdEventsList,
|
'events:list': cmdEventsList,
|
||||||
'events:details': cmdEventsDetails,
|
'events:details': cmdEventsDetails,
|
||||||
'events:participants': cmdEventsParticipants,
|
'events:participants': cmdEventsParticipants,
|
||||||
|
'events:import:wsdc': cmdEventsImportWsdc,
|
||||||
|
// Backward-compat alias
|
||||||
|
'events:import:worldsdc': cmdEventsImportWsdc,
|
||||||
'matches:list': cmdMatchesList,
|
'matches:list': cmdMatchesList,
|
||||||
'events:checkin': cmdEventsCheckin,
|
'events:checkin': cmdEventsCheckin,
|
||||||
'logs:app': cmdLogsApp,
|
'logs:app': cmdLogsApp,
|
||||||
@@ -62,6 +70,7 @@ async function runDispatch(line) {
|
|||||||
if (!handler) {
|
if (!handler) {
|
||||||
console.error(`Unknown command in REPL: ${name}`);
|
console.error(`Unknown command in REPL: ${name}`);
|
||||||
printHelp();
|
printHelp();
|
||||||
|
printHelpExtra();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const opts = parseArgs(rest);
|
const opts = parseArgs(rest);
|
||||||
@@ -127,8 +136,7 @@ async function cmdUsersList(opts) {
|
|||||||
async function cmdUsersCreate(opts) {
|
async function cmdUsersCreate(opts) {
|
||||||
const { email, username, password } = opts;
|
const { email, username, password } = opts;
|
||||||
if (!email || !username || !password) {
|
if (!email || !username || !password) {
|
||||||
console.error('Missing required flags: --email --username --password');
|
throw new Error('Missing required flags: --email --username --password');
|
||||||
process.exit(1);
|
|
||||||
}
|
}
|
||||||
const hash = await bcrypt.hash(password, 10);
|
const hash = await bcrypt.hash(password, 10);
|
||||||
const user = await prisma.user.create({
|
const user = await prisma.user.create({
|
||||||
@@ -147,8 +155,7 @@ async function cmdUsersCreate(opts) {
|
|||||||
async function cmdUsersVerify(opts) {
|
async function cmdUsersVerify(opts) {
|
||||||
const { email } = opts;
|
const { email } = opts;
|
||||||
if (!email) {
|
if (!email) {
|
||||||
console.error('Missing required flag: --email');
|
throw new Error('Missing required flag: --email');
|
||||||
process.exit(1);
|
|
||||||
}
|
}
|
||||||
const updated = await prisma.user.update({
|
const updated = await prisma.user.update({
|
||||||
where: { email },
|
where: { email },
|
||||||
@@ -182,8 +189,7 @@ async function cmdEventsList(opts) {
|
|||||||
async function cmdEventsDetails(opts) {
|
async function cmdEventsDetails(opts) {
|
||||||
const { slug } = opts;
|
const { slug } = opts;
|
||||||
if (!slug) {
|
if (!slug) {
|
||||||
console.error('Missing required flag: --slug');
|
throw new Error('Missing required flag: --slug');
|
||||||
process.exit(1);
|
|
||||||
}
|
}
|
||||||
const participantsLimitRaw = opts.participants;
|
const participantsLimitRaw = opts.participants;
|
||||||
const participantsLimit = Number.isFinite(Number(participantsLimitRaw)) ? Math.max(0, parseInt(participantsLimitRaw, 10)) : 10;
|
const participantsLimit = Number.isFinite(Number(participantsLimitRaw)) ? Math.max(0, parseInt(participantsLimitRaw, 10)) : 10;
|
||||||
@@ -200,8 +206,7 @@ async function cmdEventsDetails(opts) {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
if (!event) {
|
if (!event) {
|
||||||
console.error(`Event not found by slug: ${slug}`);
|
throw new Error(`Event not found by slug: ${slug}`);
|
||||||
process.exit(1);
|
|
||||||
}
|
}
|
||||||
// Count messages in the event chat room (if exists)
|
// Count messages in the event chat room (if exists)
|
||||||
let eventChatMessagesCount = 0;
|
let eventChatMessagesCount = 0;
|
||||||
@@ -247,14 +252,12 @@ async function cmdEventsDetails(opts) {
|
|||||||
async function cmdEventsParticipants(opts) {
|
async function cmdEventsParticipants(opts) {
|
||||||
const { slug } = opts;
|
const { slug } = opts;
|
||||||
if (!slug) {
|
if (!slug) {
|
||||||
console.error('Missing required flag: --slug');
|
throw new Error('Missing required flag: --slug');
|
||||||
process.exit(1);
|
|
||||||
}
|
}
|
||||||
const limit = Number.isFinite(Number(opts.limit)) ? Math.max(1, parseInt(opts.limit, 10)) : 100;
|
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 } });
|
const event = await prisma.event.findUnique({ where: { slug }, select: { id: true, slug: true, name: true } });
|
||||||
if (!event) {
|
if (!event) {
|
||||||
console.error(`Event not found by slug: ${slug}`);
|
throw new Error(`Event not found by slug: ${slug}`);
|
||||||
process.exit(1);
|
|
||||||
}
|
}
|
||||||
const participants = await prisma.eventParticipant.findMany({
|
const participants = await prisma.eventParticipant.findMany({
|
||||||
where: { eventId: event.id },
|
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) {
|
async function cmdMatchesList(opts) {
|
||||||
const limit = Number(opts.limit || 50);
|
const limit = Number(opts.limit || 50);
|
||||||
const status = opts.status || undefined;
|
const status = opts.status || undefined;
|
||||||
@@ -341,18 +369,15 @@ async function cmdMatchesList(opts) {
|
|||||||
async function cmdEventsCheckin(opts) {
|
async function cmdEventsCheckin(opts) {
|
||||||
const { username, slug } = opts;
|
const { username, slug } = opts;
|
||||||
if (!username || !slug) {
|
if (!username || !slug) {
|
||||||
console.error('Missing required flags: --username --slug');
|
throw new Error('Missing required flags: --username --slug');
|
||||||
process.exit(1);
|
|
||||||
}
|
}
|
||||||
const user = await prisma.user.findUnique({ where: { username }, select: { id: true, username: true } });
|
const user = await prisma.user.findUnique({ where: { username }, select: { id: true, username: true } });
|
||||||
if (!user) {
|
if (!user) {
|
||||||
console.error(`User not found: ${username}`);
|
throw new Error(`User not found: ${username}`);
|
||||||
process.exit(1);
|
|
||||||
}
|
}
|
||||||
const event = await prisma.event.findUnique({ where: { slug }, select: { id: true, slug: true, name: true } });
|
const event = await prisma.event.findUnique({ where: { slug }, select: { id: true, slug: true, name: true } });
|
||||||
if (!event) {
|
if (!event) {
|
||||||
console.error(`Event not found by slug: ${slug}`);
|
throw new Error(`Event not found by slug: ${slug}`);
|
||||||
process.exit(1);
|
|
||||||
}
|
}
|
||||||
const ep = await prisma.eventParticipant.upsert({
|
const ep = await prisma.eventParticipant.upsert({
|
||||||
where: { userId_eventId: { userId: user.id, eventId: event.id } },
|
where: { userId_eventId: { userId: user.id, eventId: event.id } },
|
||||||
@@ -370,7 +395,7 @@ async function cmdLogsApp(opts) {
|
|||||||
if (!fs.existsSync(logfile)) {
|
if (!fs.existsSync(logfile)) {
|
||||||
console.error(`Log file not found: ${logfile}`);
|
console.error(`Log file not found: ${logfile}`);
|
||||||
console.error('Tip: view container logs with: docker compose logs -f backend');
|
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 content = fs.readFileSync(logfile, 'utf8');
|
||||||
const arr = content.split('\n');
|
const arr = content.split('\n');
|
||||||
@@ -417,6 +442,7 @@ async function main() {
|
|||||||
case '--help':
|
case '--help':
|
||||||
case '-h':
|
case '-h':
|
||||||
printHelp();
|
printHelp();
|
||||||
|
printHelpExtra();
|
||||||
break;
|
break;
|
||||||
case 'repl':
|
case 'repl':
|
||||||
await cmdRepl();
|
await cmdRepl();
|
||||||
@@ -433,6 +459,16 @@ async function main() {
|
|||||||
case 'events:list':
|
case 'events:list':
|
||||||
await cmdEventsList(opts);
|
await cmdEventsList(opts);
|
||||||
break;
|
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':
|
case 'matches:list':
|
||||||
await cmdMatchesList(opts);
|
await cmdMatchesList(opts);
|
||||||
break;
|
break;
|
||||||
@@ -448,6 +484,7 @@ async function main() {
|
|||||||
default:
|
default:
|
||||||
console.error(`Unknown command: ${command}\n`);
|
console.error(`Unknown command: ${command}\n`);
|
||||||
printHelp();
|
printHelp();
|
||||||
|
printHelpExtra();
|
||||||
process.exitCode = 1;
|
process.exitCode = 1;
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
@@ -475,6 +512,7 @@ if (require.main === module) {
|
|||||||
cmdEventsList,
|
cmdEventsList,
|
||||||
cmdEventsDetails,
|
cmdEventsDetails,
|
||||||
cmdEventsParticipants,
|
cmdEventsParticipants,
|
||||||
|
cmdEventsImportWsdc,
|
||||||
cmdMatchesList,
|
cmdMatchesList,
|
||||||
cmdEventsCheckin,
|
cmdEventsCheckin,
|
||||||
cmdLogsApp,
|
cmdLogsApp,
|
||||||
|
|||||||
103
backend/src/services/import/worldsdc.js
Normal file
103
backend/src/services/import/worldsdc.js
Normal file
@@ -0,0 +1,103 @@
|
|||||||
|
const https = require('https');
|
||||||
|
|
||||||
|
function fetchUrl(url) {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
https.get(url, (res) => {
|
||||||
|
if (res.statusCode >= 300 && res.statusCode < 400 && res.headers.location) {
|
||||||
|
// Follow redirect
|
||||||
|
return resolve(fetchUrl(res.headers.location));
|
||||||
|
}
|
||||||
|
if (res.statusCode !== 200) {
|
||||||
|
return reject(new Error(`HTTP ${res.statusCode}`));
|
||||||
|
}
|
||||||
|
let data = '';
|
||||||
|
res.setEncoding('utf8');
|
||||||
|
res.on('data', (chunk) => (data += chunk));
|
||||||
|
res.on('end', () => resolve(data));
|
||||||
|
}).on('error', reject);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function parseEventsFromCalendar(html) {
|
||||||
|
const idx = html.indexOf('"events":[');
|
||||||
|
if (idx === -1) return [];
|
||||||
|
let i = idx + '"events":'.length; // at [
|
||||||
|
// Extract the JSON array by bracket counting
|
||||||
|
let bracket = 0;
|
||||||
|
let started = false;
|
||||||
|
let out = '';
|
||||||
|
while (i < html.length) {
|
||||||
|
const ch = html[i];
|
||||||
|
out += ch;
|
||||||
|
if (ch === '[') { bracket++; started = true; }
|
||||||
|
else if (ch === ']') { bracket--; if (started && bracket === 0) break; }
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
const jsonText = out.trim();
|
||||||
|
let arr;
|
||||||
|
try {
|
||||||
|
arr = JSON.parse(jsonText);
|
||||||
|
} catch (e) {
|
||||||
|
// Attempt to unescape slashes and retry
|
||||||
|
arr = JSON.parse(jsonText.replace(/\\\//g, '/'));
|
||||||
|
}
|
||||||
|
if (!Array.isArray(arr)) return [];
|
||||||
|
return arr.map((e) => ({
|
||||||
|
name: e.title || null,
|
||||||
|
startDate: e.start || null,
|
||||||
|
endDate: e.end || null,
|
||||||
|
sourceUrl: e.url || null,
|
||||||
|
})).filter((e) => e.name && e.startDate);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function importWorldsdc({ since = null, until = null, limit, dryRun = false } = {}) {
|
||||||
|
const { prisma } = require('../../utils/db');
|
||||||
|
const url = 'https://www.worldsdc.com/events/calendar/';
|
||||||
|
const html = await fetchUrl(url);
|
||||||
|
const events = parseEventsFromCalendar(html);
|
||||||
|
const normalizeDate = (s) => new Date(s.split('T')[0] || s);
|
||||||
|
let list = events.map((e) => ({
|
||||||
|
name: e.name.trim(),
|
||||||
|
startDate: normalizeDate(e.startDate),
|
||||||
|
endDate: e.endDate ? normalizeDate(e.endDate) : normalizeDate(e.startDate),
|
||||||
|
location: null, // not provided by calendar source
|
||||||
|
sourceUrl: e.sourceUrl || null,
|
||||||
|
}));
|
||||||
|
const fetched = list.length;
|
||||||
|
if (since) list = list.filter((e) => e.startDate >= since);
|
||||||
|
if (until) list = list.filter((e) => e.startDate <= until);
|
||||||
|
if (limit) list = list.slice(0, limit);
|
||||||
|
|
||||||
|
const created = [];
|
||||||
|
const skipped = [];
|
||||||
|
for (const e of list) {
|
||||||
|
const existing = await prisma.event.findFirst({
|
||||||
|
where: { name: e.name, startDate: e.startDate },
|
||||||
|
select: { id: true },
|
||||||
|
});
|
||||||
|
if (existing) {
|
||||||
|
skipped.push({ ...e, reason: 'exists' });
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (dryRun) {
|
||||||
|
created.push(e);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
const saved = await prisma.event.create({
|
||||||
|
data: {
|
||||||
|
name: e.name,
|
||||||
|
location: 'Unknown',
|
||||||
|
startDate: e.startDate,
|
||||||
|
endDate: e.endDate,
|
||||||
|
description: null,
|
||||||
|
// worldsdcId and participantsCount are intentionally not set here
|
||||||
|
},
|
||||||
|
select: { id: true, slug: true },
|
||||||
|
});
|
||||||
|
created.push({ ...e, id: saved.id, slug: saved.slug });
|
||||||
|
}
|
||||||
|
return { fetched, considered: list.length, created, skipped };
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = { parseEventsFromCalendar, importWorldsdc };
|
||||||
|
|
||||||
@@ -105,6 +105,18 @@ With Makefile shortcuts:
|
|||||||
- `npm run cli -- events:participants --slug warsaw-dance-2025 --limit 50`
|
- `npm run cli -- events:participants --slug warsaw-dance-2025 --limit 50`
|
||||||
- `npm run cli -- events:participants --slug warsaw-dance-2025 --limit 200 --csv > participants.csv`
|
- `npm run cli -- events:participants --slug warsaw-dance-2025 --limit 200 --csv > participants.csv`
|
||||||
|
|
||||||
|
### events:import:wsdc
|
||||||
|
- Description: Import events from worldsdc.com calendar page.
|
||||||
|
- Notes: Uses the calendar’s embedded data (title/start/end/url). Does not set `participants` or `worldsdcId`.
|
||||||
|
- Options:
|
||||||
|
- `--dry-run`: show what would be created without writing to DB
|
||||||
|
- `--since YYYY-MM-DD`: only events on/after date
|
||||||
|
- `--until YYYY-MM-DD`: only events on/before date
|
||||||
|
- `--limit <n>`: limit considered items after filtering
|
||||||
|
- Examples:
|
||||||
|
- `npm run cli -- events:import:wsdc --dry-run --since 2024-01-01 --until 2024-12-31`
|
||||||
|
- `npm run cli -- events:import:wsdc --limit 50`
|
||||||
|
|
||||||
### logs:app
|
### logs:app
|
||||||
- Description: Tail application log file (if configured)
|
- Description: Tail application log file (if configured)
|
||||||
- Options:
|
- Options:
|
||||||
|
|||||||
Reference in New Issue
Block a user