fix: пути (db, worktree) и config.yaml резолвятся рядом с .exe, а не от CWD
All checks were successful
CI / test (push) Successful in 1m5s
CI / build-and-package (amd64, linux) (push) Successful in 44s
CI / build-and-package (amd64, windows) (push) Successful in 43s

This commit is contained in:
Hermes
2026-08-16 20:31:08 +05:00
parent 91e91ac3ce
commit 130dc59840
3 changed files with 95 additions and 2 deletions

View File

@@ -3,6 +3,7 @@ package config
import (
"fmt"
"os"
"path/filepath"
"reflect"
"strings"
@@ -11,11 +12,13 @@ import (
// Load читает конфиг из yaml-файла, подставляет ${VAR:-default}, накладывает
// env-переменные (тег env) и валидирует. Если path пустой — ищет config.yaml
// в CWD. Ошибка C4 если файл не существует (но не fatal при path="").
// сначала в каталоге бинаря (exeDir), затем в CWD (fallback). Ошибка C4 если
// файл не существует (но не fatal при path="").
func Load(path string) (*Config, error) {
noFile := false
if path == "" {
path = "config.yaml"
// приоритет — рядом с .exe, фоллбэк на CWD
path = firstExistingPath(filepath.Join(ExeDir(), "config.yaml"), "config.yaml")
}
raw, err := os.ReadFile(path)
if err != nil {
@@ -47,6 +50,40 @@ func Load(path string) (*Config, error) {
return cfg, nil
}
// ExeDir возвращает каталог исполняемого файла (бинаря). Используется для
// поиска config.yaml и резолва относительных путей "рядом с .exe".
func ExeDir() string {
exe, err := os.Executable()
if err != nil {
return "."
}
return filepath.Dir(exe)
}
// firstExistingPath возвращает первый существующий путь из списка.
func firstExistingPath(paths ...string) string {
for _, p := range paths {
if _, err := os.Stat(p); err == nil {
return p
}
}
return paths[len(paths)-1] // фоллбэк на последний
}
// ResolveExePaths подставляет относительные пути ПУТЕЙ (db, worktree) к каталогу
// бинаря, чтобы они были стабильны независимо от CWD запуска. Абсолютные пути
// не трогает. Вызывается после Load (в app.New), а не внутри Load, чтобы
// персист-тесты дефолтов оставались независимыми от окружения.
func (c *Config) ResolveExePaths() {
base := ExeDir()
if c.Paths.DB != "" && !filepath.IsAbs(c.Paths.DB) {
c.Paths.DB = filepath.Join(base, c.Paths.DB)
}
if c.Paths.Worktree != "" && !filepath.IsAbs(c.Paths.Worktree) {
c.Paths.Worktree = filepath.Join(base, c.Paths.Worktree)
}
}
// envLookup обрабатывает ${VAR:-default} в YAML.
func envLookup(key string) string {
parts := strings.SplitN(key, ":-", 2)