110 lines
2.7 KiB
Go
110 lines
2.7 KiB
Go
package config
|
|
|
|
import (
|
|
"encoding/json"
|
|
"fmt"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
)
|
|
|
|
// ResolveHome returns the effective home for config/data paths.
|
|
// TASK_MASTER_HOME overrides the real user home (useful for demos/dev sandboxes).
|
|
func ResolveHome() (string, error) {
|
|
if h := strings.TrimSpace(os.Getenv(envHome)); h != "" {
|
|
return filepath.Clean(h), nil
|
|
}
|
|
home, err := os.UserHomeDir()
|
|
if err != nil {
|
|
return "", fmt.Errorf("resolve home directory: %w", err)
|
|
}
|
|
return home, nil
|
|
}
|
|
|
|
// LoadOrCreate loads the config from disk, creating defaults if missing.
|
|
// It also ensures DataDir and the active project directory exist.
|
|
func LoadOrCreate() (*Config, error) {
|
|
home, err := ResolveHome()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
cfgPath := Path(home)
|
|
cfgDir := filepath.Dir(cfgPath)
|
|
if err := os.MkdirAll(cfgDir, 0o755); err != nil {
|
|
return nil, fmt.Errorf("create config dir %s: %w", cfgDir, err)
|
|
}
|
|
|
|
cfg, err := readOrCreate(cfgPath, home)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if d := strings.TrimSpace(os.Getenv(envDataDir)); d != "" {
|
|
cfg.DataDir = filepath.Clean(d)
|
|
}
|
|
|
|
if err := ensureDataDirs(cfg); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return cfg, nil
|
|
}
|
|
|
|
func readOrCreate(cfgPath, home string) (*Config, error) {
|
|
data, err := os.ReadFile(cfgPath)
|
|
if err != nil {
|
|
if os.IsNotExist(err) {
|
|
cfg := Defaults(home)
|
|
if err := Save(&cfg); err != nil {
|
|
return nil, fmt.Errorf("write default config: %w", err)
|
|
}
|
|
return &cfg, nil
|
|
}
|
|
return nil, fmt.Errorf("read config %s: %w", cfgPath, err)
|
|
}
|
|
|
|
var cfg Config
|
|
if err := json.Unmarshal(data, &cfg); err != nil {
|
|
return nil, fmt.Errorf("parse config %s: %w", cfgPath, err)
|
|
}
|
|
cfg.ApplyDefaults(home)
|
|
return &cfg, nil
|
|
}
|
|
|
|
// Save writes the config to ~/.config/task-master/config.json
|
|
// (or under TASK_MASTER_HOME when set).
|
|
func Save(cfg *Config) error {
|
|
home, err := ResolveHome()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
cfgPath := Path(home)
|
|
if err := os.MkdirAll(filepath.Dir(cfgPath), 0o755); err != nil {
|
|
return fmt.Errorf("create config dir: %w", err)
|
|
}
|
|
|
|
data, err := json.MarshalIndent(cfg, "", " ")
|
|
if err != nil {
|
|
return fmt.Errorf("marshal config: %w", err)
|
|
}
|
|
data = append(data, '\n')
|
|
|
|
if err := os.WriteFile(cfgPath, data, 0o644); err != nil {
|
|
return fmt.Errorf("write config %s: %w", cfgPath, err)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func ensureDataDirs(cfg *Config) error {
|
|
if err := os.MkdirAll(cfg.DataDir, 0o755); err != nil {
|
|
return fmt.Errorf("create data dir %s: %w", cfg.DataDir, err)
|
|
}
|
|
projectDir := filepath.Join(cfg.DataDir, "projects", cfg.ActiveProject)
|
|
if err := os.MkdirAll(projectDir, 0o755); err != nil {
|
|
return fmt.Errorf("create project dir %s: %w", projectDir, err)
|
|
}
|
|
return nil
|
|
}
|