UNPKG

@taizo-pro/github-discussions-cli

Version:

A powerful command-line tool for interacting with GitHub Discussions without opening a browser

178 lines 8.07 kB
import { Command } from 'commander'; import { table } from 'table'; import chalk from 'chalk'; import { GitHubClient, FileAuthManager, FileConfigManager } from '../../core/index.js'; import { EnhancedErrorHandler } from '../utils/enhanced-error-handler.js'; import { formatDate } from '../utils/formatters.js'; import { Spinner } from '../utils/spinner.js'; export const searchCommand = new Command('search') .description('Search for discussions') .argument('<query>', 'Search query') .argument('[repo]', 'Repository in owner/name format') .option('--in <fields>', 'Search in specific fields (title,body,comments)', 'title,body') .option('--author <username>', 'Filter by author') .option('--category <category>', 'Filter by category') .option('--state <state>', 'Filter by state (open,closed)', 'open') .option('--sort <field>', 'Sort by field (created,updated,comments)', 'updated') .option('--order <direction>', 'Sort order (asc,desc)', 'desc') .option('--limit <number>', 'Maximum results to return', '20') .option('--format <format>', 'Output format (table, json, markdown)', 'table') .action(async (query, repo, options) => { const spinner = new Spinner(); const context = { operation: 'search discussions', repository: repo, query: query }; try { const authManager = new FileAuthManager(); const configManager = new FileConfigManager(); spinner.start('Checking authentication...'); const token = await authManager.getToken(); if (!token) { spinner.fail('No GitHub token found'); console.error(chalk.red('Run "gh-discussions config" to set up authentication.')); process.exit(1); } spinner.succeed('Authentication verified'); const targetRepo = repo || (await configManager.getDefaultRepo()); if (!targetRepo) { console.error(chalk.red('No repository specified. Provide a repo argument or set a default repo.')); process.exit(1); } context.repository = targetRepo; spinner.start(`Searching discussions in ${targetRepo}...`); const client = new GitHubClient(token); // Get all discussions first (GitHub API doesn't have built-in search for discussions) const allDiscussions = await EnhancedErrorHandler.retryWithBackoff(() => client.listDiscussions(targetRepo, { first: 100, // Get more to search through orderBy: { field: 'UPDATED_AT', direction: 'DESC' }, }), context); spinner.update('Filtering search results...'); // Apply search filters let filteredDiscussions = allDiscussions.filter(discussion => { // Text search const searchFields = options.in.split(','); const searchInTitle = searchFields.includes('title') && discussion.title.toLowerCase().includes(query.toLowerCase()); const searchInBody = searchFields.includes('body') && discussion.body?.toLowerCase().includes(query.toLowerCase()); const matchesQuery = searchInTitle || searchInBody; // Author filter const matchesAuthor = !options.author || discussion.author.login.toLowerCase() === options.author.toLowerCase(); // Category filter const matchesCategory = !options.category || discussion.category?.name.toLowerCase() === options.category.toLowerCase(); return matchesQuery && matchesAuthor && matchesCategory; }); // Sort results filteredDiscussions = sortDiscussions(filteredDiscussions, options.sort, options.order); // Limit results const limit = parseInt(options.limit, 10); if (filteredDiscussions.length > limit) { filteredDiscussions = filteredDiscussions.slice(0, limit); } spinner.succeed(`Found ${filteredDiscussions.length} matching discussions`); if (filteredDiscussions.length === 0) { console.log(chalk.yellow(`No discussions found matching "${query}"`)); console.log(chalk.gray('Try:')); console.log(chalk.gray(' • Different search terms')); console.log(chalk.gray(' • Removing filters (--author, --category)')); console.log(chalk.gray(' • Searching in different fields (--in title,body,comments)')); return; } const outputFormat = options.format || (await configManager.getConfig()).outputFormat || 'table'; // Display search info console.log(); console.log(chalk.blue(`🔍 Search Results for "${query}"`)); console.log(chalk.gray(`Repository: ${targetRepo}`)); if (options.author) console.log(chalk.gray(`Author: ${options.author}`)); if (options.category) console.log(chalk.gray(`Category: ${options.category}`)); console.log(chalk.gray(`Found: ${filteredDiscussions.length} discussions`)); console.log(); switch (outputFormat) { case 'json': console.log(JSON.stringify(filteredDiscussions, null, 2)); break; case 'markdown': printMarkdownTable(filteredDiscussions, query); break; default: printSearchTable(filteredDiscussions, query); } } catch (error) { spinner.fail(); await EnhancedErrorHandler.handleError(error, context); } }); function sortDiscussions(discussions, sortField, order) { return discussions.sort((a, b) => { let comparison = 0; switch (sortField) { case 'created': comparison = new Date(a.createdAt).getTime() - new Date(b.createdAt).getTime(); break; case 'updated': comparison = new Date(a.updatedAt).getTime() - new Date(b.updatedAt).getTime(); break; case 'comments': comparison = a.commentCount - b.commentCount; break; default: comparison = new Date(a.updatedAt).getTime() - new Date(b.updatedAt).getTime(); } return order === 'desc' ? -comparison : comparison; }); } function highlightMatch(text, query) { if (!text) return ''; const regex = new RegExp(`(${query})`, 'gi'); return text.replace(regex, chalk.yellow.bold('$1')); } function printSearchTable(discussions, query) { const data = [ ['Title', 'Author', 'Comments', 'Updated', 'Category'] ]; discussions.forEach((discussion) => { const title = discussion.title.length > 50 ? discussion.title.substring(0, 47) + '...' : discussion.title; data.push([ highlightMatch(title, query), discussion.author.login, discussion.commentCount.toString(), formatDate(discussion.updatedAt), discussion.category?.name || 'N/A', ]); }); const output = table(data, { header: { alignment: 'center', content: chalk.bold('Search Results'), }, columns: { 0: { width: 50, wrapWord: true }, 1: { width: 15 }, 2: { width: 8, alignment: 'center' }, 3: { width: 12 }, 4: { width: 15 }, }, }); console.log(output); } function printMarkdownTable(discussions, query) { console.log('| Title | Author | Comments | Updated | Category |'); console.log('|-------|--------|----------|---------|----------|'); discussions.forEach((discussion) => { const title = discussion.title.length > 50 ? discussion.title.substring(0, 47) + '...' : discussion.title; console.log(`| ${title} | ${discussion.author.login} | ${discussion.commentCount} | ${formatDate(discussion.updatedAt)} | ${discussion.category?.name || 'N/A'} |`); }); } //# sourceMappingURL=search.js.map