Per-project configuration overwrites

Signed-off-by: Hadi <112569860+anotherhadi@users.noreply.github.com>
This commit is contained in:
Hadi
2026-05-30 00:14:54 +02:00
parent 77b102791f
commit 9d0ef598a7
5 changed files with 97 additions and 14 deletions
+13
View File
@@ -23,6 +23,7 @@
- [Installation](#installation)
- [Project Management](#project-management)
- [Configuration](#configuration)
- [Per-project configuration](#per-project-configuration)
- [CLI Flags](#cli-flags)
- [Plugin System](#plugin-system)
- [Vim / Neovim Integration](#vim--neovim-integration)
@@ -118,6 +119,18 @@ Check the default configuration with all the options [here](./internal/config/de
Colors and styles can be customized using [ilovetui](https://github.com/anotherhadi/ilovetui), which applies theme changes across all compatible TUI applications at once.
### Per-project configuration
You can override any config value on a per-project basis by placing a `config.yaml` file inside the project directory (e.g. `~/.local/share/spilltea/projects/my-project/config.yaml`).
Only the keys present in that file are overridden; everything else falls back to the global config.
The priority order is:
1. Global config (`~/.config/spilltea/config.yaml`)
2. Project config (`<project-dir>/config.yaml`)
3. CLI flags (always win)
## CLI Flags
```
+16 -6
View File
@@ -99,21 +99,26 @@ func main() {
}
config.Global.Version = version
sslInsecureChanged := cmd.Flags().Changed("ssl-insecure")
applyCLI := func(c *config.Config) {
if flagPluginsDir != "" {
config.Global.App.PluginsDir = flagPluginsDir
c.App.PluginsDir = flagPluginsDir
}
if flagHost != "" {
config.Global.App.Host = flagHost
c.App.Host = flagHost
}
if flagPort != 0 {
config.Global.App.Port = flagPort
c.App.Port = flagPort
}
if flagUpstreamProxy != "" {
config.Global.App.UpstreamProxy = flagUpstreamProxy
c.App.UpstreamProxy = flagUpstreamProxy
}
if cmd.Flags().Changed("ssl-insecure") {
config.Global.App.SslInsecure = flagSslInsecure
if sslInsecureChanged {
c.App.SslInsecure = flagSslInsecure
}
}
applyCLI(config.Global)
config.SetCLIOverrides(applyCLI)
style.Init()
icons.Init(config.Global)
@@ -126,6 +131,11 @@ func main() {
if err != nil {
return fmt.Errorf("project: %w", err)
}
if err := config.MergeProjectConfig(filepath.Dir(project.Path)); err != nil {
return fmt.Errorf("project config: %w", err)
}
icons.Init(config.Global)
keys.Init(config.Global)
broker := intercept.NewBroker()
m := appUI.New(broker, project.Name, project.Path)
final, err := tea.NewProgram(m).Run()
+11
View File
@@ -1,8 +1,14 @@
package main
import (
"log"
"path/filepath"
tea "charm.land/bubbletea/v2"
"github.com/anotherhadi/spilltea/internal/config"
"github.com/anotherhadi/spilltea/internal/icons"
"github.com/anotherhadi/spilltea/internal/intercept"
"github.com/anotherhadi/spilltea/internal/keys"
appUI "github.com/anotherhadi/spilltea/internal/ui/app"
homeUI "github.com/anotherhadi/spilltea/internal/ui/home"
)
@@ -34,6 +40,11 @@ func (m rootModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
if m.state == rootStateHome {
if sel, ok := msg.(homeUI.ProjectSelectedMsg); ok {
if err := config.MergeProjectConfig(filepath.Dir(sel.Project.Path)); err != nil {
log.Printf("project config: %v", err)
}
icons.Init(config.Global)
keys.Init(config.Global)
broker := intercept.NewBroker()
app := appUI.New(broker, sel.Project.Name, sel.Project.Path)
m.app = app
+12
View File
@@ -15,6 +15,18 @@ Check the default configuration with all the options [here](./internal/config/de
Colors and styles can be customized using [ilovetui](https://github.com/anotherhadi/ilovetui), which applies theme changes across all compatible TUI applications at once.
### Per-project configuration
You can override any config value on a per-project basis by placing a `config.yaml` file inside the project directory (e.g. `~/.local/share/spilltea/projects/my-project/config.yaml`).
Only the keys present in that file are overridden; everything else falls back to the global config.
The priority order is:
1. Global config (`~/.config/spilltea/config.yaml`)
2. Project config (`<project-dir>/config.yaml`)
3. CLI flags (always win)
## CLI Flags
<!-- exec: echo '```' && go run ./cmd/spilltea -h && echo '```' -->
+37
View File
@@ -65,6 +65,43 @@ type GlobalPlugin struct {
var Global *Config
var projectCLIOverrides func(*Config)
// SetCLIOverrides stores a function that re-applies CLI flag overrides.
// It is called automatically after MergeProjectConfig so that CLI flags
// always win over both global and per-project config.
func SetCLIOverrides(fn func(*Config)) {
projectCLIOverrides = fn
}
// MergeProjectConfig merges <projectDir>/config.yaml on top of the current
// Global config, then re-applies any registered CLI overrides.
// If no config.yaml exists in the project directory, this is a no-op.
func MergeProjectConfig(projectDir string) error {
projectConfigPath := filepath.Join(projectDir, "config.yaml")
if _, err := os.Stat(projectConfigPath); errors.Is(err, os.ErrNotExist) {
return nil
}
pv := viper.New()
pv.SetConfigFile(projectConfigPath)
if err := pv.ReadInConfig(); err != nil {
return fmt.Errorf("project config: %w", err)
}
if err := viper.MergeConfigMap(pv.AllSettings()); err != nil {
return fmt.Errorf("merge project config: %w", err)
}
if err := viper.Unmarshal(Global); err != nil {
return err
}
Global.App.ProxyAuth = expandEnvValue(Global.App.ProxyAuth)
Global.App.UpstreamProxy = expandEnvValue(Global.App.UpstreamProxy)
if projectCLIOverrides != nil {
projectCLIOverrides(Global)
}
return nil
}
func Load(path string) error {
var defaults map[string]any
if err := yaml.Unmarshal(defaultConfig, &defaults); err != nil {