From 04a064aaf330e4fc5d9a7201bf6d056fae735618 Mon Sep 17 00:00:00 2001 From: Arvin Xu Date: Thu, 12 Mar 2026 20:56:17 +0800 Subject: [PATCH] =?UTF-8?q?=E2=9C=A8=20feat:=20support=20batch=20topic=20d?= =?UTF-8?q?eletion=20from=20file=20(#12931)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add `--file` option to `lh topic delete` command, allowing users to pass topic IDs via a file (one per line or JSON array) for bulk deletion. Co-authored-by: Claude Opus 4.6 --- apps/cli/src/commands/topic.ts | 52 ++++++++++++++++++++++++++++------ 1 file changed, 44 insertions(+), 8 deletions(-) diff --git a/apps/cli/src/commands/topic.ts b/apps/cli/src/commands/topic.ts index cb5688f8ec..f097d85640 100644 --- a/apps/cli/src/commands/topic.ts +++ b/apps/cli/src/commands/topic.ts @@ -1,3 +1,5 @@ +import fs from 'node:fs'; + import type { Command } from 'commander'; import pc from 'picocolors'; @@ -134,12 +136,46 @@ export function registerTopicCommand(program: Command) { // ── delete ──────────────────────────────────────────── topic - .command('delete ') - .description('Delete one or more topics') + .command('delete [ids...]') + .description('Delete one or more topics (pass IDs as args or via --file)') + .option('-f, --file ', 'Read topic IDs from a file (one per line, or a JSON array)') .option('--yes', 'Skip confirmation prompt') - .action(async (ids: string[], options: { yes?: boolean }) => { + .action(async (ids: string[], options: { file?: string; yes?: boolean }) => { + let allIds = [...ids]; + + if (options.file) { + const content = fs.readFileSync(options.file, 'utf8').trim(); + let fileIds: string[]; + try { + const parsed = JSON.parse(content); + if (Array.isArray(parsed)) { + fileIds = parsed.map(String).filter(Boolean); + } else { + log.error('JSON file must contain an array of topic IDs.'); + process.exit(1); + } + } catch { + // Not JSON, treat as one ID per line + fileIds = content + .split('\n') + .map((line) => line.trim()) + .filter(Boolean); + } + allIds = [...allIds, ...fileIds]; + } + + if (allIds.length === 0) { + log.error('No topic IDs provided. Pass IDs as arguments or use --file.'); + process.exit(1); + } + + // Deduplicate + allIds = [...new Set(allIds)]; + if (!options.yes) { - const confirmed = await confirm(`Are you sure you want to delete ${ids.length} topic(s)?`); + const confirmed = await confirm( + `Are you sure you want to delete ${allIds.length} topic(s)?`, + ); if (!confirmed) { console.log('Cancelled.'); return; @@ -148,13 +184,13 @@ export function registerTopicCommand(program: Command) { const client = await getTrpcClient(); - if (ids.length === 1) { - await client.topic.removeTopic.mutate({ id: ids[0] }); + if (allIds.length === 1) { + await client.topic.removeTopic.mutate({ id: allIds[0] }); } else { - await client.topic.batchDelete.mutate({ ids }); + await client.topic.batchDelete.mutate({ ids: allIds }); } - console.log(`${pc.green('✓')} Deleted ${ids.length} topic(s)`); + console.log(`${pc.green('✓')} Deleted ${allIds.length} topic(s)`); }); // ── clone ───────────────────────────────────────────