Scaffold GitManager multi-repo dashboard
Runnable skeleton per AGENT.md: Echo server (/, /help, /healthz, /api/repos), read-only repo scanner with in-memory index, the internal/git boundary, the <repo-list> web component with design tokens, and dev tooling (Dockerfile, docker-compose, air, .env.example). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,95 @@
|
||||
// Package config loads GitManager's runtime configuration from the environment
|
||||
// (and an optional .env file). See AGENT.md §1.5 — every setting is env-driven;
|
||||
// nothing is hardcoded.
|
||||
package config
|
||||
|
||||
import (
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/joho/godotenv"
|
||||
)
|
||||
|
||||
// Config is the typed application configuration.
|
||||
type Config struct {
|
||||
ListenAddr string // address the HTTP server binds to
|
||||
|
||||
RepoRoots []string // roots to scan for git repositories
|
||||
GitBin string // path to the git binary
|
||||
|
||||
ScanInterval time.Duration // scanner refresh interval
|
||||
ScanMaxDepth int // max discovery depth under each root
|
||||
ScanIgnore []string // directory names to skip during discovery
|
||||
ScanFetchEnabled bool // allow the scanner to run `git fetch`
|
||||
|
||||
Dev bool // readable console logging vs structured JSON
|
||||
LogFile string // optional file to also append logs to
|
||||
|
||||
GitHubToken string // optional forge token (AGENT.md §8)
|
||||
GitLabToken string // optional forge token (AGENT.md §8)
|
||||
}
|
||||
|
||||
// Load reads .env (if present) then the environment, applying defaults.
|
||||
// A missing .env is not an error — the environment may be set another way.
|
||||
func Load() (Config, error) {
|
||||
_ = godotenv.Load()
|
||||
|
||||
c := Config{
|
||||
ListenAddr: env("LISTEN_ADDR", "127.0.0.1:8080"),
|
||||
RepoRoots: splitList(env("GIT_REPO_ROOTS", "")),
|
||||
GitBin: env("GIT_BIN", "git"),
|
||||
ScanMaxDepth: envInt("SCAN_MAX_DEPTH", 4),
|
||||
ScanIgnore: splitList(env("SCAN_IGNORE", "node_modules,vendor,.cache")),
|
||||
ScanFetchEnabled: envBool("SCAN_FETCH_ENABLED", false),
|
||||
Dev: strings.EqualFold(env("APP_ENV", "dev"), "dev"),
|
||||
LogFile: env("LOG_FILE", ""),
|
||||
GitHubToken: env("GITHUB_TOKEN", ""),
|
||||
GitLabToken: env("GITLAB_TOKEN", ""),
|
||||
}
|
||||
|
||||
interval, err := time.ParseDuration(env("SCAN_INTERVAL", "30s"))
|
||||
if err != nil {
|
||||
return Config{}, err
|
||||
}
|
||||
c.ScanInterval = interval
|
||||
|
||||
return c, nil
|
||||
}
|
||||
|
||||
func env(key, def string) string {
|
||||
if v, ok := os.LookupEnv(key); ok && v != "" {
|
||||
return v
|
||||
}
|
||||
return def
|
||||
}
|
||||
|
||||
func envInt(key string, def int) int {
|
||||
if v, ok := os.LookupEnv(key); ok {
|
||||
if n, err := strconv.Atoi(strings.TrimSpace(v)); err == nil {
|
||||
return n
|
||||
}
|
||||
}
|
||||
return def
|
||||
}
|
||||
|
||||
func envBool(key string, def bool) bool {
|
||||
if v, ok := os.LookupEnv(key); ok {
|
||||
if b, err := strconv.ParseBool(strings.TrimSpace(v)); err == nil {
|
||||
return b
|
||||
}
|
||||
}
|
||||
return def
|
||||
}
|
||||
|
||||
// splitList splits a comma-separated value, trimming spaces and dropping empties.
|
||||
func splitList(v string) []string {
|
||||
var out []string
|
||||
for _, part := range strings.Split(v, ",") {
|
||||
if p := strings.TrimSpace(part); p != "" {
|
||||
out = append(out, p)
|
||||
}
|
||||
}
|
||||
return out
|
||||
}
|
||||
Reference in New Issue
Block a user