From a5bb0cef09b32efdcf04a5584fd3933755355dbf Mon Sep 17 00:00:00 2001 From: "m.kindritskiy" Date: Fri, 3 Apr 2026 16:08:29 +0300 Subject: [PATCH] Fix crash on malformed local mixin --- docs/docs/changelog.md | 1 + internal/config/config/config.go | 2 +- internal/config/config/mixin.go | 8 ++++++++ internal/config/load_test.go | 30 ++++++++++++++++++++++++++++++ 4 files changed, 40 insertions(+), 1 deletion(-) diff --git a/docs/docs/changelog.md b/docs/docs/changelog.md index cfe8f42b..b7e84a97 100644 --- a/docs/docs/changelog.md +++ b/docs/docs/changelog.md @@ -23,6 +23,7 @@ title: Changelog * `[Fixed]` Resolve `go to definition` from command references such as `ref: build` to the referenced command in `lets self lsp`. * `[Added]` Load local mixin files into LSP storage and command index so mixin commands are available for navigation. * `[Changed]` Replace the top-level `--upgrade` flag with the `lets self upgrade` command. +* `[Fixed]` Return a normal parse error when a local mixin contains malformed YAML instead of crashing. ## [0.0.59](https://github.com/lets-cli/lets/releases/tag/v0.0.59) diff --git a/internal/config/config/config.go b/internal/config/config/config.go index 3415e2bf..7070c2ce 100644 --- a/internal/config/config/config.go +++ b/internal/config/config/config.go @@ -292,7 +292,7 @@ func (c *Config) readMixins(mixins []*Mixin) error { for _, mixin := range mixins { if err := c.readMixin(mixin); err != nil { - return fmt.Errorf("failed to read remote mixin config '%s': %w", mixin.Remote.URL, err) + return fmt.Errorf("failed to read mixin config '%s': %w", mixin.Source(), err) } } diff --git a/internal/config/config/mixin.go b/internal/config/config/mixin.go index fd0bedd8..4b8b9c5f 100644 --- a/internal/config/config/mixin.go +++ b/internal/config/config/mixin.go @@ -190,3 +190,11 @@ func (m *Mixin) UnmarshalYAML(unmarshal func(any) error) error { func (m *Mixin) IsRemote() bool { return m.Remote != nil } + +func (m *Mixin) Source() string { + if m.IsRemote() { + return m.Remote.URL + } + + return m.FileName +} diff --git a/internal/config/load_test.go b/internal/config/load_test.go index 7a0d62c6..3b92064e 100644 --- a/internal/config/load_test.go +++ b/internal/config/load_test.go @@ -1,6 +1,9 @@ package config import ( + "os" + "path/filepath" + "strings" "testing" ) @@ -11,4 +14,31 @@ func TestLoadConfig(t *testing.T) { t.Errorf("can not load test config: %s", err) } }) + + t.Run("returns error for malformed local mixin", func(t *testing.T) { + tempDir := t.TempDir() + + mainConfig := "shell: bash\nmixins: [mixin.yaml]\ncommands:\n ok:\n cmd: echo ok\n" + if err := os.WriteFile(filepath.Join(tempDir, "lets.yaml"), []byte(mainConfig), 0o644); err != nil { + t.Fatalf("write main config: %v", err) + } + + mixinConfig := "commands:\n test1:\n xxx\n cmd: echo Test\n" + if err := os.WriteFile(filepath.Join(tempDir, "mixin.yaml"), []byte(mixinConfig), 0o644); err != nil { + t.Fatalf("write mixin config: %v", err) + } + + _, err := Load("", tempDir, "0.0.0-test") + if err == nil { + t.Fatal("expected malformed mixin error") + } + + if !strings.Contains(err.Error(), "failed to read mixin config 'mixin.yaml'") { + t.Fatalf("unexpected error: %v", err) + } + + if !strings.Contains(err.Error(), "can not parse mixin config mixin.yaml") { + t.Fatalf("unexpected error: %v", err) + } + }) }