Add the Cobra-based command surface and the flag/env/config-file resolution layer that all commands share. Configuration (internal/config): - Load() resolves settings with the documented precedence flags > env > config file > defaults, via viper: GITFLOW_-prefixed env vars with dot-to-underscore mapping, plus ~/.gitflow.yaml (or $GITFLOW_CONFIG) - RegisterFlags/NewFlagSet own the flag definitions so every command and the tests share a single source of truth - Config/Validate/Dump cover dir, interval, format (table/json/compact), exclude globs, max depth, worker count, and the AI block (enabled, provider, model, api_key_env, base_url); ConfigFile records the loaded path; Dump renders the effective config as human-readable YAML with the interval as a duration string App orchestration (internal/app): - New() validates the configuration at the boundary (fail fast) - ScanOnce() runs discovery then a concurrent status scan, warning on stderr and continuing when discovery is only partially successful (e.g. permission-denied subtrees), and bundles everything into a ScanResult CLI (cmd/gitflow): - root command with scan / config / version subcommands - scan: resolves config, prompts for the parent directory when stdin is a TTY and --dir was not given (per the README), runs a single pass, and renders the result — interim plain/JSON output until phase 3 lands the presenter package - config: prints the effective configuration - version: prints the build version (ldflags-injectable) - signalContext() wires SIGINT/SIGTERM into a cancellable context for graceful shutdown Testing: - config: defaults, flag overrides, env overrides, flag-beats-env precedence, config file loading (including duration and slice values), GITFLOW_CONFIG path override, validation failures, and Dump output - app: config validation on New, end-to-end ScanOnce over a real temp repo, and missing-directory errors Verified: go build, go vet, go test -race, gofmt clean; manual smoke of `gitflow version`, `gitflow config`, and `gitflow scan -d <dir>` against a scratch directory with a dirty repo.
65 lines
1.7 KiB
Go
65 lines
1.7 KiB
Go
// Package app wires discovery, status scanning, and presentation into the
|
|
// runnable operations used by the CLI commands.
|
|
package app
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"os"
|
|
"time"
|
|
|
|
"gitea.oblak.solutions/dimitar/gitFlow/internal/config"
|
|
"gitea.oblak.solutions/dimitar/gitFlow/internal/scanner"
|
|
"gitea.oblak.solutions/dimitar/gitFlow/pkg/status"
|
|
)
|
|
|
|
// App coordinates the components built from the resolved configuration.
|
|
type App struct {
|
|
cfg *config.Config
|
|
discoverer *scanner.Discoverer
|
|
scanner *scanner.Scanner
|
|
warn func(format string, args ...any)
|
|
}
|
|
|
|
// New builds an App from a validated configuration.
|
|
func New(cfg *config.Config) (*App, error) {
|
|
if err := cfg.Validate(); err != nil {
|
|
return nil, err
|
|
}
|
|
return &App{
|
|
cfg: cfg,
|
|
discoverer: scanner.NewDiscoverer(
|
|
scanner.WithExclude(cfg.Exclude...),
|
|
scanner.WithMaxDepth(cfg.MaxDepth),
|
|
),
|
|
scanner: scanner.NewScanner(scanner.WithWorkers(cfg.Workers)),
|
|
warn: func(format string, args ...any) {
|
|
fmt.Fprintf(os.Stderr, "warning: "+format+"\n", args...)
|
|
},
|
|
}, nil
|
|
}
|
|
|
|
// ScanOnce discovers repositories under the configured directory and
|
|
// snapshots their status in a single pass.
|
|
func (a *App) ScanOnce(ctx context.Context) (status.ScanResult, error) {
|
|
repos, err := a.discoverer.Discover(ctx, a.cfg.Dir)
|
|
if err != nil {
|
|
if len(repos) == 0 {
|
|
return status.ScanResult{}, err
|
|
}
|
|
// Partial discovery (e.g. permission denied on some subtree):
|
|
// warn and continue with what was found.
|
|
a.warn("%v", err)
|
|
}
|
|
|
|
infos, err := a.scanner.Scan(ctx, repos)
|
|
if err != nil {
|
|
return status.ScanResult{}, err
|
|
}
|
|
return status.ScanResult{
|
|
ScannedAt: time.Now(),
|
|
ParentDir: a.cfg.Dir,
|
|
Repos: infos,
|
|
}, nil
|
|
}
|