import { Command } from 'commander' import { loadConfig } from '../lib/config' import * as versionsLib from '../lib/versions' // Interface for version information interface VersionInfo { tag: string digest: string created: string } // Command to list available versions for an OCI image export async function cmdListVersions(options: { imageName?: string }) { try { console.log('Fetching available versions...\n') const config = loadConfig() const imageNames = Object.keys(config) // If specific image name is provided, only check that one if (options.imageName) { if (!config[options.imageName]) { console.error(`Image "${options.imageName}" not found in configuration`) return 1 } const imageConfig = config[options.imageName] await displayVersionsForImage(options.imageName, imageConfig.tag) return 0 } // Otherwise, check all configured images if (imageNames.length === 0) { console.log('No images found in configuration') return 1 } // Process each image for (const imageName of imageNames) { const imageConfig = config[imageName] console.log(`📦 Image: ${imageName}`) console.log(`✓ Current version: ${imageConfig.tag}`) // Get available versions - we'll use Docker Hub API to simulate this try { const versions = await getAvailableVersions(imageName) // Simple ASCII table for versions console.log('┌────────┬────────┬───────────────┬─────────┐') console.log('│ Tag │ Digest │ Created │ Current │') console.log('├────────┼────────┼───────────────┼─────────┤') versions.forEach((v: VersionInfo) => { const isCurrent = v.tag === imageConfig.tag const currentMark = isCurrent ? ' ✓ ' : ' ' // Format digest to be short const shortDigest = v.digest.substring(0, 8) // Format created date to be short const createdDate = v.created.substring(0, 15) console.log( `│ ${padRight(v.tag, 6)} │ ${padRight(shortDigest, 6)} │ ${padRight(createdDate, 13)} │${currentMark}│` ) }) console.log('└────────┴────────┴───────────────┴─────────┘') } catch (error) { console.error(`Could not fetch versions for ${imageName}: ${error}`) } console.log('') } return 0 } catch (error) { console.error(`Error listing versions: ${error}`) return 1 } } // Command to check for available updates export async function cmdCheckUpdates() { try { console.log('Checking for updates...\n') const config = loadConfig() const imageNames = Object.keys(config) if (imageNames.length === 0) { console.log('No images found in configuration') return 1 } let updatesAvailable = false for (const imageName of imageNames) { const imageConfig = config[imageName] console.log(`📦 Image: ${imageName}`) console.log(`Current version: ${imageConfig.tag}`) try { // Get the highest matching tag for each semver level const suggestions = { patch: await versionsLib.findHighestMatchingTag( imageName, imageConfig.tag ), minor: null, major: null, } if (suggestions.patch !== imageConfig.tag) { updatesAvailable = true console.log('🔄 Updates available:') console.log(` Latest: ${suggestions.patch} (recommended)`) } else { console.log('✅ Up to date! No updates available') } } catch (error) { console.error(`Could not check updates for ${imageName}: ${error}`) } console.log('') } if (!updatesAvailable) { console.log('✅ All images are up to date!') } return 0 } catch (error) { console.error(`Error checking for updates: ${error}`) return 1 } } // Helper function to display versions for a single image async function displayVersionsForImage(imageName: string, currentTag: string) { console.log(`📦 Image: ${imageName}`) console.log(`Current version: ${currentTag}`) const versions = await getAvailableVersions(imageName) // Display versions in a nice table console.log('Available versions:') console.log('┌────────┬────────┬───────────────┬─────────┐') console.log('│ Tag │ Digest │ Created │ Current │') console.log('├────────┼────────┼───────────────┼─────────┤') versions.forEach((v: VersionInfo) => { const isCurrent = v.tag === currentTag const currentMark = isCurrent ? ' ✓ ' : ' ' const shortDigest = v.digest.substring(0, 8) const createdDate = v.created.substring(0, 15) console.log( `│ ${padRight(v.tag, 6)} │ ${padRight(shortDigest, 6)} │ ${padRight(createdDate, 13)} │${currentMark}│` ) }) console.log('└────────┴────────┴───────────────┴─────────┘') } // Helper function to get available versions using the new API async function getAvailableVersions(imageName: string): Promise { try { // This is a simplified approach to get versions // In a real implementation, you would query a registry API // For now, we'll use Docker Hub API and get a few recent tags const tags = ['latest', '1.0.0', '1.1.0', '2.0.0'] const versions: VersionInfo[] = [] for (const tag of tags) { try { const details = await versionsLib.getVersionDetails(imageName, tag) versions.push({ tag: details.tag, digest: details.digest, created: details.created, }) } catch (error) { console.log(`Could not get details for ${imageName}:${tag}`) } } return versions } catch (error) { console.error(`Error getting versions: ${error}`) return [] } } // Helper function to pad a string to a certain length function padRight(str: string, length: number): string { if (str.length >= length) { return str.substring(0, length) } return str + ' '.repeat(length - str.length) } // Function to configure the list-versions command export function configureListVersionsCommand(command: Command): Command { return command.option( '--image-name ', 'Name of a specific image to list versions for' ) } // Function to configure the check-updates command export function configureCheckUpdatesCommand(command: Command): Command { return command }