diff --git a/.chloggen/chloggen-multiple-logs.yaml b/.chloggen/chloggen-multiple-logs.yaml new file mode 100755 index 00000000..2924cc4b --- /dev/null +++ b/.chloggen/chloggen-multiple-logs.yaml @@ -0,0 +1,16 @@ +# One of 'breaking', 'deprecation', 'new_component', 'enhancement', 'bug_fix' +change_type: enhancement + +# The name of the component, or a single word describing the area of concern, (e.g. crosslink) +component: chloggen + +# A brief description of the change. Surround your text with quotes ("") if it needs to start with a backtick (`). +note: Add ability to configure separate changelogs for different audiences + +# One or more tracking issues related to the change +issues: [364] + +# (Optional) One or more lines of additional information to render under the primary note. +# These lines will be padded with 2 spaces and then inserted directly into the document. +# Use pipe (|) for multiline entries. +subtext: diff --git a/chloggen/cmd/cmd_test.go b/chloggen/cmd/cmd_test.go index b2453580..34846b37 100644 --- a/chloggen/cmd/cmd_test.go +++ b/chloggen/cmd/cmd_test.go @@ -98,15 +98,33 @@ func entryWithSubtext() *chlog.Entry { } } +func entryForChangelogs(changeType string, issue int, keys ...string) *chlog.Entry { + keyStr := "default" + if len(keys) > 0 { + keyStr = strings.Join(keys, ",") + } + return &chlog.Entry{ + ChangeLogs: keys, + ChangeType: changeType, + Component: "receiver/foo", + Note: fmt.Sprintf("Some change relevant to [%s]", keyStr), + Issues: []int{issue}, + } +} + func setupTestDir(t *testing.T, entries []*chlog.Entry) { require.NotNil(t, globalCfg, "test should instantiate globalCfg before calling setupTestDir") - // Create a known dummy changelog which may be updated by the test - changelogBytes, err := os.ReadFile(filepath.Join("testdata", config.DefaultChangelogMD)) + // Create dummy changelogs which may be updated by the test + changelogBytes, err := os.ReadFile(filepath.Join("testdata", config.DefaultChangeLogFilename)) require.NoError(t, err) - require.NoError(t, os.WriteFile(globalCfg.ChangelogMD, changelogBytes, os.FileMode(0755))) + for _, filename := range globalCfg.ChangeLogs { + require.NoError(t, os.MkdirAll(filepath.Dir(filename), os.FileMode(0755))) + require.NoError(t, os.WriteFile(filename, changelogBytes, os.FileMode(0755))) + } - require.NoError(t, os.Mkdir(globalCfg.ChlogsDir, os.FileMode(0755))) + // Create the chlogs directory + require.NoError(t, os.MkdirAll(globalCfg.ChlogsDir, os.FileMode(0755))) // Copy the entry template, for tests that ensure it is not deleted templateInRootDir := config.New("testdata").TemplateYAML @@ -114,6 +132,7 @@ func setupTestDir(t *testing.T, entries []*chlog.Entry) { require.NoError(t, err) require.NoError(t, os.WriteFile(globalCfg.TemplateYAML, templateBytes, os.FileMode(0755))) + // Write the entries to the chlogs directory for i, entry := range entries { entryBytes, err := yaml.Marshal(entry) require.NoError(t, err) diff --git a/chloggen/cmd/testdata/all_change_types.md b/chloggen/cmd/testdata/all_change_types/CHANGELOG.md similarity index 100% rename from chloggen/cmd/testdata/all_change_types.md rename to chloggen/cmd/testdata/all_change_types/CHANGELOG.md diff --git a/chloggen/cmd/testdata/all_change_types_multiple.md b/chloggen/cmd/testdata/all_change_types_multiple/CHANGELOG.md similarity index 100% rename from chloggen/cmd/testdata/all_change_types_multiple.md rename to chloggen/cmd/testdata/all_change_types_multiple/CHANGELOG.md diff --git a/chloggen/cmd/testdata/breaking_only.md b/chloggen/cmd/testdata/breaking_only/CHANGELOG.md similarity index 100% rename from chloggen/cmd/testdata/breaking_only.md rename to chloggen/cmd/testdata/breaking_only/CHANGELOG.md diff --git a/chloggen/cmd/testdata/bug_fix_only.md b/chloggen/cmd/testdata/bug_fix_only/CHANGELOG.md similarity index 100% rename from chloggen/cmd/testdata/bug_fix_only.md rename to chloggen/cmd/testdata/bug_fix_only/CHANGELOG.md diff --git a/chloggen/cmd/testdata/deprecation_only.md b/chloggen/cmd/testdata/deprecation_only/CHANGELOG.md similarity index 100% rename from chloggen/cmd/testdata/deprecation_only.md rename to chloggen/cmd/testdata/deprecation_only/CHANGELOG.md diff --git a/chloggen/cmd/testdata/dry_run.md b/chloggen/cmd/testdata/dry_run/CHANGELOG.md similarity index 100% rename from chloggen/cmd/testdata/dry_run.md rename to chloggen/cmd/testdata/dry_run/CHANGELOG.md diff --git a/chloggen/cmd/testdata/enhancement_only.md b/chloggen/cmd/testdata/enhancement_only/CHANGELOG.md similarity index 100% rename from chloggen/cmd/testdata/enhancement_only.md rename to chloggen/cmd/testdata/enhancement_only/CHANGELOG.md diff --git a/chloggen/cmd/testdata/multiple_changelogs/CHANGELOG-API.md b/chloggen/cmd/testdata/multiple_changelogs/CHANGELOG-API.md new file mode 100755 index 00000000..21e53086 --- /dev/null +++ b/chloggen/cmd/testdata/multiple_changelogs/CHANGELOG-API.md @@ -0,0 +1,40 @@ +# Changelog + + + +## v0.45.0 + +### 🛑 Breaking changes 🛑 + +- `receiver/foo`: Some change relevant to [api] (#125) +- `receiver/foo`: Some change relevant to [user,api] (#11) + +### 🚩 Deprecations 🚩 + +- `receiver/foo`: Some change relevant to [api] (#223) +- `receiver/foo`: Some change relevant to [user,api] (#234) + +### 💡 Enhancements 💡 + +- `receiver/foo`: Some change relevant to [api,user] (#333) +- `receiver/foo`: Some change relevant to [api] (#555) + +### 🧰 Bug fixes 🧰 + +- `receiver/foo`: Some change relevant to [api] (#111) +- `receiver/foo`: Some change relevant to [api] (#777) + +## v0.44.0 + +### 🛑 Breaking changes 🛑 + +- `prometheusexporter`: Automatically rename metrics with units to follow Prometheus naming convention (#8950) + +### 💡 Enhancements 💡 + +- `filterprocessor`: Ability to filter `Spans` (#6341) +- `flinkmetricsreceiver`: add attribute values to metadata #11520 + +### 🧰 Bug fixes 🧰 + +- `redactionprocessor`: respect allow_all_keys configuration (#11542) diff --git a/chloggen/cmd/testdata/multiple_changelogs/CHANGELOG.md b/chloggen/cmd/testdata/multiple_changelogs/CHANGELOG.md new file mode 100755 index 00000000..44d8767b --- /dev/null +++ b/chloggen/cmd/testdata/multiple_changelogs/CHANGELOG.md @@ -0,0 +1,39 @@ +# Changelog + + + +## v0.45.0 + +### 🛑 Breaking changes 🛑 + +- `receiver/foo`: Some change relevant to [user,api] (#11) + +### 🚩 Deprecations 🚩 + +- `receiver/foo`: Some change relevant to [user] (#123) +- `receiver/foo`: Some change relevant to [user,api] (#234) + +### 💡 Enhancements 💡 + +- `receiver/foo`: Some change relevant to [user] (#21) +- `receiver/foo`: Some change relevant to [api,user] (#333) + +### 🧰 Bug fixes 🧰 + +- `receiver/foo`: Some change relevant to [user] (#32) +- `receiver/foo`: Some change relevant to [user] (#222) + +## v0.44.0 + +### 🛑 Breaking changes 🛑 + +- `prometheusexporter`: Automatically rename metrics with units to follow Prometheus naming convention (#8950) + +### 💡 Enhancements 💡 + +- `filterprocessor`: Ability to filter `Spans` (#6341) +- `flinkmetricsreceiver`: add attribute values to metadata #11520 + +### 🧰 Bug fixes 🧰 + +- `redactionprocessor`: respect allow_all_keys configuration (#11542) diff --git a/chloggen/cmd/testdata/multiple_changelogs_multiple_defaults/CHANGELOG-API.md b/chloggen/cmd/testdata/multiple_changelogs_multiple_defaults/CHANGELOG-API.md new file mode 100755 index 00000000..ba40ad50 --- /dev/null +++ b/chloggen/cmd/testdata/multiple_changelogs_multiple_defaults/CHANGELOG-API.md @@ -0,0 +1,44 @@ +# Changelog + + + +## v0.45.0 + +### 🛑 Breaking changes 🛑 + +- `receiver/foo`: Some change relevant to [api] (#125) +- `receiver/foo`: Some change relevant to [user,api] (#11) + +### 🚩 Deprecations 🚩 + +- `receiver/foo`: Some change relevant to [default] (#123) +- `receiver/foo`: Some change relevant to [api] (#223) +- `receiver/foo`: Some change relevant to [user,api] (#234) + +### 💡 Enhancements 💡 + +- `receiver/foo`: Some change relevant to [default] (#21) +- `receiver/foo`: Some change relevant to [api,user] (#333) +- `receiver/foo`: Some change relevant to [api] (#555) + +### 🧰 Bug fixes 🧰 + +- `receiver/foo`: Some change relevant to [default] (#32) +- `receiver/foo`: Some change relevant to [default] (#222) +- `receiver/foo`: Some change relevant to [api] (#111) +- `receiver/foo`: Some change relevant to [api] (#777) + +## v0.44.0 + +### 🛑 Breaking changes 🛑 + +- `prometheusexporter`: Automatically rename metrics with units to follow Prometheus naming convention (#8950) + +### 💡 Enhancements 💡 + +- `filterprocessor`: Ability to filter `Spans` (#6341) +- `flinkmetricsreceiver`: add attribute values to metadata #11520 + +### 🧰 Bug fixes 🧰 + +- `redactionprocessor`: respect allow_all_keys configuration (#11542) diff --git a/chloggen/cmd/testdata/multiple_changelogs_multiple_defaults/CHANGELOG.md b/chloggen/cmd/testdata/multiple_changelogs_multiple_defaults/CHANGELOG.md new file mode 100755 index 00000000..c140e7cc --- /dev/null +++ b/chloggen/cmd/testdata/multiple_changelogs_multiple_defaults/CHANGELOG.md @@ -0,0 +1,39 @@ +# Changelog + + + +## v0.45.0 + +### 🛑 Breaking changes 🛑 + +- `receiver/foo`: Some change relevant to [user,api] (#11) + +### 🚩 Deprecations 🚩 + +- `receiver/foo`: Some change relevant to [default] (#123) +- `receiver/foo`: Some change relevant to [user,api] (#234) + +### 💡 Enhancements 💡 + +- `receiver/foo`: Some change relevant to [default] (#21) +- `receiver/foo`: Some change relevant to [api,user] (#333) + +### 🧰 Bug fixes 🧰 + +- `receiver/foo`: Some change relevant to [default] (#32) +- `receiver/foo`: Some change relevant to [default] (#222) + +## v0.44.0 + +### 🛑 Breaking changes 🛑 + +- `prometheusexporter`: Automatically rename metrics with units to follow Prometheus naming convention (#8950) + +### 💡 Enhancements 💡 + +- `filterprocessor`: Ability to filter `Spans` (#6341) +- `flinkmetricsreceiver`: add attribute values to metadata #11520 + +### 🧰 Bug fixes 🧰 + +- `redactionprocessor`: respect allow_all_keys configuration (#11542) diff --git a/chloggen/cmd/testdata/multiple_changelogs_single_default/CHANGELOG-API.md b/chloggen/cmd/testdata/multiple_changelogs_single_default/CHANGELOG-API.md new file mode 100755 index 00000000..21e53086 --- /dev/null +++ b/chloggen/cmd/testdata/multiple_changelogs_single_default/CHANGELOG-API.md @@ -0,0 +1,40 @@ +# Changelog + + + +## v0.45.0 + +### 🛑 Breaking changes 🛑 + +- `receiver/foo`: Some change relevant to [api] (#125) +- `receiver/foo`: Some change relevant to [user,api] (#11) + +### 🚩 Deprecations 🚩 + +- `receiver/foo`: Some change relevant to [api] (#223) +- `receiver/foo`: Some change relevant to [user,api] (#234) + +### 💡 Enhancements 💡 + +- `receiver/foo`: Some change relevant to [api,user] (#333) +- `receiver/foo`: Some change relevant to [api] (#555) + +### 🧰 Bug fixes 🧰 + +- `receiver/foo`: Some change relevant to [api] (#111) +- `receiver/foo`: Some change relevant to [api] (#777) + +## v0.44.0 + +### 🛑 Breaking changes 🛑 + +- `prometheusexporter`: Automatically rename metrics with units to follow Prometheus naming convention (#8950) + +### 💡 Enhancements 💡 + +- `filterprocessor`: Ability to filter `Spans` (#6341) +- `flinkmetricsreceiver`: add attribute values to metadata #11520 + +### 🧰 Bug fixes 🧰 + +- `redactionprocessor`: respect allow_all_keys configuration (#11542) diff --git a/chloggen/cmd/testdata/multiple_changelogs_single_default/CHANGELOG.md b/chloggen/cmd/testdata/multiple_changelogs_single_default/CHANGELOG.md new file mode 100755 index 00000000..c140e7cc --- /dev/null +++ b/chloggen/cmd/testdata/multiple_changelogs_single_default/CHANGELOG.md @@ -0,0 +1,39 @@ +# Changelog + + + +## v0.45.0 + +### 🛑 Breaking changes 🛑 + +- `receiver/foo`: Some change relevant to [user,api] (#11) + +### 🚩 Deprecations 🚩 + +- `receiver/foo`: Some change relevant to [default] (#123) +- `receiver/foo`: Some change relevant to [user,api] (#234) + +### 💡 Enhancements 💡 + +- `receiver/foo`: Some change relevant to [default] (#21) +- `receiver/foo`: Some change relevant to [api,user] (#333) + +### 🧰 Bug fixes 🧰 + +- `receiver/foo`: Some change relevant to [default] (#32) +- `receiver/foo`: Some change relevant to [default] (#222) + +## v0.44.0 + +### 🛑 Breaking changes 🛑 + +- `prometheusexporter`: Automatically rename metrics with units to follow Prometheus naming convention (#8950) + +### 💡 Enhancements 💡 + +- `filterprocessor`: Ability to filter `Spans` (#6341) +- `flinkmetricsreceiver`: add attribute values to metadata #11520 + +### 🧰 Bug fixes 🧰 + +- `redactionprocessor`: respect allow_all_keys configuration (#11542) diff --git a/chloggen/cmd/testdata/new_component_only.md b/chloggen/cmd/testdata/new_component_only/CHANGELOG.md similarity index 100% rename from chloggen/cmd/testdata/new_component_only.md rename to chloggen/cmd/testdata/new_component_only/CHANGELOG.md diff --git a/chloggen/cmd/testdata/subtext.md b/chloggen/cmd/testdata/subtext/CHANGELOG.md similarity index 100% rename from chloggen/cmd/testdata/subtext.md rename to chloggen/cmd/testdata/subtext/CHANGELOG.md diff --git a/chloggen/cmd/update.go b/chloggen/cmd/update.go index 4dbfa3a1..227db489 100644 --- a/chloggen/cmd/update.go +++ b/chloggen/cmd/update.go @@ -40,55 +40,57 @@ func updateCmd() *cobra.Command { Use: "update", Short: "Updates CHANGELOG.MD to include all new changes", RunE: func(cmd *cobra.Command, args []string) error { - entries, err := chlog.ReadEntries(globalCfg) + entriesByChangelog, err := chlog.ReadEntries(globalCfg) if err != nil { return err } - if len(entries) == 0 { - return fmt.Errorf("no entries to add to the changelog") + for changeLogKey, entries := range entriesByChangelog { + chlogUpdate, err := chlog.GenerateSummary(version, entries) + if err != nil { + return err + } + + if dry { + cmd.Printf("Generated changelog updates for %s:", changeLogKey) + cmd.Println(chlogUpdate) + continue + } + + filename := globalCfg.ChangeLogs[changeLogKey] + oldChlogBytes, err := os.ReadFile(filepath.Clean(filename)) + if err != nil { + return err + } + chlogParts := bytes.Split(oldChlogBytes, []byte(insertPoint)) + if len(chlogParts) != 2 { + return fmt.Errorf("expected one instance of %s", insertPoint) + } + + chlogHeader, chlogHistory := string(chlogParts[0]), string(chlogParts[1]) + + var chlogBuilder strings.Builder + chlogBuilder.WriteString(chlogHeader) + chlogBuilder.WriteString(insertPoint) + chlogBuilder.WriteString(chlogUpdate) + chlogBuilder.WriteString(chlogHistory) + + tmpMD := filename + ".tmp" + if err = os.WriteFile(filepath.Clean(tmpMD), []byte(chlogBuilder.String()), 0600); err != nil { + return err + } + + if err = os.Rename(tmpMD, filename); err != nil { + return err + } + + cmd.Printf("Finished updating %s\n", filename) + + if err = chlog.DeleteEntries(globalCfg); err != nil { + return err + } } - - chlogUpdate, err := chlog.GenerateSummary(version, entries) - if err != nil { - return err - } - - if dry { - cmd.Printf("Generated changelog updates:") - cmd.Println(chlogUpdate) - return nil - } - - oldChlogBytes, err := os.ReadFile(filepath.Clean(globalCfg.ChangelogMD)) - if err != nil { - return err - } - chlogParts := bytes.Split(oldChlogBytes, []byte(insertPoint)) - if len(chlogParts) != 2 { - return fmt.Errorf("expected one instance of %s", insertPoint) - } - - chlogHeader, chlogHistory := string(chlogParts[0]), string(chlogParts[1]) - - var chlogBuilder strings.Builder - chlogBuilder.WriteString(chlogHeader) - chlogBuilder.WriteString(insertPoint) - chlogBuilder.WriteString(chlogUpdate) - chlogBuilder.WriteString(chlogHistory) - - tmpMD := globalCfg.ChangelogMD + ".tmp" - if err = os.WriteFile(filepath.Clean(tmpMD), []byte(chlogBuilder.String()), 0600); err != nil { - return err - } - - if err = os.Rename(tmpMD, globalCfg.ChangelogMD); err != nil { - return err - } - - cmd.Printf("Finished updating %s\n", globalCfg.ChangelogMD) - - return chlog.DeleteEntries(globalCfg) + return nil }, } cmd.Flags().StringVarP(&version, "version", "v", "vTODO", "will be rendered directly into the update text") diff --git a/chloggen/cmd/update_test.go b/chloggen/cmd/update_test.go index cc5a4d1e..3b513db4 100644 --- a/chloggen/cmd/update_test.go +++ b/chloggen/cmd/update_test.go @@ -49,10 +49,6 @@ func TestUpdateErr(t *testing.T) { assert.Contains(t, out, updateUsage) assert.Empty(t, err) - out, err = runCobra(t, "update") - assert.Contains(t, out, updateUsage) - assert.Contains(t, err, "no entries to add to the changelog") - badEntry, ioErr := os.CreateTemp(globalCfg.ChlogsDir, "*.yaml") require.NoError(t, ioErr) defer badEntry.Close() @@ -70,10 +66,12 @@ func TestUpdate(t *testing.T) { } tests := []struct { - name string - entries []*chlog.Entry - version string - dry bool + name string + entries []*chlog.Entry + changeLogs map[string]string + defaultChangeLogs []string + version string + dry bool }{ { name: "all_change_types", @@ -121,11 +119,90 @@ func TestUpdate(t *testing.T) { entries: []*chlog.Entry{entryWithSubtext()}, version: "v0.45.0", }, + { + name: "multiple_changelogs", + entries: []*chlog.Entry{ + entryForChangelogs(chlog.Deprecation, 123, "user"), + entryForChangelogs(chlog.Breaking, 125, "api"), + entryForChangelogs(chlog.Enhancement, 333, "api", "user"), + entryForChangelogs(chlog.BugFix, 222, "user"), + entryForChangelogs(chlog.Deprecation, 223, "api"), + entryForChangelogs(chlog.BugFix, 111, "api"), + entryForChangelogs(chlog.Breaking, 11, "user", "api"), + entryForChangelogs(chlog.Enhancement, 555, "api"), + entryForChangelogs(chlog.BugFix, 777, "api"), + entryForChangelogs(chlog.Deprecation, 234, "user", "api"), + entryForChangelogs(chlog.Enhancement, 21, "user"), + entryForChangelogs(chlog.BugFix, 32, "user"), + }, + changeLogs: map[string]string{ + "user": "CHANGELOG.md", + "api": "CHANGELOG-API.md", + }, + version: "v0.45.0", + }, + { + name: "multiple_changelogs_single_default", + entries: []*chlog.Entry{ + entryForChangelogs(chlog.Deprecation, 123), + entryForChangelogs(chlog.Breaking, 125, "api"), + entryForChangelogs(chlog.Enhancement, 333, "api", "user"), + entryForChangelogs(chlog.BugFix, 222), + entryForChangelogs(chlog.Deprecation, 223, "api"), + entryForChangelogs(chlog.BugFix, 111, "api"), + entryForChangelogs(chlog.Breaking, 11, "user", "api"), + entryForChangelogs(chlog.Enhancement, 555, "api"), + entryForChangelogs(chlog.BugFix, 777, "api"), + entryForChangelogs(chlog.Deprecation, 234, "user", "api"), + entryForChangelogs(chlog.Enhancement, 21), + entryForChangelogs(chlog.BugFix, 32), + }, + changeLogs: map[string]string{ + "user": "CHANGELOG.md", + "api": "CHANGELOG-API.md", + }, + defaultChangeLogs: []string{"user"}, + version: "v0.45.0", + }, + { + name: "multiple_changelogs_multiple_defaults", + entries: []*chlog.Entry{ + entryForChangelogs(chlog.Deprecation, 123), + entryForChangelogs(chlog.Breaking, 125, "api"), + entryForChangelogs(chlog.Enhancement, 333, "api", "user"), + entryForChangelogs(chlog.BugFix, 222), + entryForChangelogs(chlog.Deprecation, 223, "api"), + entryForChangelogs(chlog.BugFix, 111, "api"), + entryForChangelogs(chlog.Breaking, 11, "user", "api"), + entryForChangelogs(chlog.Enhancement, 555, "api"), + entryForChangelogs(chlog.BugFix, 777, "api"), + entryForChangelogs(chlog.Deprecation, 234, "user", "api"), + entryForChangelogs(chlog.Enhancement, 21), + entryForChangelogs(chlog.BugFix, 32), + }, + changeLogs: map[string]string{ + "user": "CHANGELOG.md", + "api": "CHANGELOG-API.md", + }, + defaultChangeLogs: []string{"user", "api"}, + version: "v0.45.0", + }, } for _, tc := range tests { t.Run(tc.name, func(t *testing.T) { - globalCfg = config.New(t.TempDir()) + tempDir := t.TempDir() + globalCfg = config.New(tempDir) + if len(tc.changeLogs) > 0 { + globalCfg.ChangeLogs = make(map[string]string) + for key, filename := range tc.changeLogs { + globalCfg.ChangeLogs[key] = filepath.Join(tempDir, filename) + } + } + if len(tc.defaultChangeLogs) > 0 { + globalCfg.DefaultChangeLogs = tc.defaultChangeLogs + } + setupTestDir(t, tc.entries) args := []string{"update", "--version", tc.version} @@ -138,27 +215,31 @@ func TestUpdate(t *testing.T) { assert.Empty(t, err) if tc.dry { - assert.Contains(t, out, "Generated changelog updates:") + assert.Contains(t, out, "Generated changelog updates for") } else { - assert.Contains(t, out, fmt.Sprintf("Finished updating %s", globalCfg.ChangelogMD)) + for _, filename := range globalCfg.ChangeLogs { + assert.Contains(t, out, fmt.Sprintf("Finished updating %s", filename)) + } } - actualBytes, ioErr := os.ReadFile(globalCfg.ChangelogMD) - require.NoError(t, ioErr) + for _, filename := range globalCfg.ChangeLogs { + actualBytes, ioErr := os.ReadFile(filename) // nolint:gosec + require.NoError(t, ioErr) - expectedChangelogMD := filepath.Join("testdata", tc.name+".md") - expectedBytes, ioErr := os.ReadFile(filepath.Clean(expectedChangelogMD)) - require.NoError(t, ioErr) + expectedChangelogMD := filepath.Join("testdata", tc.name, filepath.Base(filename)) + expectedBytes, ioErr := os.ReadFile(filepath.Clean(expectedChangelogMD)) + require.NoError(t, ioErr) - require.Equal(t, string(expectedBytes), string(actualBytes)) + require.Equal(t, string(expectedBytes), string(actualBytes)) - remainingYAMLs, ioErr := filepath.Glob(filepath.Join(globalCfg.ChlogsDir, "*.yaml")) - require.NoError(t, ioErr) - if tc.dry { - require.Equal(t, 1+len(tc.entries), len(remainingYAMLs)) - } else { - require.Equal(t, 1, len(remainingYAMLs)) - require.Equal(t, globalCfg.TemplateYAML, remainingYAMLs[0]) + remainingYAMLs, ioErr := filepath.Glob(filepath.Join(globalCfg.ChlogsDir, "*.yaml")) + require.NoError(t, ioErr) + if tc.dry { + require.Equal(t, 1+len(tc.entries), len(remainingYAMLs)) + } else { + require.Equal(t, 1, len(remainingYAMLs)) + require.Equal(t, globalCfg.TemplateYAML, remainingYAMLs[0]) + } } }) } diff --git a/chloggen/cmd/validate.go b/chloggen/cmd/validate.go index 945e0ace..9f73add4 100644 --- a/chloggen/cmd/validate.go +++ b/chloggen/cmd/validate.go @@ -31,13 +31,20 @@ func validateCmd() *cobra.Command { return err } - entries, err := chlog.ReadEntries(globalCfg) + entriesByChangelog, err := chlog.ReadEntries(globalCfg) if err != nil { return err } - for _, entry := range entries { - if err = entry.Validate(); err != nil { - return err + for _, entries := range entriesByChangelog { + for _, entry := range entries { + changelogRequired := len(globalCfg.DefaultChangeLogs) == 0 + validChangeLogs := []string{} + for changeLogKey := range globalCfg.ChangeLogs { + validChangeLogs = append(validChangeLogs, changeLogKey) + } + if err = entry.Validate(changelogRequired, validChangeLogs...); err != nil { + return err + } } } cmd.Printf("PASS: all files in %s/ are valid\n", globalCfg.ChlogsDir) diff --git a/chloggen/internal/chlog/TEMPLATE.yaml b/chloggen/internal/chlog/TEMPLATE.yaml new file mode 100644 index 00000000..2702ed24 --- /dev/null +++ b/chloggen/internal/chlog/TEMPLATE.yaml @@ -0,0 +1,25 @@ +# Use this changelog template to create an entry for release notes. + +# Optional: The change log or logs in which this entry should be included. +# e.g. '[user]' or '[user, api]' +# Include 'user' if the change is relevant to end users. +# Include 'api' if there is a change to a library API. +# Default: '[user]' +change_logs: [] + +# One of 'breaking', 'deprecation', 'new_component', 'enhancement', 'bug_fix' +change_type: + +# The name of the component, or a single word describing the area of concern, (e.g. crosslink) +component: + +# A brief description of the change. Surround your text with quotes ("") if it needs to start with a backtick (`). +note: + +# One or more tracking issues related to the change +issues: [] + +# (Optional) One or more lines of additional information to render under the primary note. +# These lines will be padded with 2 spaces and then inserted directly into the document. +# Use pipe (|) for multiline entries. +subtext: diff --git a/chloggen/internal/chlog/entry.go b/chloggen/internal/chlog/entry.go index a8aa17b3..a5358084 100644 --- a/chloggen/internal/chlog/entry.go +++ b/chloggen/internal/chlog/entry.go @@ -34,11 +34,12 @@ const ( ) type Entry struct { - ChangeType string `yaml:"change_type"` - Component string `yaml:"component"` - Note string `yaml:"note"` - Issues []int `yaml:"issues"` - SubText string `yaml:"subtext"` + ChangeLogs []string `yaml:"change_logs"` + ChangeType string `yaml:"change_type"` + Component string `yaml:"component"` + Note string `yaml:"note"` + Issues []int `yaml:"issues"` + SubText string `yaml:"subtext"` } var changeTypes = []string{ @@ -49,7 +50,22 @@ var changeTypes = []string{ BugFix, } -func (e Entry) Validate() error { +func (e Entry) Validate(requireChangelog bool, validChangeLogs ...string) error { + if requireChangelog && len(e.ChangeLogs) == 0 { + return fmt.Errorf("specify one or more 'change_logs'") + } + for _, cl := range e.ChangeLogs { + var valid bool + for _, vcl := range validChangeLogs { + if cl == vcl { + valid = true + } + } + if !valid { + return fmt.Errorf("'%s' is not a valid 'change_log'. Specify one of %v", cl, validChangeLogs) + } + } + var validType bool for _, ct := range changeTypes { if e.ChangeType == ct { @@ -93,19 +109,23 @@ func (e Entry) String() string { return sb.String() } -func ReadEntries(cfg *config.Config) ([]*Entry, error) { - entryYAMLs, err := filepath.Glob(filepath.Join(cfg.ChlogsDir, "*.yaml")) +func ReadEntries(cfg *config.Config) (map[string][]*Entry, error) { + yamlFiles, err := filepath.Glob(filepath.Join(cfg.ChlogsDir, "*.yaml")) if err != nil { return nil, err } - entries := make([]*Entry, 0, len(entryYAMLs)) - for _, entryYAML := range entryYAMLs { - if entryYAML == cfg.TemplateYAML || entryYAML == cfg.ConfigYAML { + entries := make(map[string][]*Entry) + for key := range cfg.ChangeLogs { + entries[key] = make([]*Entry, 0) + } + + for _, file := range yamlFiles { + if file == cfg.TemplateYAML || file == cfg.ConfigYAML { continue } - fileBytes, err := os.ReadFile(filepath.Clean(entryYAML)) + fileBytes, err := os.ReadFile(filepath.Clean(file)) if err != nil { return nil, err } @@ -114,24 +134,33 @@ func ReadEntries(cfg *config.Config) ([]*Entry, error) { if err = yaml.Unmarshal(fileBytes, entry); err != nil { return nil, err } - entries = append(entries, entry) + + if len(entry.ChangeLogs) == 0 { + for _, cl := range cfg.DefaultChangeLogs { + entries[cl] = append(entries[cl], entry) + } + } else { + for _, cl := range entry.ChangeLogs { + entries[cl] = append(entries[cl], entry) + } + } } return entries, nil } func DeleteEntries(cfg *config.Config) error { - entryYAMLs, err := filepath.Glob(filepath.Join(cfg.ChlogsDir, "*.yaml")) + yamlFiles, err := filepath.Glob(filepath.Join(cfg.ChlogsDir, "*.yaml")) if err != nil { return err } - for _, entryYAML := range entryYAMLs { - if filepath.Base(entryYAML) == filepath.Base(cfg.TemplateYAML) { + for _, file := range yamlFiles { + if file == cfg.TemplateYAML || file == cfg.ConfigYAML { continue } - if err := os.Remove(entryYAML); err != nil { - fmt.Printf("Failed to delete: %s\n", entryYAML) + if err := os.Remove(file); err != nil { + fmt.Printf("Failed to delete: %s\n", file) } } return nil diff --git a/chloggen/internal/chlog/entry_test.go b/chloggen/internal/chlog/entry_test.go index 370686d6..a148bd88 100644 --- a/chloggen/internal/chlog/entry_test.go +++ b/chloggen/internal/chlog/entry_test.go @@ -28,10 +28,12 @@ import ( func TestEntry(t *testing.T) { testCases := []struct { - name string - entry Entry - expectErr string - toString string + name string + entry Entry + requireChangeLog bool + validChangeLogs []string + expectErr string + toString string }{ { name: "empty", @@ -68,6 +70,32 @@ func TestEntry(t *testing.T) { }, expectErr: "specify one or more issues #'s", }, + { + name: "missing_required_changelog", + entry: Entry{ + ChangeType: "bug_fix", + Component: "bar", + Note: "fix bar", + Issues: []int{123}, + SubText: "", + }, + requireChangeLog: true, + validChangeLogs: []string{"foo"}, + expectErr: "specify one or more 'change_logs'", + }, + { + name: "invalid_changelog", + entry: Entry{ + ChangeLogs: []string{"bar"}, + ChangeType: "bug_fix", + Component: "bar", + Note: "fix bar", + Issues: []int{123}, + SubText: "", + }, + validChangeLogs: []string{"foo"}, + expectErr: "'bar' is not a valid 'change_log'. Specify one of [foo]", + }, { name: "valid", entry: Entry{ @@ -101,12 +129,80 @@ func TestEntry(t *testing.T) { }, toString: "- `foo`: broke foo (#123)\n more details", }, + { + name: "required_changelog", + entry: Entry{ + ChangeLogs: []string{"foo"}, + ChangeType: "breaking", + Component: "foo", + Note: "broke foo", + Issues: []int{123}, + SubText: "more details", + }, + requireChangeLog: true, + validChangeLogs: []string{"foo"}, + toString: "- `foo`: broke foo (#123)\n more details", + }, + { + name: "default_changelog", + entry: Entry{ + ChangeLogs: []string{"foo"}, + ChangeType: "breaking", + Component: "foo", + Note: "broke foo", + Issues: []int{123}, + SubText: "more details", + }, + requireChangeLog: false, + validChangeLogs: []string{"foo"}, + toString: "- `foo`: broke foo (#123)\n more details", + }, + { + name: "subset_of_changelogs", + entry: Entry{ + ChangeLogs: []string{"foo", "bar"}, + ChangeType: "breaking", + Component: "foo", + Note: "broke foo", + Issues: []int{123}, + SubText: "more details", + }, + validChangeLogs: []string{"foo", "bar", "baz"}, + toString: "- `foo`: broke foo (#123)\n more details", + }, + { + name: "all_changelogs", + entry: Entry{ + ChangeLogs: []string{"foo", "bar"}, + ChangeType: "breaking", + Component: "foo", + Note: "broke foo", + Issues: []int{123}, + SubText: "more details", + }, + validChangeLogs: []string{"foo", "bar"}, + toString: "- `foo`: broke foo (#123)\n more details", + }, + { + name: "all_changelogs", + entry: Entry{ + ChangeLogs: []string{"foo", "bar"}, + ChangeType: "breaking", + Component: "foo", + Note: "broke foo", + Issues: []int{123}, + SubText: "more details", + }, + validChangeLogs: []string{"foo", "bar"}, + toString: "- `foo`: broke foo (#123)\n more details", + }, } for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { - err := tc.entry.Validate() + err := tc.entry.Validate(tc.requireChangeLog, tc.validChangeLogs...) if tc.expectErr != "" { + assert.Error(t, err) assert.Equal(t, tc.expectErr, err.Error()) return } @@ -119,68 +215,97 @@ func TestEntry(t *testing.T) { func TestReadDeleteEntries(t *testing.T) { tempDir := t.TempDir() - entriesDir := filepath.Join(tempDir, config.DefaultChloggenDir) + entriesDir := filepath.Join(tempDir, config.DefaultChlogsDir) require.NoError(t, os.Mkdir(entriesDir, os.ModePerm)) entryA := Entry{ + ChangeLogs: []string{"foo"}, ChangeType: "breaking", Component: "foo", Note: "broke foo", Issues: []int{123}, } - - bytesA, err := yaml.Marshal(entryA) - require.NoError(t, err) - - fileA, err := os.CreateTemp(entriesDir, "*.yaml") - require.NoError(t, err) - defer fileA.Close() - - _, err = fileA.Write(bytesA) - require.NoError(t, err) + writeEntry(t, entriesDir, &entryA) entryB := Entry{ + ChangeLogs: []string{"bar"}, ChangeType: "bug_fix", Component: "bar", Note: "fix bar", Issues: []int{345, 678}, SubText: "more details", } + writeEntry(t, entriesDir, &entryB) - bytesB, err := yaml.Marshal(entryB) - require.NoError(t, err) - - fileB, err := os.CreateTemp(entriesDir, "*.yaml") - require.NoError(t, err) - defer fileB.Close() + entryC := Entry{ + ChangeLogs: []string{}, + ChangeType: "enhancement", + Component: "other", + Note: "enhance!", + Issues: []int{555}, + } + writeEntry(t, entriesDir, &entryC) - _, err = fileB.Write(bytesB) - require.NoError(t, err) + entryD := Entry{ + ChangeLogs: []string{"foo", "bar"}, + ChangeType: "deprecation", + Component: "foobar", + Note: "deprecate something", + Issues: []int{999}, + } + writeEntry(t, entriesDir, &entryD) // Put config and template files in chlogs_dir to ensure they are ignored when reading/deleting entries - configYAML, err := os.CreateTemp(entriesDir, "config.yaml") + configYAML, err := os.Create(filepath.Join(entriesDir, "config.yaml")) //nolint:gosec require.NoError(t, err) defer configYAML.Close() - templateYAML, err := os.CreateTemp(entriesDir, "TEMPLATE.yaml") + templateYAML, err := os.Create(filepath.Join(entriesDir, "TEMPLATE.yaml")) //nolint:gosec require.NoError(t, err) defer templateYAML.Close() - cfg := config.New(tempDir) - cfg.ConfigYAML = configYAML.Name() - cfg.TemplateYAML = templateYAML.Name() + cfg := &config.Config{ + ConfigYAML: configYAML.Name(), + TemplateYAML: templateYAML.Name(), + ChangeLogs: map[string]string{ + "foo": filepath.Join(entriesDir, "CHANGELOG.foo.md"), + "bar": filepath.Join(entriesDir, "CHANGELOG.bar.md"), + }, + DefaultChangeLogs: []string{"foo"}, + ChlogsDir: entriesDir, + } - entries, err := ReadEntries(cfg) + changeLogEntries, err := ReadEntries(cfg) assert.NoError(t, err) - assert.ElementsMatch(t, []*Entry{&entryA, &entryB}, entries) + assert.Equal(t, 2, len(changeLogEntries)) + + assert.Contains(t, changeLogEntries, "foo") + assert.Contains(t, changeLogEntries, "bar") + + assert.ElementsMatch(t, []*Entry{&entryA, &entryC, &entryD}, changeLogEntries["foo"]) + assert.ElementsMatch(t, []*Entry{&entryB, &entryD}, changeLogEntries["bar"]) assert.NoError(t, DeleteEntries(cfg)) - entries, err = ReadEntries(cfg) + changeLogEntries, err = ReadEntries(cfg) assert.NoError(t, err) - assert.Empty(t, entries) + assert.Equal(t, 2, len(changeLogEntries)) + assert.Empty(t, changeLogEntries["foo"]) + assert.Empty(t, changeLogEntries["bar"]) // Ensure these weren't deleted assert.FileExists(t, cfg.ConfigYAML) assert.FileExists(t, cfg.TemplateYAML) } + +func writeEntry(t *testing.T, dir string, entry *Entry) { + entryBytes, err := yaml.Marshal(entry) + require.NoError(t, err) + + entryFile, err := os.CreateTemp(dir, "*.yaml") + require.NoError(t, err) + defer entryFile.Close() + + _, err = entryFile.Write(entryBytes) + require.NoError(t, err) +} diff --git a/chloggen/internal/config/config.go b/chloggen/internal/config/config.go index 85ba42d9..506ae548 100644 --- a/chloggen/internal/config/config.go +++ b/chloggen/internal/config/config.go @@ -15,6 +15,8 @@ package config import ( + "errors" + "fmt" "os" "path/filepath" "strings" @@ -23,47 +25,76 @@ import ( ) const ( - DefaultChangelogMD = "CHANGELOG.md" - DefaultChloggenDir = ".chloggen" - DefaultTemplateYAML = "TEMPLATE.yaml" + DefaultChlogsDir = ".chloggen" + DefaultTemplateYAML = "TEMPLATE.yaml" + DefaultChangeLogKey = "default" + DefaultChangeLogFilename = "CHANGELOG.md" ) type Config struct { - ChangelogMD string `yaml:"changelog_md"` - ChlogsDir string `yaml:"chlogs_dir"` - TemplateYAML string `yaml:"template_yaml"` - ConfigYAML string + ChangeLogs map[string]string `yaml:"change_logs"` + DefaultChangeLogs []string `yaml:"default_change_logs"` + ChlogsDir string `yaml:"chlogs_dir"` + TemplateYAML string `yaml:"template_yaml"` + ConfigYAML string } func New(rootDir string) *Config { return &Config{ - ChangelogMD: filepath.Join(rootDir, DefaultChangelogMD), - ChlogsDir: filepath.Join(rootDir, DefaultChloggenDir), - TemplateYAML: filepath.Join(rootDir, DefaultChloggenDir, DefaultTemplateYAML), + ChangeLogs: map[string]string{DefaultChangeLogKey: filepath.Join(rootDir, DefaultChangeLogFilename)}, + DefaultChangeLogs: []string{DefaultChangeLogKey}, + ChlogsDir: filepath.Join(rootDir, DefaultChlogsDir), + TemplateYAML: filepath.Join(rootDir, DefaultChlogsDir, DefaultTemplateYAML), } } -func NewFromFile(rootDir string, filename string) (*Config, error) { - cfg := New(rootDir) - cfg.ConfigYAML = filepath.Clean(filepath.Join(rootDir, filename)) - cfgBytes, err := os.ReadFile(cfg.ConfigYAML) +func NewFromFile(rootDir string, cfgFilename string) (*Config, error) { + cfgYAML := filepath.Clean(filepath.Join(rootDir, cfgFilename)) + cfgBytes, err := os.ReadFile(cfgYAML) if err != nil { return nil, err } + cfg := &Config{} if err = yaml.Unmarshal(cfgBytes, &cfg); err != nil { return nil, err } - // If the user specified any of the following, interpret as a relative path from rootDir - // (unless they specified an absolute path including rootDir) - if !strings.HasPrefix(cfg.ChangelogMD, rootDir) { - cfg.ChangelogMD = filepath.Join(rootDir, cfg.ChangelogMD) - } - if !strings.HasPrefix(cfg.ChlogsDir, rootDir) { + cfg.ConfigYAML = cfgYAML + if cfg.ChlogsDir == "" { + cfg.ChlogsDir = filepath.Join(rootDir, DefaultChlogsDir) + } else if !strings.HasPrefix(cfg.ChlogsDir, rootDir) { cfg.ChlogsDir = filepath.Join(rootDir, cfg.ChlogsDir) } - if !strings.HasPrefix(cfg.TemplateYAML, rootDir) { + + if cfg.TemplateYAML == "" { + cfg.TemplateYAML = filepath.Join(rootDir, DefaultChlogsDir, DefaultTemplateYAML) + } else if !strings.HasPrefix(cfg.TemplateYAML, rootDir) { cfg.TemplateYAML = filepath.Join(rootDir, cfg.TemplateYAML) } + + if len(cfg.ChangeLogs) == 0 && len(cfg.DefaultChangeLogs) > 0 { + return nil, errors.New("cannot specify 'default_changelogs' without 'changelogs'") + } + + if len(cfg.ChangeLogs) == 0 { + cfg.ChangeLogs[DefaultChangeLogKey] = filepath.Join(rootDir, DefaultChangeLogFilename) + cfg.DefaultChangeLogs = []string{DefaultChangeLogKey} + return cfg, nil + } + + // The user specified at least one changelog. Interpret filename as a relative path from rootDir + // (unless they specified an absolute path including rootDir) + for key, filename := range cfg.ChangeLogs { + if !strings.HasPrefix(filename, rootDir) { + cfg.ChangeLogs[key] = filepath.Join(rootDir, filename) + } + } + + for _, key := range cfg.DefaultChangeLogs { + if _, ok := cfg.ChangeLogs[key]; !ok { + return nil, fmt.Errorf("'default_changelogs' contains key %q which is not defined in 'changelogs'", key) + } + } + return cfg, nil } diff --git a/chloggen/internal/config/config.tmpl b/chloggen/internal/config/config.yaml similarity index 60% rename from chloggen/internal/config/config.tmpl rename to chloggen/internal/config/config.yaml index b448a5b3..9740a3fc 100644 --- a/chloggen/internal/config/config.tmpl +++ b/chloggen/internal/config/config.yaml @@ -7,11 +7,17 @@ # (Optional) Default: .chloggen # chlogs_dir: -# The CHANGELOG file to which 'chloggen update' will write new entries -# (Optional) Default: CHANGELOG.md -# changelog_md: - # This file is used as the input for individul changelog entries. # Specify as relative path from root of repo. # (Optional) Default: .chloggen/TEMPLATE.yaml # template_yaml: + +# The CHANGELOG file or files to which 'chloggen update' will write new entries +# (Optional) Default filename: CHANGELOG.md +# change_logs: +# default: CHANGELOG.md + +# The default change_log or change_logs to which an entry should be added. +# If 'change_logs' is specified in this file, and no value is specified for 'default_change_logs', +# then 'change_logs' MUST be specified in every entry file. +# default_change_logs: [] diff --git a/chloggen/internal/config/config_test.go b/chloggen/internal/config/config_test.go index 88ee95ee..cb00a6c2 100644 --- a/chloggen/internal/config/config_test.go +++ b/chloggen/internal/config/config_test.go @@ -27,34 +27,136 @@ import ( func TestNew(t *testing.T) { root := "/tmp" cfg := New(root) - assert.Equal(t, filepath.Join(root, DefaultChloggenDir), cfg.ChlogsDir) - assert.Equal(t, filepath.Join(root, DefaultChangelogMD), cfg.ChangelogMD) - assert.Equal(t, filepath.Join(root, DefaultChloggenDir, DefaultTemplateYAML), cfg.TemplateYAML) + assert.Equal(t, filepath.Join(root, DefaultChlogsDir), cfg.ChlogsDir) + assert.Equal(t, filepath.Join(root, DefaultChlogsDir, DefaultTemplateYAML), cfg.TemplateYAML) + + assert.Equal(t, 1, len(cfg.ChangeLogs)) + assert.NotNil(t, cfg.ChangeLogs[DefaultChangeLogKey]) + assert.Equal(t, filepath.Join(root, DefaultChangeLogFilename), cfg.ChangeLogs[DefaultChangeLogKey]) + + assert.Equal(t, 1, len(cfg.DefaultChangeLogs)) + assert.Equal(t, DefaultChangeLogKey, cfg.DefaultChangeLogs[0]) } func TestNewFromFile(t *testing.T) { - tempDir := t.TempDir() + testCases := []struct { + name string + cfg *Config + expectErr string + }{ + { + name: "empty", + cfg: &Config{}, + }, + { + name: "multi-changelog-no-default", + cfg: &Config{ + ChlogsDir: ".test", + TemplateYAML: "TEMPLATE-custom.yaml", + ChangeLogs: map[string]string{ + "foo": "CHANGELOG-1.md", + "bar": "CHANGELOG-2.md", + }, + }, + }, + { + name: "multi-changelog-with-default", + cfg: &Config{ + ChlogsDir: ".test", + TemplateYAML: "TEMPLATE-custom.yaml", + ChangeLogs: map[string]string{ + "foo": "CHANGELOG-1.md", + "bar": "CHANGELOG-2.md", + }, + DefaultChangeLogs: []string{"foo"}, + }, + }, + { + name: "default-changelogs-without-changelogs", + cfg: &Config{ + ChlogsDir: ".test", + TemplateYAML: "TEMPLATE-custom.yaml", + DefaultChangeLogs: []string{"foo"}, + }, + expectErr: "cannot specify 'default_changelogs' without 'changelogs", + }, + { + name: "default-changelog-not-in-changelogs", + cfg: &Config{ + ChlogsDir: ".test", + TemplateYAML: "TEMPLATE-custom.yaml", + ChangeLogs: map[string]string{ + "foo": "CHANGELOG-1.md", + "bar": "CHANGELOG-2.md", + }, + DefaultChangeLogs: []string{"foo", "bar", "fake"}, + }, + expectErr: `contains key "fake" which is not defined in 'changelogs'`, + }, + } - cfg := New(tempDir) - cfg.ChlogsDir = ".test" - cfg.ChangelogMD = "CHANGELOG-custom.md" - cfg.TemplateYAML = "TEMPLATE-custom.yaml" + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + tempDir := t.TempDir() - cfgBytes, err := yaml.Marshal(cfg) - require.NoError(t, err) + cfgBytes, err := yaml.Marshal(tc.cfg) + require.NoError(t, err) - cfgFile, err := os.CreateTemp(tempDir, "*.yaml") - require.NoError(t, err) - defer cfgFile.Close() + cfgFile, err := os.CreateTemp(tempDir, "*.yaml") + require.NoError(t, err) + defer cfgFile.Close() - _, err = cfgFile.Write(cfgBytes) - require.NoError(t, err) + _, err = cfgFile.Write(cfgBytes) + require.NoError(t, err) + + actualCfg, err := NewFromFile(tempDir, filepath.Base(cfgFile.Name())) + if tc.expectErr != "" { + require.ErrorContains(t, err, tc.expectErr) + return + } + assert.NoError(t, err) + + // This would be the default config is no values were specified in the config file. + // Instantiate it here to compare against the actual config as appropriate. + defaultCfg := New(tempDir) + + expectedChlogsDir := defaultCfg.ChlogsDir + if tc.cfg.ChlogsDir != "" { + expectedChlogsDir = filepath.Join(tempDir, tc.cfg.ChlogsDir) + } + assert.Equal(t, expectedChlogsDir, actualCfg.ChlogsDir) + + expectedTeamplateYAML := defaultCfg.TemplateYAML + if tc.cfg.TemplateYAML != "" { + expectedTeamplateYAML = filepath.Join(tempDir, tc.cfg.TemplateYAML) + } + assert.Equal(t, expectedTeamplateYAML, actualCfg.TemplateYAML) + + if len(tc.cfg.ChangeLogs) == 0 { + assert.Equal(t, 1, len(actualCfg.ChangeLogs)) + assert.NotNil(t, actualCfg.ChangeLogs[DefaultChangeLogKey]) + assert.Equal(t, filepath.Join(tempDir, DefaultChangeLogFilename), actualCfg.ChangeLogs[DefaultChangeLogKey]) + + // When no changelogs are specified, the default changelog must be the only default changelog. + assert.Equal(t, 1, len(actualCfg.DefaultChangeLogs)) + assert.Equal(t, DefaultChangeLogKey, actualCfg.DefaultChangeLogs[0]) + } else { + assert.Equal(t, len(tc.cfg.ChangeLogs), len(actualCfg.ChangeLogs)) + for key, filename := range tc.cfg.ChangeLogs { + assert.NotNil(t, actualCfg.ChangeLogs[key]) + assert.Equal(t, filepath.Join(tempDir, filename), actualCfg.ChangeLogs[key]) + } + + // When changelogs are specified, the default changelogs must be a subset of the changelogs. + // It is acceptable to have no default changelog. + assert.Equal(t, len(tc.cfg.DefaultChangeLogs), len(actualCfg.DefaultChangeLogs)) + } - actualCfg, err := NewFromFile(tempDir, filepath.Base(cfgFile.Name())) - assert.NoError(t, err) - assert.Equal(t, filepath.Join(tempDir, ".test"), actualCfg.ChlogsDir) - assert.Equal(t, filepath.Join(tempDir, "CHANGELOG-custom.md"), actualCfg.ChangelogMD) - assert.Equal(t, filepath.Join(tempDir, "TEMPLATE-custom.yaml"), actualCfg.TemplateYAML) + for _, key := range actualCfg.DefaultChangeLogs { + assert.NotNil(t, actualCfg.ChangeLogs[key]) + } + }) + } } func TestNewFromFileErr(t *testing.T) {