feat: change wand file via arg or env var

This commit is contained in:
2026-03-31 01:13:18 +03:00
parent a1a50cdc86
commit 8200f68a89
5 changed files with 140 additions and 17 deletions

View File

@@ -114,6 +114,18 @@ wand test --help
The first config file found is used.
You can override config discovery with an explicit path:
```bash
# via flag
wand --wand-file ./other-config.yml build
# via environment variable
WAND_FILE=./other-config.yml wand build
```
The `--wand-file` flag takes precedence over `WAND_FILE`.
---
## 📖 Config Reference

View File

@@ -87,10 +87,16 @@ type rawConfig struct {
Commands map[string]Command `yaml:",inline"`
}
func loadConfig() (*Config, map[string]Command, error) {
configPath, err := findConfigFile()
if err != nil {
return nil, nil, err
func loadConfig(explicitPath string) (*Config, map[string]Command, error) {
var configPath string
var err error
if explicitPath != "" {
configPath = explicitPath
} else {
configPath, err = findConfigFile()
if err != nil {
return nil, nil, err
}
}
data, err := os.ReadFile(configPath)

View File

@@ -115,7 +115,7 @@ build:
cmd: go build
`)
cfg, commands, err := loadConfig()
cfg, commands, err := loadConfig("")
if err != nil {
t.Fatal(err)
}
@@ -154,7 +154,7 @@ parent:
cmd: echo grandchild
`)
_, commands, err := loadConfig()
_, commands, err := loadConfig("")
if err != nil {
t.Fatal(err)
}
@@ -185,7 +185,7 @@ main:
cmd: echo test
`)
cfg, commands, err := loadConfig()
cfg, commands, err := loadConfig("")
if err != nil {
t.Fatal(err)
}
@@ -212,7 +212,7 @@ main:
cmd: echo test
`)
cfg, _, err := loadConfig()
cfg, _, err := loadConfig("")
if err != nil {
t.Fatal(err)
}
@@ -239,7 +239,7 @@ main:
cmd: echo test
`)
cfg, _, err := loadConfig()
cfg, _, err := loadConfig("")
if err != nil {
t.Fatal(err)
}
@@ -258,7 +258,7 @@ main:
MY_VAR: hello
`)
_, commands, err := loadConfig()
_, commands, err := loadConfig("")
if err != nil {
t.Fatal(err)
}
@@ -329,7 +329,7 @@ deploy:
confirm: "Deploy to production?"
`)
_, commands, err := loadConfig()
_, commands, err := loadConfig("")
if err != nil {
t.Fatal(err)
}
@@ -348,7 +348,7 @@ deploy:
confirm: true
`)
_, commands, err := loadConfig()
_, commands, err := loadConfig("")
if err != nil {
t.Fatal(err)
}
@@ -367,7 +367,7 @@ build:
aliases: [b, compile]
`)
_, commands, err := loadConfig()
_, commands, err := loadConfig("")
if err != nil {
t.Fatal(err)
}
@@ -386,7 +386,7 @@ main:
working_dir: /tmp
`)
_, commands, err := loadConfig()
_, commands, err := loadConfig("")
if err != nil {
t.Fatal(err)
}
@@ -396,6 +396,28 @@ main:
}
}
func TestLoadConfig_ExplicitPath(t *testing.T) {
dir := t.TempDir()
configPath := filepath.Join(dir, "custom.yml")
err := os.WriteFile(configPath, []byte(`
main:
description: from explicit
cmd: echo explicit
`), 0644)
if err != nil {
t.Fatal(err)
}
_, commands, err := loadConfig(configPath)
if err != nil {
t.Fatal(err)
}
if commands["main"].Description != "from explicit" {
t.Errorf("description = %q, want 'from explicit'", commands["main"].Description)
}
}
func TestLoadConfig_NoConfigFile(t *testing.T) {
dir := t.TempDir()
origDir, _ := os.Getwd()
@@ -408,7 +430,7 @@ func TestLoadConfig_NoConfigFile(t *testing.T) {
// Remove HOME to prevent finding a real ~/.wand.yml
t.Setenv("HOME", dir)
_, _, err := loadConfig()
_, _, err := loadConfig("")
if err == nil {
t.Error("expected error for missing config, got nil")
}
@@ -434,7 +456,7 @@ main:
viper.Reset()
}()
_, commands, err := loadConfig()
_, commands, err := loadConfig("")
if err != nil {
t.Fatal(err)
}

View File

@@ -1,12 +1,16 @@
package cmd
import (
"os"
"strings"
"github.com/samber/lo"
"github.com/spf13/cobra"
)
func Execute() error {
cfg, commands, err := loadConfig()
configFile := resolveConfigFile()
cfg, commands, err := loadConfig(configFile)
if err != nil {
return err
}
@@ -17,6 +21,8 @@ func Execute() error {
SilenceErrors: true,
}
rootCmd.PersistentFlags().String("wand-file", "", "path to wand config file (overrides discovery)")
if main, ok := commands["main"]; ok {
rootCmd.Short = main.Description
rootCmd.Args = cobra.ArbitraryArgs
@@ -62,6 +68,19 @@ func buildCobraCommand(cfg *Config, name string, cmd Command) *cobra.Command {
return c
}
// resolveConfigFile extracts --wand-file from os.Args or WAND_FILE env before cobra parses.
func resolveConfigFile() string {
for i, arg := range os.Args {
if arg == "--wand-file" && i+1 < len(os.Args) {
return os.Args[i+1]
}
if strings.HasPrefix(arg, "--wand-file=") {
return strings.TrimPrefix(arg, "--wand-file=")
}
}
return os.Getenv("WAND_FILE")
}
func registerFlags(c *cobra.Command, flags map[string]Flag) {
lo.ForEach(
lo.Entries(flags),

View File

@@ -1,6 +1,8 @@
package cmd
import (
"os"
"path/filepath"
"testing"
)
@@ -285,6 +287,68 @@ build:
}
}
func TestResolveConfigFile_Flag(t *testing.T) {
origArgs := setArgs("wand", "--wand-file", "/tmp/custom.yml", "build")
defer restoreArgs(origArgs)
got := resolveConfigFile()
if got != "/tmp/custom.yml" {
t.Errorf("resolveConfigFile() = %q, want /tmp/custom.yml", got)
}
}
func TestResolveConfigFile_FlagEquals(t *testing.T) {
origArgs := setArgs("wand", "--wand-file=/tmp/custom.yml", "build")
defer restoreArgs(origArgs)
got := resolveConfigFile()
if got != "/tmp/custom.yml" {
t.Errorf("resolveConfigFile() = %q, want /tmp/custom.yml", got)
}
}
func TestResolveConfigFile_EnvVar(t *testing.T) {
origArgs := setArgs("wand", "build")
defer restoreArgs(origArgs)
t.Setenv("WAND_FILE", "/tmp/env.yml")
got := resolveConfigFile()
if got != "/tmp/env.yml" {
t.Errorf("resolveConfigFile() = %q, want /tmp/env.yml", got)
}
}
func TestResolveConfigFile_FlagOverridesEnv(t *testing.T) {
origArgs := setArgs("wand", "--wand-file", "/tmp/flag.yml")
defer restoreArgs(origArgs)
t.Setenv("WAND_FILE", "/tmp/env.yml")
got := resolveConfigFile()
if got != "/tmp/flag.yml" {
t.Errorf("resolveConfigFile() = %q, want /tmp/flag.yml (flag should override env)", got)
}
}
func TestExecute_WithWandFileFlag(t *testing.T) {
dir := t.TempDir()
configPath := filepath.Join(dir, "custom.yml")
_ = os.WriteFile(configPath, []byte(`
main:
description: custom
cmd: echo custom
`), 0644)
origArgs := setArgs("wand", "--wand-file", configPath)
defer restoreArgs(origArgs)
err := Execute()
if err != nil {
t.Fatalf("Execute() failed: %v", err)
}
}
func TestExecute_NoMain(t *testing.T) {
setupTestConfig(t, `
build: