UNPKG

@aaronshaf/ger

Version:

Gerrit CLI and SDK - A modern CLI tool and TypeScript SDK for Gerrit Code Review, built with Effect-TS

134 lines (118 loc) 4.86 kB
import { Effect } from 'effect' import { type ApiError, GerritApiService } from '@/api/gerrit' import type { ChangeInfo } from '@/schemas/gerrit' import { colors } from '@/utils/formatters' interface MineOptions { xml?: boolean json?: boolean } // ANSI color codes export const mineCommand = ( options: MineOptions, ): Effect.Effect<void, ApiError, GerritApiService> => Effect.gen(function* () { const gerritApi = yield* GerritApiService const changes = yield* gerritApi.listChanges('owner:self status:open') if (options.json) { const jsonOutput = { status: 'success', count: changes.length, changes: changes.map((change) => ({ number: change._number, subject: change.subject, project: change.project, branch: change.branch, status: change.status, change_id: change.change_id, ...(change.updated ? { updated: change.updated } : {}), ...(change.owner?.name ? { owner: change.owner.name } : {}), ...(change.labels ? { labels: change.labels } : {}), })), } console.log(JSON.stringify(jsonOutput, null, 2)) } else if (options.xml) { console.log(`<?xml version="1.0" encoding="UTF-8"?>`) console.log(`<changes count="${changes.length}">`) for (const change of changes) { console.log(` <change>`) console.log(` <number>${change._number}</number>`) console.log(` <subject><![CDATA[${change.subject}]]></subject>`) console.log(` <project>${change.project}</project>`) console.log(` <branch>${change.branch}</branch>`) console.log(` <status>${change.status}</status>`) console.log(` <change_id>${change.change_id}</change_id>`) if (change.updated) { console.log(` <updated>${change.updated}</updated>`) } if (change.owner?.name) { console.log(` <owner>${change.owner.name}</owner>`) } console.log(` </change>`) } console.log(`</changes>`) } else { // Pretty output by default if (changes.length === 0) { return } // Group changes by project const changesByProject = changes.reduce( (acc, change) => { if (!acc[change.project]) { acc[change.project] = [] } acc[change.project] = [...acc[change.project], change] return acc }, {} as Record<string, ChangeInfo[]>, ) // Sort projects alphabetically const sortedProjects = Object.keys(changesByProject).sort() for (const [index, project] of sortedProjects.entries()) { if (index > 0) { console.log('') // Add blank line between projects } console.log(`${colors.blue}${project}${colors.reset}`) const projectChanges = changesByProject[project] for (const change of projectChanges) { // Build status indicators const indicators: string[] = [] const indicatorChars: string[] = [] // Track visual characters for padding if (change.labels?.['Code-Review']) { const cr = change.labels['Code-Review'] if (cr.approved || cr.value === 2) { indicators.push(`${colors.green}${colors.reset}`) indicatorChars.push('✓') } else if (cr.rejected || cr.value === -2) { indicators.push(`${colors.red}${colors.reset}`) indicatorChars.push('✗') } else if (cr.recommended || cr.value === 1) { indicators.push(`${colors.cyan}${colors.reset}`) indicatorChars.push('↑') } else if (cr.disliked || cr.value === -1) { indicators.push(`${colors.yellow}${colors.reset}`) indicatorChars.push('↓') } } // Check for Verified label as well if (change.labels?.['Verified']) { const v = change.labels.Verified if (v.approved || v.value === 1) { if (!indicatorChars.includes('✓')) { indicators.push(`${colors.green}${colors.reset}`) indicatorChars.push('✓') } } else if (v.rejected || v.value === -1) { indicators.push(`${colors.red}${colors.reset}`) indicatorChars.push('✗') } } // Calculate padding based on visual characters, not color codes const visualWidth = indicatorChars.join(' ').length const padding = ' '.repeat(Math.max(0, 8 - visualWidth)) const statusStr = indicators.length > 0 ? indicators.join(' ') + padding : ' ' console.log(`${statusStr} ${change._number} ${change.subject}`) } } } })