You've already forked goreleaser
							
							
				mirror of
				https://github.com/goreleaser/goreleaser.git
				synced 2025-10-30 23:58:09 +02:00 
			
		
		
		
	feat: template release notes (#2242)
Signed-off-by: Carlos A Becker <caarlos0@gmail.com>
This commit is contained in:
		
				
					committed by
					
						 GitHub
						GitHub
					
				
			
			
				
	
			
			
			
						parent
						
							2d136c4c90
						
					
				
				
					commit
					9913fe7db6
				
			| @@ -19,18 +19,21 @@ type releaseCmd struct { | ||||
| } | ||||
|  | ||||
| type releaseOpts struct { | ||||
| 	config        string | ||||
| 	releaseNotes  string | ||||
| 	releaseHeader string | ||||
| 	releaseFooter string | ||||
| 	snapshot      bool | ||||
| 	skipPublish   bool | ||||
| 	skipSign      bool | ||||
| 	skipValidate  bool | ||||
| 	rmDist        bool | ||||
| 	deprecated    bool | ||||
| 	parallelism   int | ||||
| 	timeout       time.Duration | ||||
| 	config            string | ||||
| 	releaseNotesFile  string | ||||
| 	releaseNotesTmpl  string | ||||
| 	releaseHeaderFile string | ||||
| 	releaseHeaderTmpl string | ||||
| 	releaseFooterFile string | ||||
| 	releaseFooterTmpl string | ||||
| 	snapshot          bool | ||||
| 	skipPublish       bool | ||||
| 	skipSign          bool | ||||
| 	skipValidate      bool | ||||
| 	rmDist            bool | ||||
| 	deprecated        bool | ||||
| 	parallelism       int | ||||
| 	timeout           time.Duration | ||||
| } | ||||
|  | ||||
| func newReleaseCmd() *releaseCmd { | ||||
| @@ -63,9 +66,12 @@ func newReleaseCmd() *releaseCmd { | ||||
| 	} | ||||
|  | ||||
| 	cmd.Flags().StringVarP(&root.opts.config, "config", "f", "", "Load configuration from file") | ||||
| 	cmd.Flags().StringVar(&root.opts.releaseNotes, "release-notes", "", "Load custom release notes from a markdown file") | ||||
| 	cmd.Flags().StringVar(&root.opts.releaseHeader, "release-header", "", "Load custom release notes header from a markdown file") | ||||
| 	cmd.Flags().StringVar(&root.opts.releaseFooter, "release-footer", "", "Load custom release notes footer from a markdown file") | ||||
| 	cmd.Flags().StringVar(&root.opts.releaseNotesFile, "release-notes", "", "Load custom release notes from a markdown file") | ||||
| 	cmd.Flags().StringVar(&root.opts.releaseHeaderFile, "release-header", "", "Load custom release notes header from a markdown file") | ||||
| 	cmd.Flags().StringVar(&root.opts.releaseFooterFile, "release-footer", "", "Load custom release notes footer from a markdown file") | ||||
| 	cmd.Flags().StringVar(&root.opts.releaseNotesTmpl, "release-notes-tmpl", "", "Load custom release notes from a templated markdown file (overrides --release-notes)") | ||||
| 	cmd.Flags().StringVar(&root.opts.releaseHeaderTmpl, "release-header-tmpl", "", "Load custom release notes header from a templated markdown file (overrides --release-header)") | ||||
| 	cmd.Flags().StringVar(&root.opts.releaseFooterTmpl, "release-footer-tmpl", "", "Load custom release notes footer from a templated markdown file (overrides --release-footer)") | ||||
| 	cmd.Flags().BoolVar(&root.opts.snapshot, "snapshot", false, "Generate an unversioned snapshot release, skipping all validations and without publishing any artifacts") | ||||
| 	cmd.Flags().BoolVar(&root.opts.skipPublish, "skip-publish", false, "Skips publishing artifacts") | ||||
| 	cmd.Flags().BoolVar(&root.opts.skipSign, "skip-sign", false, "Skips signing the artifacts") | ||||
| @@ -108,9 +114,12 @@ func setupReleaseContext(ctx *context.Context, options releaseOpts) *context.Con | ||||
| 		ctx.Parallelism = options.parallelism | ||||
| 	} | ||||
| 	log.Debugf("parallelism: %v", ctx.Parallelism) | ||||
| 	ctx.ReleaseNotes = options.releaseNotes | ||||
| 	ctx.ReleaseHeader = options.releaseHeader | ||||
| 	ctx.ReleaseFooter = options.releaseFooter | ||||
| 	ctx.ReleaseNotesFile = options.releaseNotesFile | ||||
| 	ctx.ReleaseNotesTmpl = options.releaseNotesTmpl | ||||
| 	ctx.ReleaseHeaderFile = options.releaseHeaderFile | ||||
| 	ctx.ReleaseHeaderTmpl = options.releaseHeaderTmpl | ||||
| 	ctx.ReleaseFooterFile = options.releaseFooterFile | ||||
| 	ctx.ReleaseFooterTmpl = options.releaseFooterTmpl | ||||
| 	ctx.Snapshot = options.snapshot | ||||
| 	ctx.SkipPublish = ctx.Snapshot || options.skipPublish | ||||
| 	ctx.SkipValidate = ctx.Snapshot || options.skipValidate | ||||
|   | ||||
| @@ -67,13 +67,27 @@ func TestReleaseFlags(t *testing.T) { | ||||
| 		header := "header.md" | ||||
| 		footer := "footer.md" | ||||
| 		ctx := setup(releaseOpts{ | ||||
| 			releaseNotes:  notes, | ||||
| 			releaseHeader: header, | ||||
| 			releaseFooter: footer, | ||||
| 			releaseNotesFile:  notes, | ||||
| 			releaseHeaderFile: header, | ||||
| 			releaseFooterFile: footer, | ||||
| 		}) | ||||
| 		require.Equal(t, notes, ctx.ReleaseNotes) | ||||
| 		require.Equal(t, header, ctx.ReleaseHeader) | ||||
| 		require.Equal(t, footer, ctx.ReleaseFooter) | ||||
| 		require.Equal(t, notes, ctx.ReleaseNotesFile) | ||||
| 		require.Equal(t, header, ctx.ReleaseHeaderFile) | ||||
| 		require.Equal(t, footer, ctx.ReleaseFooterFile) | ||||
| 	}) | ||||
|  | ||||
| 	t.Run("templated notes", func(t *testing.T) { | ||||
| 		notes := "foo.md" | ||||
| 		header := "header.md" | ||||
| 		footer := "footer.md" | ||||
| 		ctx := setup(releaseOpts{ | ||||
| 			releaseNotesTmpl:  notes, | ||||
| 			releaseHeaderTmpl: header, | ||||
| 			releaseFooterTmpl: footer, | ||||
| 		}) | ||||
| 		require.Equal(t, notes, ctx.ReleaseNotesTmpl) | ||||
| 		require.Equal(t, header, ctx.ReleaseHeaderTmpl) | ||||
| 		require.Equal(t, footer, ctx.ReleaseFooterTmpl) | ||||
| 	}) | ||||
|  | ||||
| 	t.Run("rm dist", func(t *testing.T) { | ||||
|   | ||||
| @@ -29,21 +29,12 @@ func (Pipe) String() string { | ||||
|  | ||||
| // Run the pipe. | ||||
| func (Pipe) Run(ctx *context.Context) error { | ||||
| 	// TODO: should probably have a different field for the filename and its | ||||
| 	// contents. | ||||
| 	if ctx.ReleaseNotes != "" { | ||||
| 		notes, err := loadFromFile(ctx.ReleaseNotes) | ||||
| 		if err != nil { | ||||
| 			return err | ||||
| 		} | ||||
| 		notes, err = tmpl.New(ctx).Apply(notes) | ||||
| 		if err != nil { | ||||
| 			return err | ||||
| 		} | ||||
| 		log.WithField("file", ctx.ReleaseNotes).Info("loaded custom release notes") | ||||
| 		log.WithField("file", ctx.ReleaseNotes).Debugf("custom release notes: \n%s", notes) | ||||
| 		ctx.ReleaseNotes = notes | ||||
| 	notes, err := loadContent(ctx, ctx.ReleaseNotesFile, ctx.ReleaseNotesTmpl) | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 	ctx.ReleaseNotes = notes | ||||
|  | ||||
| 	if ctx.Config.Changelog.Skip { | ||||
| 		return pipe.ErrSkipDisabledPipe | ||||
| 	} | ||||
| @@ -53,27 +44,15 @@ func (Pipe) Run(ctx *context.Context) error { | ||||
| 	if ctx.ReleaseNotes != "" { | ||||
| 		return nil | ||||
| 	} | ||||
| 	if ctx.ReleaseHeader != "" { | ||||
| 		header, err := loadFromFile(ctx.ReleaseHeader) | ||||
| 		if err != nil { | ||||
| 			return err | ||||
| 		} | ||||
| 		header, err = tmpl.New(ctx).Apply(header) | ||||
| 		if err != nil { | ||||
| 			return err | ||||
| 		} | ||||
| 		ctx.ReleaseHeader = header | ||||
|  | ||||
| 	footer, err := loadContent(ctx, ctx.ReleaseFooterFile, ctx.ReleaseFooterTmpl) | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 	if ctx.ReleaseFooter != "" { | ||||
| 		footer, err := loadFromFile(ctx.ReleaseFooter) | ||||
| 		if err != nil { | ||||
| 			return err | ||||
| 		} | ||||
| 		footer, err = tmpl.New(ctx).Apply(footer) | ||||
| 		if err != nil { | ||||
| 			return err | ||||
| 		} | ||||
| 		ctx.ReleaseFooter = footer | ||||
|  | ||||
| 	header, err := loadContent(ctx, ctx.ReleaseHeaderFile, ctx.ReleaseHeaderTmpl) | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
|  | ||||
| 	if err := checkSortDirection(ctx.Config.Changelog.Sort); err != nil { | ||||
| @@ -97,11 +76,11 @@ func (Pipe) Run(ctx *context.Context) error { | ||||
| 		"## Changelog", | ||||
| 		strings.Join(entries, changelogStringJoiner), | ||||
| 	} | ||||
| 	if len(ctx.ReleaseHeader) > 0 { | ||||
| 		changelogElements = append([]string{ctx.ReleaseHeader}, changelogElements...) | ||||
| 	if header != "" { | ||||
| 		changelogElements = append([]string{header}, changelogElements...) | ||||
| 	} | ||||
| 	if len(ctx.ReleaseFooter) > 0 { | ||||
| 		changelogElements = append(changelogElements, ctx.ReleaseFooter) | ||||
| 	if footer != "" { | ||||
| 		changelogElements = append(changelogElements, footer) | ||||
| 	} | ||||
|  | ||||
| 	ctx.ReleaseNotes = strings.Join(changelogElements, "\n\n") | ||||
| @@ -225,3 +204,21 @@ var validSHA1 = regexp.MustCompile(`^[a-fA-F0-9]{40}$`) | ||||
| func isSHA1(ref string) bool { | ||||
| 	return validSHA1.MatchString(ref) | ||||
| } | ||||
|  | ||||
| func loadContent(ctx *context.Context, fileName, tmplName string) (string, error) { | ||||
| 	if tmplName != "" { | ||||
| 		log.Debugf("loading template %s", tmplName) | ||||
| 		content, err := loadFromFile(tmplName) | ||||
| 		if err != nil { | ||||
| 			return "", err | ||||
| 		} | ||||
| 		return tmpl.New(ctx).Apply(content) | ||||
| 	} | ||||
|  | ||||
| 	if fileName != "" { | ||||
| 		log.Debugf("loading file %s", fileName) | ||||
| 		return loadFromFile(fileName) | ||||
| 	} | ||||
|  | ||||
| 	return "", nil | ||||
| } | ||||
|   | ||||
| @@ -18,14 +18,15 @@ func TestDescription(t *testing.T) { | ||||
|  | ||||
| func TestChangelogProvidedViaFlag(t *testing.T) { | ||||
| 	ctx := context.New(config.Project{}) | ||||
| 	ctx.ReleaseNotes = "testdata/changes.md" | ||||
| 	ctx.ReleaseNotesFile = "testdata/changes.md" | ||||
| 	require.NoError(t, Pipe{}.Run(ctx)) | ||||
| 	require.Equal(t, "c0ff33 coffeee\n", ctx.ReleaseNotes) | ||||
| } | ||||
|  | ||||
| func TestTemplatedChangelogProvidedViaFlag(t *testing.T) { | ||||
| 	ctx := context.New(config.Project{}) | ||||
| 	ctx.ReleaseNotes = "testdata/changes-templated.md" | ||||
| 	ctx.ReleaseNotesFile = "testdata/changes.md" | ||||
| 	ctx.ReleaseNotesTmpl = "testdata/changes-templated.md" | ||||
| 	ctx.Git.CurrentTag = "v0.0.1" | ||||
| 	require.NoError(t, Pipe{}.Run(ctx)) | ||||
| 	require.Equal(t, "c0ff33 coffeee v0.0.1\n", ctx.ReleaseNotes) | ||||
| @@ -37,14 +38,14 @@ func TestChangelogProvidedViaFlagAndSkipEnabled(t *testing.T) { | ||||
| 			Skip: true, | ||||
| 		}, | ||||
| 	}) | ||||
| 	ctx.ReleaseNotes = "testdata/changes.md" | ||||
| 	ctx.ReleaseNotesFile = "testdata/changes.md" | ||||
| 	testlib.AssertSkipped(t, Pipe{}.Run(ctx)) | ||||
| 	require.Equal(t, "c0ff33 coffeee\n", ctx.ReleaseNotes) | ||||
| } | ||||
|  | ||||
| func TestChangelogProvidedViaFlagDoesntExist(t *testing.T) { | ||||
| 	ctx := context.New(config.Project{}) | ||||
| 	ctx.ReleaseNotes = "testdata/changes.nope" | ||||
| 	ctx.ReleaseNotesFile = "testdata/changes.nope" | ||||
| 	require.EqualError(t, Pipe{}.Run(ctx), "open testdata/changes.nope: no such file or directory") | ||||
| } | ||||
|  | ||||
| @@ -56,13 +57,13 @@ func TestChangelogSkip(t *testing.T) { | ||||
|  | ||||
| func TestReleaseHeaderProvidedViaFlagDoesntExist(t *testing.T) { | ||||
| 	ctx := context.New(config.Project{}) | ||||
| 	ctx.ReleaseHeader = "testdata/header.nope" | ||||
| 	ctx.ReleaseHeaderFile = "testdata/header.nope" | ||||
| 	require.EqualError(t, Pipe{}.Run(ctx), "open testdata/header.nope: no such file or directory") | ||||
| } | ||||
|  | ||||
| func TestReleaseFooterProvidedViaFlagDoesntExist(t *testing.T) { | ||||
| 	ctx := context.New(config.Project{}) | ||||
| 	ctx.ReleaseFooter = "testdata/footer.nope" | ||||
| 	ctx.ReleaseFooterFile = "testdata/footer.nope" | ||||
| 	require.EqualError(t, Pipe{}.Run(ctx), "open testdata/footer.nope: no such file or directory") | ||||
| } | ||||
|  | ||||
| @@ -341,7 +342,7 @@ func TestChangeLogWithReleaseHeader(t *testing.T) { | ||||
| 	testlib.GitCheckoutBranch(t, "v0.0.1") | ||||
| 	ctx := context.New(config.Project{}) | ||||
| 	ctx.Git.CurrentTag = "v0.0.1" | ||||
| 	ctx.ReleaseHeader = "testdata/release-header.md" | ||||
| 	ctx.ReleaseHeaderFile = "testdata/release-header.md" | ||||
| 	require.NoError(t, Pipe{}.Run(ctx)) | ||||
| 	require.Contains(t, ctx.ReleaseNotes, "## Changelog") | ||||
| 	require.Contains(t, ctx.ReleaseNotes, "test header") | ||||
| @@ -366,7 +367,7 @@ func TestChangeLogWithTemplatedReleaseHeader(t *testing.T) { | ||||
| 	testlib.GitCheckoutBranch(t, "v0.0.1") | ||||
| 	ctx := context.New(config.Project{}) | ||||
| 	ctx.Git.CurrentTag = "v0.0.1" | ||||
| 	ctx.ReleaseHeader = "testdata/release-header-templated.md" | ||||
| 	ctx.ReleaseHeaderTmpl = "testdata/release-header-templated.md" | ||||
| 	require.NoError(t, Pipe{}.Run(ctx)) | ||||
| 	require.Contains(t, ctx.ReleaseNotes, "## Changelog") | ||||
| 	require.Contains(t, ctx.ReleaseNotes, "test header with tag v0.0.1") | ||||
| @@ -391,7 +392,7 @@ func TestChangeLogWithReleaseFooter(t *testing.T) { | ||||
| 	testlib.GitCheckoutBranch(t, "v0.0.1") | ||||
| 	ctx := context.New(config.Project{}) | ||||
| 	ctx.Git.CurrentTag = "v0.0.1" | ||||
| 	ctx.ReleaseFooter = "testdata/release-footer.md" | ||||
| 	ctx.ReleaseFooterFile = "testdata/release-footer.md" | ||||
| 	require.NoError(t, Pipe{}.Run(ctx)) | ||||
| 	require.Contains(t, ctx.ReleaseNotes, "## Changelog") | ||||
| 	require.Contains(t, ctx.ReleaseNotes, "test footer") | ||||
| @@ -417,7 +418,7 @@ func TestChangeLogWithTemplatedReleaseFooter(t *testing.T) { | ||||
| 	testlib.GitCheckoutBranch(t, "v0.0.1") | ||||
| 	ctx := context.New(config.Project{}) | ||||
| 	ctx.Git.CurrentTag = "v0.0.1" | ||||
| 	ctx.ReleaseFooter = "testdata/release-footer-templated.md" | ||||
| 	ctx.ReleaseFooterTmpl = "testdata/release-footer-templated.md" | ||||
| 	require.NoError(t, Pipe{}.Run(ctx)) | ||||
| 	require.Contains(t, ctx.ReleaseNotes, "## Changelog") | ||||
| 	require.Contains(t, ctx.ReleaseNotes, "test footer with tag v0.0.1") | ||||
|   | ||||
| @@ -73,8 +73,12 @@ type Context struct { | ||||
| 	Date               time.Time | ||||
| 	Artifacts          artifact.Artifacts | ||||
| 	ReleaseNotes       string | ||||
| 	ReleaseHeader      string | ||||
| 	ReleaseFooter      string | ||||
| 	ReleaseNotesFile   string | ||||
| 	ReleaseNotesTmpl   string | ||||
| 	ReleaseHeaderFile  string | ||||
| 	ReleaseHeaderTmpl  string | ||||
| 	ReleaseFooterFile  string | ||||
| 	ReleaseFooterTmpl  string | ||||
| 	Version            string | ||||
| 	ModulePath         string | ||||
| 	Snapshot           bool | ||||
|   | ||||
| @@ -9,18 +9,21 @@ goreleaser release [flags] | ||||
| ## Options | ||||
|  | ||||
| ``` | ||||
|   -f, --config string           Load configuration from file | ||||
|   -h, --help                    help for release | ||||
|   -p, --parallelism int         Amount tasks to run concurrently (default: number of CPUs) | ||||
|       --release-footer string   Load custom release notes footer from a markdown file | ||||
|       --release-header string   Load custom release notes header from a markdown file | ||||
|       --release-notes string    Load custom release notes from a markdown file | ||||
|       --rm-dist                 Remove the dist folder before building | ||||
|       --skip-publish            Skips publishing artifacts | ||||
|       --skip-sign               Skips signing the artifacts | ||||
|       --skip-validate           Skips several sanity checks | ||||
|       --snapshot                Generate an unversioned snapshot release, skipping all validations and without publishing any artifacts | ||||
|       --timeout duration        Timeout to the entire release process (default 30m0s) | ||||
|   -f, --config string                Load configuration from file | ||||
|   -h, --help                         help for release | ||||
|   -p, --parallelism int              Amount tasks to run concurrently (default: number of CPUs) | ||||
|       --release-footer string        Load custom release notes footer from a markdown file | ||||
|       --release-footer-tmpl string   Load custom release notes footer from a templated markdown file (overrides --release-footer) | ||||
|       --release-header string        Load custom release notes header from a markdown file | ||||
|       --release-header-tmpl string   Load custom release notes header from a templated markdown file (overrides --release-header) | ||||
|       --release-notes string         Load custom release notes from a markdown file | ||||
|       --release-notes-tmpl string    Load custom release notes from a templated markdown file (overrides --release-notes) | ||||
|       --rm-dist                      Remove the dist folder before building | ||||
|       --skip-publish                 Skips publishing artifacts | ||||
|       --skip-sign                    Skips signing the artifacts | ||||
|       --skip-validate                Skips several sanity checks | ||||
|       --snapshot                     Generate an unversioned snapshot release, skipping all validations and without publishing any artifacts | ||||
|       --timeout duration             Timeout to the entire release process (default 30m0s) | ||||
| ``` | ||||
|  | ||||
| ## Options inherited from parent commands | ||||
|   | ||||
		Reference in New Issue
	
	Block a user