From a88175afe227315466072dfa1ba86e9a765abed6 Mon Sep 17 00:00:00 2001 From: George Tsiolis Date: Tue, 16 Jun 2026 16:09:49 +0300 Subject: [PATCH] Fix duplicate config section when writing a second key setInFile() appended a new [section] header whenever the target field was absent. Writing a second distinct key to a section that already existed (e.g. a second [cli] field) therefore produced a duplicate TOML table. TOML forbids duplicate tables, so the next config load failed with "table cli already exists", corrupting the user's config. Insert the field under the existing section header when that section is already present; only append a new header when the section is absent. Add a regression test covering a second key in an existing section. --- internal/config/config.go | 5 +++++ internal/config/config_test.go | 30 ++++++++++++++++++++++++++++++ 2 files changed, 35 insertions(+) diff --git a/internal/config/config.go b/internal/config/config.go index 8f17825b..34d4cc34 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -162,8 +162,13 @@ func setInFile(path, key string, value any) error { content := string(data) re := regexp.MustCompile(`(?m)^\s*` + regexp.QuoteMeta(field) + `\s*=.*$`) + secRe := regexp.MustCompile(`(?m)^\[` + regexp.QuoteMeta(section) + `\][^\n]*$`) if re.MatchString(content) { content = re.ReplaceAllString(content, assignment) + } else if secRe.MatchString(content) { + content = secRe.ReplaceAllStringFunc(content, func(header string) string { + return header + "\n" + assignment + }) } else { content = strings.TrimRight(content, "\n") + "\n\n[" + section + "]\n" + assignment + "\n" } diff --git a/internal/config/config_test.go b/internal/config/config_test.go index 4fedc013..82c8e8d7 100644 --- a/internal/config/config_test.go +++ b/internal/config/config_test.go @@ -60,6 +60,36 @@ update_skipped_version = "v1.0.0" require.NoError(t, toml.Unmarshal(got, &parsed)) } +func TestSetInFileAddsSecondFieldToExistingSection(t *testing.T) { + path := filepath.Join(t.TempDir(), "config.toml") + original := `# Keep this +[[containers]] +type = "aws" + +[cli] +update_skipped_version = "v1.0.0" +` + require.NoError(t, os.WriteFile(path, []byte(original), 0644)) + + require.NoError(t, setInFile(path, "cli.notify_cadence", "minor")) + + got, err := os.ReadFile(path) + require.NoError(t, err) + result := string(got) + + var parsed struct { + CLI struct { + UpdateSkippedVersion string `toml:"update_skipped_version"` + NotifyCadence string `toml:"notify_cadence"` + } `toml:"cli"` + } + require.NoError(t, toml.Unmarshal(got, &parsed)) + assert.Equal(t, "v1.0.0", parsed.CLI.UpdateSkippedVersion) + assert.Equal(t, "minor", parsed.CLI.NotifyCadence) + + assert.Equal(t, 1, strings.Count(result, "[cli]"), "expected a single [cli] section header") +} + func TestSetInFilePreservesCommentsAndFormatting(t *testing.T) { path := filepath.Join(t.TempDir(), "config.toml") original := `# lstk configuration file