From 3d0416e5b6888ca5ef3a4236a247bc0e6783dbce Mon Sep 17 00:00:00 2001 From: Theo Beers Date: Mon, 12 Jan 2026 23:35:14 -0500 Subject: [PATCH 1/3] respect XDG_CONFIG_HOME if set Co-authored-by: Mufeed Ali --- cmd/root.go | 58 +++++++++++++++++++++++++++++++++++------------------ 1 file changed, 39 insertions(+), 19 deletions(-) diff --git a/cmd/root.go b/cmd/root.go index ec2abd5..64e16fc 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -4,7 +4,7 @@ import ( "context" "fmt" "os" - "path" + "path/filepath" "time" api "github.com/bootdotdev/bootdev/client" @@ -34,14 +34,13 @@ func Execute(currentVersion string) error { func init() { cobra.OnInitialize(initConfig) - rootCmd.PersistentFlags().StringVar(&cfgFile, "config", "", "config file (default $HOME/.bootdev.yaml)") + rootCmd.PersistentFlags().StringVar(&cfgFile, "config", "", "config file (default $HOME/.bootdev.yaml or $XDG_CONFIG_HOME/bootdev/config.yaml)") } func readViperConfig(paths []string) error { - for _, path := range paths { - _, err := os.Stat(path) - if err == nil { - viper.SetConfigFile(path) + for _, p := range paths { + if _, err := os.Stat(p); err == nil { + viper.SetConfigFile(p) break } } @@ -57,24 +56,45 @@ func initConfig() { viper.SetDefault("last_refresh", 0) if cfgFile != "" { // Use config file from the flag. - viper.SetConfigFile(cfgFile) - err := viper.ReadInConfig() - cobra.CheckErr(err) + viper.SetConfigFile(filepath.Clean(cfgFile)) + cobra.CheckErr(viper.ReadInConfig()) } else { - // Find home directory. + // find home dir home, err := os.UserHomeDir() cobra.CheckErr(err) - // viper's built in config path thing sucks, let's do it ourselves - defaultPath := path.Join(home, ".bootdev.yaml") - configPaths := []string{} - configPaths = append(configPaths, path.Join(home, ".config", "bootdev", "config.yaml")) - configPaths = append(configPaths, defaultPath) + // collect paths where existing config files may be located + var configPaths []string + + // first check XDG_CONFIG_HOME if set + xdgConfigHome := os.Getenv("XDG_CONFIG_HOME") + var xdgEnvPath string + if xdgConfigHome != "" { + xdgEnvPath = filepath.Join(xdgConfigHome, "bootdev", "config.yaml") + configPaths = append(configPaths, xdgEnvPath) + } + + // then check legacy hard-coded "XDG" path, then home dotfile + xdgLegacyPath := filepath.Join(home, ".config", "bootdev", "config.yaml") + homeDotfilePath := filepath.Join(home, ".bootdev.yaml") + + configPaths = append(configPaths, xdgLegacyPath) + configPaths = append(configPaths, homeDotfilePath) + if err := readViperConfig(configPaths); err != nil { - viper.SafeWriteConfigAs(defaultPath) - viper.SetConfigFile(defaultPath) - err = viper.ReadInConfig() - cobra.CheckErr(err) + // no existing config found; try to create a new one + // respect XDG_CONFIG_HOME if set, otherwise use dotfile in home dir + var newConfigPath string + if xdgEnvPath != "" { + newConfigPath = xdgEnvPath + cobra.CheckErr(os.MkdirAll(filepath.Dir(newConfigPath), 0o755)) + } else { + newConfigPath = homeDotfilePath + } + + cobra.CheckErr(viper.SafeWriteConfigAs(newConfigPath)) + viper.SetConfigFile(newConfigPath) + cobra.CheckErr(viper.ReadInConfig()) } } From 0688ebecdb4085e91585970a8768a664528e0cda Mon Sep 17 00:00:00 2001 From: Theo Beers Date: Mon, 12 Jan 2026 23:47:00 -0500 Subject: [PATCH 2/3] edit readme --- README.md | 54 +++++++++++++++++++++++++++--------------------------- 1 file changed, 27 insertions(+), 27 deletions(-) diff --git a/README.md b/README.md index 925b7bc..5cf345e 100644 --- a/README.md +++ b/README.md @@ -16,7 +16,7 @@ The Boot.dev CLI requires an up-to-date Golang installation, and only works on L **Option 1**: [The webi installer](https://webinstall.dev/golang/) is the simplest way for most people. Just run this in your terminal: -```bash +```sh curl -sS https://webi.sh/golang | sh ``` @@ -38,14 +38,14 @@ Run `go version` on your command line to make sure the installation worked. If i You can ensure it exists by attempting to run `go` using its full filepath. For example, if you think it's in `~/.local/opt/go/bin`, you can run `~/.local/opt/go/bin/go version`. If that works, then you just need to add `~/.local/opt/go/bin` to your `PATH` and reload your shell: -```bash +```sh # For Linux/WSL echo 'export PATH=$PATH:$HOME/.local/opt/go/bin' >> ~/.bashrc # next, reload your shell configuration source ~/.bashrc ``` -```bash +```sh # For Mac OS echo 'export PATH=$PATH:$HOME/.local/opt/go/bin' >> ~/.zshrc # next, reload your shell configuration @@ -56,7 +56,7 @@ source ~/.zshrc This command will download, build, and install the `bootdev` command into your Go toolchain's `bin` directory. Go ahead and run it: -```bash +```sh go install github.com/bootdotdev/bootdev@latest ``` @@ -66,14 +66,14 @@ Run `bootdev --version` on your command line to make sure the installation worke If you're getting a "command not found" error for `bootdev help`, it's most likely because the directory containing the `bootdev` program isn't in your [`PATH`](https://opensource.com/article/17/6/set-path-linux). You need to add the directory to your `PATH` by modifying your shell's configuration file. You probably need to add `$HOME/go/bin` (the default `GOBIN` directory where `go` installs programs) to your `PATH`: -```bash +```sh # For Linux/WSL echo 'export PATH=$PATH:$HOME/go/bin' >> ~/.bashrc # next, reload your shell configuration source ~/.bashrc ``` -```bash +```sh # For Mac OS echo 'export PATH=$PATH:$HOME/go/bin' >> ~/.zshrc # next, reload your shell configuration @@ -86,7 +86,7 @@ Run `bootdev login` to authenticate with your Boot.dev account. After authentica ## Configuration -The Boot.dev CLI offers a couple of configuration options that are stored in a config file (default is `~/.bootdev.yaml`). +The Boot.dev CLI offers a couple of configuration options that are stored in a config file (default is `~/.bootdev.yaml`, or `$XDG_CONFIG_HOME/bootdev/config.yaml` if `XDG_CONFIG_HOME` is set). All commands have `-h`/`--help` flags if you want to see available options on the command line. @@ -96,23 +96,23 @@ For lessons with HTTP tests, you can configure the CLI with a base URL that over - To set the base URL run: -```bash -bootdev config base_url -``` + ```sh + bootdev config base_url + ``` -_Make sure you include the protocol scheme (`http://`) in the URL._ + _Make sure you include the protocol scheme (`http://`) in the URL._ - To get the current base URL (the default is an empty string), run: -```bash -bootdev config base_url -``` + ```sh + bootdev config base_url + ``` - To reset the base URL and revert to using the lessons' defaults, run: -```bash -bootdev config base_url --reset -``` + ```sh + bootdev config base_url --reset + ``` ### CLI colors @@ -120,20 +120,20 @@ The CLI text output is rendered with extra colors: green (e.g., success messages - To customize these colors, run: -```bash -bootdev config colors --red --green --gray -``` + ```sh + bootdev config colors --red --green --gray + ``` -_You can use an [ANSI color code](https://en.wikipedia.org/wiki/ANSI_escape_code#8-bit) or a hex string as the ``._ + _You can use an [ANSI color code](https://en.wikipedia.org/wiki/ANSI_escape_code#8-bit) or a hex string as the ``._ - To get the current colors, run: -```bash -bootdev config colors -``` + ```sh + bootdev config colors + ``` - To reset the colors to their default values, run: -```bash -bootdev config colors --reset -``` + ```sh + bootdev config colors --reset + ``` From efa14604e7986dd04dce3761dce1fa98ce17d080 Mon Sep 17 00:00:00 2001 From: Theo Beers Date: Mon, 12 Jan 2026 23:47:00 -0500 Subject: [PATCH 3/3] lowercase error strings --- client/auth.go | 2 +- client/lessons.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/client/auth.go b/client/auth.go index 91b5548..59e1a15 100644 --- a/client/auth.go +++ b/client/auth.go @@ -88,7 +88,7 @@ func fetchWithAuth(method string, url string) ([]byte, error) { return nil, err } if code == 402 { - return nil, fmt.Errorf("To run and submit the tests for this lesson, you must have an active Boot.dev membership\nhttps://boot.dev/pricing") + return nil, fmt.Errorf("to run and submit the tests for this lesson, you must have an active Boot.dev membership\nhttps://boot.dev/pricing") } if code != 200 { return nil, fmt.Errorf("failed to %s to %s\nResponse: %d %s", method, url, code, string(body)) diff --git a/client/lessons.go b/client/lessons.go index d6b9131..d9df87c 100644 --- a/client/lessons.go +++ b/client/lessons.go @@ -181,7 +181,7 @@ func SubmitCLILesson(uuid string, results []CLIStepResult) (*VerificationResultS return nil, err } if code == 402 { - return nil, fmt.Errorf("To run and submit the tests for this lesson, you must have an active Boot.dev membership\nhttps://boot.dev/pricing") + return nil, fmt.Errorf("to run and submit the tests for this lesson, you must have an active Boot.dev membership\nhttps://boot.dev/pricing") } if code != 200 { return nil, fmt.Errorf("failed to submit CLI lesson (code %v): %s", code, string(resp))