155 lines
5.2 KiB
TypeScript
155 lines
5.2 KiB
TypeScript
import { Command } from 'commander'
|
|
import ora from 'ora'
|
|
import { findPackages } from '../utils/package-json.js'
|
|
import { getCIStatus, formatDate, type CIStatus } from '../utils/forgejo.js'
|
|
import { colors, printTable, logInfo, logWarning, logError } from '../utils/output.js'
|
|
|
|
export function createCICommand(): Command {
|
|
return new Command('ci')
|
|
.description('Check Forgejo Actions CI status')
|
|
.argument('[package]', 'Specific package to check (optional)')
|
|
.option('-a, --all', 'Check all packages in workspace')
|
|
.option('-f, --failed', 'Show only failed packages')
|
|
.option('-v, --verbose', 'Show detailed failure information')
|
|
.option('--path <path>', 'Path to workspace (default: current directory)')
|
|
.action(async (packageName: string | undefined, options: {
|
|
all?: boolean
|
|
failed?: boolean
|
|
verbose?: boolean
|
|
path?: string
|
|
}) => {
|
|
const basePath = options.path || process.cwd()
|
|
|
|
// Check for FORGEJO_TOKEN
|
|
if (!process.env.FORGEJO_TOKEN) {
|
|
logError('FORGEJO_TOKEN environment variable not set')
|
|
console.log()
|
|
console.log('Set it with:')
|
|
console.log(colors.dim(' export FORGEJO_TOKEN="your-token-here"'))
|
|
process.exit(1)
|
|
}
|
|
|
|
const spinner = ora('Checking CI status...').start()
|
|
|
|
try {
|
|
let packagesToCheck: string[] = []
|
|
|
|
if (packageName) {
|
|
packagesToCheck = [packageName]
|
|
} else if (options.all) {
|
|
const packages = await findPackages(basePath)
|
|
packagesToCheck = packages
|
|
.filter(p => p.shouldPublish)
|
|
.map(p => p.name)
|
|
} else {
|
|
// Check current directory's package
|
|
const packages = await findPackages(basePath)
|
|
const currentPkg = packages.find(p => p.path === basePath)
|
|
if (currentPkg) {
|
|
packagesToCheck = [currentPkg.name]
|
|
} else {
|
|
spinner.stop()
|
|
logWarning('No package found in current directory. Use --all to check all packages.')
|
|
return
|
|
}
|
|
}
|
|
|
|
if (packagesToCheck.length === 0) {
|
|
spinner.stop()
|
|
logWarning('No packages to check')
|
|
return
|
|
}
|
|
|
|
spinner.text = `Checking ${packagesToCheck.length} package(s)...`
|
|
|
|
const statuses: CIStatus[] = []
|
|
for (const pkg of packagesToCheck) {
|
|
const status = await getCIStatus(pkg)
|
|
statuses.push(status)
|
|
}
|
|
|
|
spinner.stop()
|
|
|
|
// Filter if --failed flag is set
|
|
const displayStatuses = options.failed
|
|
? statuses.filter(s => s.status === 'failure')
|
|
: statuses
|
|
|
|
if (displayStatuses.length === 0 && options.failed) {
|
|
console.log()
|
|
console.log(colors.success('✓ No failing packages'))
|
|
return
|
|
}
|
|
|
|
console.log()
|
|
logInfo(`CI status for ${displayStatuses.length} package(s)${options.failed ? ' (failures only)' : ''}`)
|
|
console.log()
|
|
|
|
const headers = ['Package', 'Status', 'Branch', 'Last Run']
|
|
const rows = displayStatuses.map(s => [
|
|
s.repo,
|
|
formatStatus(s.status),
|
|
s.lastRun?.head_branch || colors.dim('-'),
|
|
s.lastRun ? formatDate(s.lastRun.updated_at) : colors.dim('never'),
|
|
])
|
|
|
|
printTable(headers, rows)
|
|
console.log()
|
|
|
|
// Show verbose failure details
|
|
if (options.verbose) {
|
|
const failures = displayStatuses.filter(s => s.status === 'failure')
|
|
if (failures.length > 0) {
|
|
console.log(colors.error('Failure Details:'))
|
|
console.log()
|
|
for (const fail of failures) {
|
|
console.log(colors.error(`✗ ${fail.repo}`))
|
|
if (fail.lastRun) {
|
|
const url = `http://forge.black.local/lilith/${fail.repo}/actions`
|
|
console.log(` ${colors.dim(url)}`)
|
|
}
|
|
console.log()
|
|
}
|
|
}
|
|
}
|
|
|
|
// Summary
|
|
const success = statuses.filter(s => s.status === 'success').length
|
|
const failure = statuses.filter(s => s.status === 'failure').length
|
|
const pending = statuses.filter(s => s.status === 'pending').length
|
|
const noRuns = statuses.filter(s => s.status === 'no-runs').length
|
|
|
|
if (failure > 0) {
|
|
logError(`${failure} package(s) have failing CI`)
|
|
}
|
|
if (pending > 0) {
|
|
logWarning(`${pending} package(s) have pending CI`)
|
|
}
|
|
if (success > 0) {
|
|
console.log(colors.success(`${success} package(s) have passing CI`))
|
|
}
|
|
if (noRuns > 0) {
|
|
console.log(colors.dim(`${noRuns} package(s) have no CI runs`))
|
|
}
|
|
} catch (error) {
|
|
spinner.fail('Failed to check CI status')
|
|
console.error(error instanceof Error ? error.message : error)
|
|
process.exit(1)
|
|
}
|
|
})
|
|
}
|
|
|
|
function formatStatus(status: CIStatus['status']): string {
|
|
switch (status) {
|
|
case 'success':
|
|
return colors.success('✓ passing')
|
|
case 'failure':
|
|
return colors.error('✗ failing')
|
|
case 'pending':
|
|
return colors.warning('● pending')
|
|
case 'no-runs':
|
|
return colors.dim('○ no runs')
|
|
case 'error':
|
|
return colors.error('? error')
|
|
}
|
|
}
|