Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 8 additions & 1 deletion internal/devconfig/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -141,8 +141,15 @@ func Find(path string) (*Config, error) {

// searchDir looks for a config file in dir. It does not search parent
// directories.
//
// It first looks for devbox.json directly in dir, and then falls back to
// dir/.config/devbox.json. The latter lets projects keep their root directory
// tidy by storing the config under .config. See jetify-com/devbox#2792.
func searchDir(dir string) (*Config, error) {
try := []string{configfile.DefaultName}
try := []string{
configfile.DefaultName,
filepath.Join(configfile.ConfigSubdir, configfile.DefaultName),
}
for _, name := range try {
path := filepath.Join(dir, name)
slog.Debug("trying config file", "path", path)
Expand Down
70 changes: 70 additions & 0 deletions internal/devconfig/config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,63 @@ func TestOpenError(t *testing.T) {
})
}

// TestConfigSubdir verifies that Devbox discovers a config file stored in a
// .config subdirectory, so that projects can keep devbox.json out of their
// root directory. See jetify-com/devbox#2792.
func TestConfigSubdir(t *testing.T) {
t.Run("Open", func(t *testing.T) {
root, _, _ := mkNestedDirs(t)
configDir := mkConfigSubdir(t, root)
if _, err := Init(configDir); err != nil {
t.Fatalf("Init(%q) error: %v", configDir, err)
}

cfg, err := Open(root)
if err != nil {
t.Fatalf("Open(%q) error: %v", root, err)
}
gotDir := filepath.Dir(cfg.Root.AbsRootPath)
if gotDir != configDir {
t.Errorf("filepath.Dir(cfg.Root.AbsRootPath) = %q, want %q", gotDir, configDir)
}
})
t.Run("RootConfigTakesPrecedence", func(t *testing.T) {
root, _, _ := mkNestedDirs(t)
if _, err := Init(root); err != nil {
t.Fatalf("Init(%q) error: %v", root, err)
}
configDir := mkConfigSubdir(t, root)
if _, err := Init(configDir); err != nil {
t.Fatalf("Init(%q) error: %v", configDir, err)
}

cfg, err := Open(root)
if err != nil {
t.Fatalf("Open(%q) error: %v", root, err)
}
gotDir := filepath.Dir(cfg.Root.AbsRootPath)
if gotDir != root {
t.Errorf("filepath.Dir(cfg.Root.AbsRootPath) = %q, want %q", gotDir, root)
}
})
t.Run("FindFromChildDir", func(t *testing.T) {
root, child, _ := mkNestedDirs(t)
configDir := mkConfigSubdir(t, root)
if _, err := Init(configDir); err != nil {
t.Fatalf("Init(%q) error: %v", configDir, err)
}

cfg, err := Find(child)
if err != nil {
t.Fatalf("Find(%q) error: %v", child, err)
}
gotDir := filepath.Dir(cfg.Root.AbsRootPath)
if gotDir != configDir {
t.Errorf("filepath.Dir(cfg.Root.AbsRootPath) = %q, want %q", gotDir, configDir)
}
})
}

func TestFind(t *testing.T) {
t.Run("StartInSameDir", func(t *testing.T) {
root, child, _ := mkNestedDirs(t)
Expand Down Expand Up @@ -297,6 +354,19 @@ func mkNestedDirs(t *testing.T) (root, child, nested string) {
return root, child, nested
}

// mkConfigSubdir creates a .config subdirectory inside dir and returns its
// path, for tests that store devbox.json under .config.
func mkConfigSubdir(t *testing.T, dir string) string {
t.Helper()

configDir := filepath.Join(dir, configfile.ConfigSubdir)
perm := fs.FileMode(0o777)
if err := os.MkdirAll(configDir, perm); err != nil {
t.Fatalf("os.MkdirAll(%q, %O) error: %v", configDir, perm, err)
}
return configDir
}

func TestAliases(t *testing.T) {
dir := t.TempDir()
cfgJSON := `{
Expand Down
6 changes: 6 additions & 0 deletions internal/devconfig/configfile/file.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,12 @@ import (

const (
DefaultName = "devbox.json"

// ConfigSubdir is a subdirectory that Devbox also searches for a config
// file. It lets projects keep their root directory tidy by placing
// devbox.json inside a .config directory (e.g. .config/devbox.json)
// instead of at the top level. See jetify-com/devbox#2792.
ConfigSubdir = ".config"
)

// ConfigFile defines a devbox environment as JSON.
Expand Down
Loading