patina

package module
v0.0.0-...-31152ed Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Dec 5, 2025 License: MIT Imports: 12 Imported by: 0

README

patina

A CLI tool to scan GitHub organizations and assess repository freshness.

Overview

patina helps you find repositories that haven't been updated recently. It categorizes repositories by freshness:

  • 🟢 Green: Updated within last 2 months (active)
  • 🟡 Yellow: Updated between 2-6 months ago (aging)
  • 🔴 Red: Not updated in over 6 months (stale)

Installation

Prerequisites
  • Go 1.21 or later
  • GitHub CLI (gh) - for authentication (unless using GITHUB_TOKEN)
  • Task (optional, for development)
From Source
go install github.com/scottbrown/patina/cmd/patina@latest
Build Locally
git clone https://github.com/scottbrown/patina.git
cd patina
task build
# or: go build -o patina ./cmd/patina

Authentication

patina supports two authentication methods:

Set the GITHUB_TOKEN environment variable with a personal access token:

export GITHUB_TOKEN=ghp_xxxxxxxxxxxxxxxxxxxx
patina scan my-org

The token requires the repo scope to access private repositories, or public_repo for public repositories only.

Option 2: GitHub CLI

If GITHUB_TOKEN is not set, patina falls back to using the GitHub CLI for authentication:

gh auth login
patina scan my-org

This provides access to both public and private repositories in your organizations.

Usage

Scan Command

Scan an organization and display a freshness summary with the top 10 most stale repositories:

patina scan <organization>

Example output:

Scanning organization: my-org

Repository Freshness Summary
============================

Total repositories: 42

🟢 Green  (≤2 months):  25
🟡 Yellow (2-6 months): 10
🔴 Red    (>6 months):  7

Top 10 Most Stale Repositories
==============================

 1. 🔴 legacy-api          2 years, 3 months ago
 2. 🔴 old-frontend        1 year, 8 months ago
 3. 🔴 deprecated-utils    1 year, 2 months ago
...
List Command

List all repositories with their age and freshness indicator:

patina list <organization>

Filter by freshness status:

patina list <organization> --freshness red      # Show only stale repos
patina list <organization> --freshness yellow   # Show only aging repos
patina list <organization> --freshness green    # Show only active repos
Report Command

Generate a standalone HTML report with visual charts and a complete repository table:

patina report <organization>
patina report <organization> -o my-report.html

The report includes:

  • Summary cards with colour-coded counts
  • Pie chart showing freshness distribution
  • Sortable table of all repositories with links
Options

All commands support:

  • -r, --refresh: Force refresh from GitHub API (bypass cache)

The list command additionally supports:

  • -f, --freshness <colour>: Filter by freshness (green, yellow, red)

The report command additionally supports:

  • -o, --output <file>: Output file path (default: patina-report.html)

Caching

Repository data is cached locally for 30 days to speed up subsequent commands. The cache is stored in:

  • macOS: ~/Library/Caches/patina/
  • Linux: ~/.cache/patina/

Use the --refresh flag to force a fresh fetch from GitHub.

Development

Running Tests
task test           # Run all tests
task test:coverage  # Run tests with coverage report
Building
task build          # Build the binary
task install        # Install to GOPATH/bin
Other Tasks
task fmt            # Format code
task lint           # Run linter
task tidy           # Tidy go modules
task clean          # Clean build artifacts

Licence

MIT

Documentation

Index

Constants

This section is empty.

Variables

View Source
var (
	ErrCacheExpired  = errors.New("cache expired")
	ErrCacheNotFound = errors.New("cache not found")
)

Functions

func Age

func Age(lastUpdated time.Time, now time.Time) string

Age returns a human-readable age string.

func ColourReset

func ColourReset() string

Reset returns the ANSI reset code.

func SortByAge

func SortByAge(repos []Repository)

SortByAge sorts repositories by last update time, oldest first.

func SortByAgeDesc

func SortByAgeDesc(repos []Repository)

SortByAgeDesc sorts repositories by last update time, newest first.

Types

type Cache

type Cache struct {
	// contains filtered or unexported fields
}

Cache provides methods for storing and retrieving organization data.

func NewCache

func NewCache() (*Cache, error)

NewCache creates a new Cache instance with the default cache directory.

func NewCacheWithDir

func NewCacheWithDir(baseDir string) *Cache

NewCacheWithDir creates a Cache with a custom base directory (useful for testing).

func (*Cache) CacheDir

func (c *Cache) CacheDir() string

CacheDir returns the cache directory path.

func (*Cache) Clear

func (c *Cache) Clear(org string) error

Clear removes the cache file for an organization.

func (*Cache) ClearAll

func (c *Cache) ClearAll() error

ClearAll removes all cache files.

func (*Cache) IsValid

func (c *Cache) IsValid(org string) bool

IsValid checks if a valid (non-expired) cache exists for the organization.

func (*Cache) IsValidWithTime

func (c *Cache) IsValidWithTime(org string, now time.Time) bool

IsValidWithTime checks cache validity using a specific reference time.

func (*Cache) Load

func (c *Cache) Load(org string) (OrganizationCache, error)

Load retrieves organization repository data from the cache. Returns ErrCacheNotFound if no cache exists, or ErrCacheExpired if cache is stale.

func (*Cache) LoadWithTime

func (c *Cache) LoadWithTime(org string, now time.Time) (OrganizationCache, error)

LoadWithTime retrieves organization data using a specific reference time (for testing).

func (*Cache) Save

func (c *Cache) Save(data OrganizationCache) error

Save stores organization repository data to the cache.

type Freshness

type Freshness string

Freshness represents the staleness level of a repository.

const (
	FreshnessGreen  Freshness = "green"
	FreshnessYellow Freshness = "yellow"
	FreshnessRed    Freshness = "red"
)

func CalculateFreshness

func CalculateFreshness(lastUpdated time.Time, now time.Time) Freshness

CalculateFreshness determines the freshness level based on the last update time.

func ParseFreshness

func ParseFreshness(s string) (Freshness, bool)

ParseFreshness converts a string to a Freshness value.

func (Freshness) Colour

func (f Freshness) Colour() string

FreshnessColour returns the ANSI colour code for terminal output.

func (Freshness) Emoji

func (f Freshness) Emoji() string

Emoji returns the emoji indicator for the freshness level.

func (Freshness) String

func (f Freshness) String() string

String returns the string representation of freshness.

type FreshnessSummary

type FreshnessSummary struct {
	Green  int
	Yellow int
	Red    int
	Total  int
}

FreshnessSummary contains counts of repositories by freshness level.

func CalculateSummary

func CalculateSummary(repos []Repository, now time.Time) FreshnessSummary

CalculateSummary computes the freshness summary for a list of repositories.

type GitHubClient

type GitHubClient interface {
	FetchRepositories(org string) ([]Repository, error)
}

GitHubClient provides methods for fetching GitHub data.

func NewGitHubClient

func NewGitHubClient() GitHubClient

NewGitHubClient creates a new GitHub client. If GITHUB_TOKEN is set, uses direct API calls; otherwise falls back to gh CLI.

type OrganizationCache

type OrganizationCache struct {
	Organization string       `json:"organization"`
	FetchedAt    time.Time    `json:"fetched_at"`
	Repositories []Repository `json:"repositories"`
}

OrganizationCache holds cached repository data for an organization.

type Repository

type Repository struct {
	Name        string    `json:"name"`
	FullName    string    `json:"full_name"`
	LastUpdated time.Time `json:"last_updated"`
	HTMLURL     string    `json:"html_url"`
}

Repository represents a GitHub repository with its last update timestamp.

func FilterByFreshness

func FilterByFreshness(repos []Repository, freshness Freshness, now time.Time) []Repository

FilterByFreshness returns repositories matching the specified freshness level.

func GetTopStale

func GetTopStale(repos []Repository, n int) []Repository

GetTopStale returns the n oldest repositories.

type ScanOptions

type ScanOptions struct {
	Refresh bool // Force refresh even if cache is valid
}

ScanOptions configures the scan behaviour.

type ScanResult

type ScanResult struct {
	Organization string
	Repositories []Repository
	FetchedAt    time.Time
	FromCache    bool
}

ScanResult contains the results of scanning an organization.

type Scanner

type Scanner struct {
	// contains filtered or unexported fields
}

Scanner provides methods for scanning organizations.

func NewScanner

func NewScanner() (*Scanner, error)

NewScanner creates a new Scanner with the default GitHub client and cache.

func NewScannerWithDeps

func NewScannerWithDeps(client GitHubClient, cache *Cache) *Scanner

NewScannerWithDeps creates a Scanner with custom dependencies (useful for testing).

func (*Scanner) Scan

func (s *Scanner) Scan(org string, opts ScanOptions) (*ScanResult, error)

Scan retrieves repository data for an organization, using cache if available.

Directories

Path Synopsis
cmd
patina command

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL