diff --git a/CHANGELOG.md b/CHANGELOG.md index 96963b3..dc8fbe2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,10 @@ ## 0.11.6 - Unreleased +### Fixes + +- Show setup guidance instead of a raw missing-file error when configuration-dependent commands cannot find `config.toml`. Thanks @0xdevalias. + ## 0.11.5 - 2026-07-09 ### Maintenance diff --git a/internal/cli/cli.go b/internal/cli/cli.go index d2636e5..0ef849e 100644 --- a/internal/cli/cli.go +++ b/internal/cli/cli.go @@ -554,6 +554,9 @@ func (r *runtime) withMessagesSyncServices(fn func() error) error { func (r *runtime) withServicesUpdateLockedOperation(withDiscord bool, updateMode shareUpdateMode, lockDB bool, operation string, fn func() error) error { cfg, err := config.Load(r.configPath) if err != nil { + if errors.Is(err, os.ErrNotExist) { + return configErr(fmt.Errorf("config file not found at %s; run `discrawl init`, pass `--config `, or set %s", r.configPath, config.DefaultConfigEnv)) + } return configErr(err) } if err := config.EnsureRuntimeDirs(cfg); err != nil { diff --git a/internal/cli/cli_test.go b/internal/cli/cli_test.go index a005248..cb6dfde 100644 --- a/internal/cli/cli_test.go +++ b/internal/cli/cli_test.go @@ -4503,9 +4503,21 @@ func TestPrintJSONAndPlain(t *testing.T) { func TestWithServicesErrors(t *testing.T) { t.Parallel() - rt := &runtime{ctx: context.Background(), configPath: filepath.Join(t.TempDir(), "missing.toml"), stdout: &bytes.Buffer{}, stderr: &bytes.Buffer{}} + missingPath := filepath.Join(t.TempDir(), "missing.toml") + rt := &runtime{ctx: context.Background(), configPath: missingPath, stdout: &bytes.Buffer{}, stderr: &bytes.Buffer{}} err := rt.withServices(false, func() error { return nil }) require.Equal(t, 3, ExitCode(err)) + require.ErrorContains(t, err, missingPath) + require.ErrorContains(t, err, "discrawl init") + require.ErrorContains(t, err, "--config") + require.ErrorContains(t, err, config.DefaultConfigEnv) + + invalidPath := filepath.Join(t.TempDir(), "invalid.toml") + require.NoError(t, os.WriteFile(invalidPath, []byte("invalid = ["), 0o600)) + rt = &runtime{ctx: context.Background(), configPath: invalidPath, stdout: &bytes.Buffer{}, stderr: &bytes.Buffer{}} + err = rt.withServices(false, func() error { return nil }) + require.Equal(t, 3, ExitCode(err)) + require.NotContains(t, err.Error(), "discrawl init") cfgPath := filepath.Join(t.TempDir(), "config.toml") cfg := config.Default() @@ -4535,6 +4547,24 @@ func TestWithServicesErrors(t *testing.T) { require.Equal(t, 4, ExitCode(err)) } +func TestTailMissingConfigGuidance(t *testing.T) { + home := t.TempDir() + configHome := filepath.Join(home, "config") + t.Setenv("HOME", home) + t.Setenv("XDG_CONFIG_HOME", configHome) + t.Setenv("XDG_DATA_HOME", filepath.Join(home, "data")) + t.Setenv("XDG_CACHE_HOME", filepath.Join(home, "cache")) + t.Setenv("XDG_STATE_HOME", filepath.Join(home, "state")) + t.Setenv(config.DefaultConfigEnv, "") + + err := Run(context.Background(), []string{"tail"}, &bytes.Buffer{}, &bytes.Buffer{}) + require.Equal(t, 3, ExitCode(err)) + require.ErrorContains(t, err, filepath.Join(configHome, "discrawl", "config.toml")) + require.ErrorContains(t, err, "discrawl init") + require.ErrorContains(t, err, "--config") + require.ErrorContains(t, err, config.DefaultConfigEnv) +} + func TestCommandUsageErrors(t *testing.T) { t.Parallel()