Repository: goreleaser/goreleaser
Pull request #3091 (2022-05-09 10:26:09)

docs: add example for semantic-release
The commit will add an example of the use of sematic-release in the documentation (https://github.com/semantic-release/semantic-release).

The solution in the issue https://github.com/goreleaser/goreleaser/issues/910 was not documented.


Patch:
diff --git a/go.mod b/go.mod
index eaecfaf0..b580b821 100644
--- a/go.mod
+++ b/go.mod
@@ -23,7 +23,7 @@ require (
 	github.com/goreleaser/fileglob v1.3.0
 	github.com/goreleaser/nfpm/v2 v2.15.1
 	github.com/imdario/mergo v0.3.12
-	github.com/jarcoal/httpmock v1.1.0
+	github.com/jarcoal/httpmock v1.2.0
 	github.com/klauspost/pgzip v1.2.5
 	github.com/mitchellh/go-homedir v1.1.0
 	github.com/muesli/mango-cobra v1.1.0
diff --git a/go.sum b/go.sum
index 2995fc3a..1fb57b62 100644
--- a/go.sum
+++ b/go.sum
@@ -476,8 +476,8 @@ github.com/imdario/mergo v0.3.12 h1:b6R2BslTbIEToALKP7LxUvijTsNI9TAe80pLWN2g/HU=
 github.com/imdario/mergo v0.3.12/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA=
 github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM=
 github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
-github.com/jarcoal/httpmock v1.1.0 h1:F47ChZj1Y2zFsCXxNkBPwNNKnAyOATcdQibk0qEdVCE=
-github.com/jarcoal/httpmock v1.1.0/go.mod h1:ATjnClrvW/3tijVmpL/va5Z3aAyGvqU3gCT8nX0Txik=
+github.com/jarcoal/httpmock v1.2.0 h1:gSvTxxFR/MEMfsGrvRbdfpRUMBStovlSRLw0Ep1bwwc=
+github.com/jarcoal/httpmock v1.2.0/go.mod h1:oCoTsnAz4+UoOUIf5lJOWV2QQIW5UoeUI6aM2YnWAZk=
 github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOlocH6Fxy8MmwDt+yVQYULKfN0RoTN8A=
 github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo=
 github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=
@@ -541,6 +541,7 @@ github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Ky
 github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y=
 github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
 github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
+github.com/maxatome/go-testdeep v1.11.0 h1:Tgh5efyCYyJFGUYiT0qxBSIDeXw0F5zSoatlou685kk=
 github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b/go.mod h1:01TrycV0kFyexm33Z7vhZRXopbI8J3TDReVlkTgMUxE=
 github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg=
 github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc=
diff --git a/internal/git/config.go b/internal/git/config.go
index 9b9943b2..2987ebd5 100644
--- a/internal/git/config.go
+++ b/internal/git/config.go
@@ -16,7 +16,7 @@ func ExtractRepoFromConfig(ctx context.Context) (result config.Repo, err error)
 	if !IsRepo(ctx) {
 		return result, errors.New("current folder is not a git repository")
 	}
-	out, err := Run(ctx, "ls-remote", "--get-url")
+	out, err := Clean(Run(ctx, "ls-remote", "--get-url"))
 	if err != nil {
 		return result, fmt.Errorf("no remote configured to list refs from")
 	}
@@ -44,19 +44,32 @@ func ExtractRepoFromURL(rawurl string) (config.Repo, error) {
 	// now we can parse it with net/url
 	u, err := url.Parse(s)
 	if err != nil {
-		return config.Repo{}, err
+		return config.Repo{
+			RawURL: rawurl,
+		}, err
 	}
 
 	// split the parsed url path by /, the last parts should be the owner and name
 	ss := strings.Split(strings.TrimPrefix(u.Path, "/"), "/")
 
-	// if less than 2 parts, its likely not a valid repository
+	// if empty, returns an error
+	if len(ss) == 0 || ss[0] == "" {
+		return config.Repo{
+			RawURL: rawurl,
+		}, fmt.Errorf("unsupported repository URL: %s", rawurl)
+	}
+
+	// if less than 2 parts, its likely not a valid repository, but we'll allow it.
 	if len(ss) < 2 {
-		return config.Repo{}, fmt.Errorf("unsupported repository URL: %s", rawurl)
+		return config.Repo{
+			RawURL: rawurl,
+			Owner:  ss[0],
+		}, nil
 	}
 	repo := config.Repo{
-		Owner: strings.Join(ss[:len(ss)-1], "/"),
-		Name:  ss[len(ss)-1],
+		RawURL: rawurl,
+		Owner:  strings.Join(ss[:len(ss)-1], "/"),
+		Name:   ss[len(ss)-1],
 	}
 	log.WithField("owner", repo.Owner).WithField("name", repo.Name).Debugf("parsed url")
 	return repo, nil
diff --git a/internal/pipe/changelog/changelog.go b/internal/pipe/changelog/changelog.go
index 5eb9af14..8c3997a6 100644
--- a/internal/pipe/changelog/changelog.go
+++ b/internal/pipe/changelog/changelog.go
@@ -287,6 +287,9 @@ func newGithubChangeloger(ctx *context.Context) (changeloger, error) {
 	if err != nil {
 		return nil, err
 	}
+	if err := repo.CheckSCM(); err != nil {
+		return nil, err
+	}
 	return &githubNativeChangeloger{
 		client: cli,
 		repo: client.Repo{
@@ -305,6 +308,9 @@ func newSCMChangeloger(ctx *context.Context) (changeloger, error) {
 	if err != nil {
 		return nil, err
 	}
+	if err := repo.CheckSCM(); err != nil {
+		return nil, err
+	}
 	return &scmChangeloger{
 		client: cli,
 		repo: client.Repo{
diff --git a/internal/pipe/milestone/milestone.go b/internal/pipe/milestone/milestone.go
index 05c05c24..ee8526b3 100644
--- a/internal/pipe/milestone/milestone.go
+++ b/internal/pipe/milestone/milestone.go
@@ -29,10 +29,12 @@ func (Pipe) Default(ctx *context.Context) error {
 
 		if milestone.Repo.Name == "" {
 			repo, err := git.ExtractRepoFromConfig(ctx)
-
 			if err != nil && !ctx.Snapshot {
 				return err
 			}
+			if err := repo.CheckSCM(); err != nil && !ctx.Snapshot {
+				return err
+			}
 
 			milestone.Repo = repo
 		}
diff --git a/internal/pipe/release/release.go b/internal/pipe/release/release.go
index 8208d331..b5a3d9c8 100644
--- a/internal/pipe/release/release.go
+++ b/internal/pipe/release/release.go
@@ -12,6 +12,7 @@ import (
 	"github.com/goreleaser/goreleaser/internal/extrafiles"
 	"github.com/goreleaser/goreleaser/internal/git"
 	"github.com/goreleaser/goreleaser/internal/semerrgroup"
+	"github.com/goreleaser/goreleaser/pkg/config"
 	"github.com/goreleaser/goreleaser/pkg/context"
 )
 
@@ -48,7 +49,7 @@ func (Pipe) Default(ctx *context.Context) error {
 	switch ctx.TokenType {
 	case context.TokenTypeGitLab:
 		if ctx.Config.Release.GitLab.Name == "" {
-			repo, err := git.ExtractRepoFromConfig(ctx)
+			repo, err := getRepository(ctx)
 			if err != nil {
 				return err
 			}
@@ -63,7 +64,7 @@ func (Pipe) Default(ctx *context.Context) error {
 		)
 	case context.TokenTypeGitea:
 		if ctx.Config.Release.Gitea.Name == "" {
-			repo, err := git.ExtractRepoFromConfig(ctx)
+			repo, err := getRepository(ctx)
 			if err != nil {
 				return err
 			}
@@ -79,7 +80,7 @@ func (Pipe) Default(ctx *context.Context) error {
 	default:
 		// We keep github as default for now
 		if ctx.Config.Release.GitHub.Name == "" {
-			repo, err := git.ExtractRepoFromConfig(ctx)
+			repo, err := getRepository(ctx)
 			if err != nil && !ctx.Snapshot {
 				return err
 			}
@@ -109,6 +110,17 @@ func (Pipe) Default(ctx *context.Context) error {
 	return nil
 }
 
+func getRepository(ctx *context.Context) (config.Repo, error) {
+	repo, err := git.ExtractRepoFromConfig(ctx)
+	if err != nil {
+		return config.Repo{}, err
+	}
+	if err := repo.CheckSCM(); err != nil {
+		return config.Repo{}, err
+	}
+	return repo, nil
+}
+
 // Publish the release.
 func (Pipe) Publish(ctx *context.Context) error {
 	c, err := client.New(ctx)
diff --git a/pkg/config/config.go b/pkg/config/config.go
index f3720dce..82b1a3ee 100644
--- a/pkg/config/config.go
+++ b/pkg/config/config.go
@@ -4,6 +4,7 @@ package config
 
 import (
 	"encoding/json"
+	"fmt"
 	"io"
 	"os"
 	"strings"
@@ -42,16 +43,30 @@ type GiteaURLs struct {
 // Repo represents any kind of repo (github, gitlab, etc).
 // to upload releases into.
 type Repo struct {
-	Owner string `yaml:"owner,omitempty"`
-	Name  string `yaml:"name,omitempty"`
+	Owner  string `yaml:"owner,omitempty"`
+	Name   string `yaml:"name,omitempty"`
+	RawURL string `yaml:"-"`
 }
 
 // String of the repo, e.g. owner/name.
 func (r Repo) String() string {
-	if r.Owner == "" && r.Name == "" {
-		return ""
+	if r.isSCM() {
+		return r.Owner + "/" + r.Name
 	}
-	return r.Owner + "/" + r.Name
+	return r.Owner
+}
+
+// CheckSCM returns an error if the given url is not a valid scm url.
+func (r Repo) CheckSCM() error {
+	if r.isSCM() {
+		return nil
+	}
+	return fmt.Errorf("invalid scm url: %s", r.RawURL)
+}
+
+// isSCM returns true if the repo has both an owner and name.
+func (r Repo) isSCM() bool {
+	return r.Owner != "" && r.Name != ""
 }
 
 // RepoRef represents any kind of repo which may differ
diff --git a/www/docs/cookbooks/semantic-release.md b/www/docs/cookbooks/semantic-release.md
index 50cd1fae..19a614d6 100644
--- a/www/docs/cookbooks/semantic-release.md
+++ b/www/docs/cookbooks/semantic-release.md
@@ -3,10 +3,33 @@
 GoReleaser does not create any tags, it just runs on what is already there.
 
 You can, though, leverage other tools to do the work for you, like for example
-[svu](https://github.com/caarlos0/svu):
+[svu](https://github.com/caarlos0/svu) or [semantic-release](https://github.com/semantic-release/semantic-release).
+
+## Example: svu
 
 ```bash
 git tag "$(svu next)"
 git push --tags
 goreleaser --rm-dist
 ```
+
+## Example: semantic-release
+
+.releaserc.yml
+
+```yaml
+preset: angular
+plugins:
+  - "@semantic-release/commit-analyzer"
+  - "@semantic-release/release-notes-generator"
+  - "@semantic-release/changelog"
+  - "@semantic-release/git"
+  - - "@semantic-release/exec"
+    - publishCmd: |
+        echo "${nextRelease.notes}" > /tmp/release-notes.md
+        goreleaser release --release-notes /tmp/release-notes.md --rm-dist
+```
+
+```bash
+npx -p @semantic-release/changelog -p @semantic-release/exec -p @semantic-release/git semantic-release
+```
diff --git a/www/docs/overrides/home.html b/www/docs/overrides/home.html
index 87c600a9..338c3861 100644
--- a/www/docs/overrides/home.html
+++ b/www/docs/overrides/home.html
@@ -134,8 +134,9 @@
 					<li>Create nightly builds</li>
 					<li>Create Docker images and manifests</li>
 					<li>Create Linux packages and Homebrew taps</li>
-					<li>Sign artifacts, checksums and Docker images</li>
+					<li>Sign artifacts, checksums and container images</li>
 					<li>Announce new releases on Twitter, Slack, Discord and others</li>
+					<li>Generate SBOMs (Software Bill of Materials) for binaries and container images</li> 
 					<li>... and much more!</li>
 				</ul>
 				<a href="{{ page.next_page.url | url }}" title="{{ page.next_page.title | striptags }}"
diff --git a/www/docs/static/schema.json b/www/docs/static/schema.json
index 342d8a11..488cd4e6 100644
--- a/www/docs/static/schema.json
+++ b/www/docs/static/schema.json
@@ -321,9 +321,15 @@
 					"gobinary": {
 						"type": "string"
 					},
+					"command": {
+						"type": "string"
+					},
 					"no_unique_dist_dir": {
 						"type": "boolean"
 					},
+					"no_main_check": {
+						"type": "boolean"
+					},
 					"ldflags": {
 						"oneOf": [
 							{


Test patch:
diff --git a/internal/git/config_test.go b/internal/git/config_test.go
index 1519c6c1..8de694ac 100644
--- a/internal/git/config_test.go
+++ b/internal/git/config_test.go
@@ -60,6 +60,8 @@ func TestExtractRepoFromURL(t *testing.T) {
 			repo, err := git.ExtractRepoFromURL(url)
 			require.NoError(t, err)
 			require.Equal(t, "goreleaser/goreleaser", repo.String())
+			require.NoError(t, repo.CheckSCM())
+			require.Equal(t, url, repo.RawURL)
 		})
 	}
 
@@ -73,18 +75,35 @@ func TestExtractRepoFromURL(t *testing.T) {
 			repo, err := git.ExtractRepoFromURL(url)
 			require.NoError(t, err)
 			require.Equal(t, "group/nested/goreleaser/goreleaser", repo.String())
+			require.NoError(t, repo.CheckSCM())
+			require.Equal(t, url, repo.RawURL)
 		})
 	}
 
-	// invalid urls
 	for _, url := range []string{
 		"git@gist.github.com:someid.git",
 		"https://gist.github.com/someid.git",
+	} {
+		t.Run(url, func(t *testing.T) {
+			repo, err := git.ExtractRepoFromURL(url)
+			require.NoError(t, err)
+			require.Equal(t, "someid", repo.String())
+			require.Error(t, repo.CheckSCM())
+			require.Equal(t, url, repo.RawURL)
+		})
+	}
+
+	// invalid urls
+	for _, url := range []string{
+		"git@gist.github.com:",
+		"https://gist.github.com/",
 	} {
 		t.Run(url, func(t *testing.T) {
 			repo, err := git.ExtractRepoFromURL(url)
 			require.EqualError(t, err, "unsupported repository URL: "+url)
 			require.Equal(t, "", repo.String())
+			require.Error(t, repo.CheckSCM())
+			require.Equal(t, url, repo.RawURL)
 		})
 	}
 }
diff --git a/internal/pipe/changelog/changelog_test.go b/internal/pipe/changelog/changelog_test.go
index 886b9fa2..776747dd 100644
--- a/internal/pipe/changelog/changelog_test.go
+++ b/internal/pipe/changelog/changelog_test.go
@@ -536,6 +536,21 @@ func TestGetChangeloger(t *testing.T) {
 		require.IsType(t, c, &githubNativeChangeloger{})
 	})
 
+	t.Run(useGitHubNative+"-invalid-repo", func(t *testing.T) {
+		testlib.Mktmp(t)
+		testlib.GitInit(t)
+		testlib.GitRemoteAdd(t, "https://gist.github.com/")
+		ctx := context.New(config.Project{
+			Changelog: config.Changelog{
+				Use: useGitHubNative,
+			},
+		})
+		ctx.TokenType = context.TokenTypeGitHub
+		c, err := getChangeloger(ctx)
+		require.EqualError(t, err, "unsupported repository URL: https://gist.github.com/")
+		require.Nil(t, c)
+	})
+
 	t.Run(useGitLab, func(t *testing.T) {
 		ctx := context.New(config.Project{
 			Changelog: config.Changelog{
@@ -548,6 +563,21 @@ func TestGetChangeloger(t *testing.T) {
 		require.IsType(t, c, &scmChangeloger{})
 	})
 
+	t.Run(useGitHub+"-invalid-repo", func(t *testing.T) {
+		testlib.Mktmp(t)
+		testlib.GitInit(t)
+		testlib.GitRemoteAdd(t, "https://gist.github.com/")
+		ctx := context.New(config.Project{
+			Changelog: config.Changelog{
+				Use: useGitHub,
+			},
+		})
+		ctx.TokenType = context.TokenTypeGitHub
+		c, err := getChangeloger(ctx)
+		require.EqualError(t, err, "unsupported repository URL: https://gist.github.com/")
+		require.Nil(t, c)
+	})
+
 	t.Run("invalid", func(t *testing.T) {
 		c, err := getChangeloger(context.New(config.Project{
 			Changelog: config.Changelog{
diff --git a/internal/pipe/milestone/milestone_test.go b/internal/pipe/milestone/milestone_test.go
index a15b41a9..1255143c 100644
--- a/internal/pipe/milestone/milestone_test.go
+++ b/internal/pipe/milestone/milestone_test.go
@@ -31,6 +31,18 @@ func TestDefaultWithRepoConfig(t *testing.T) {
 	require.Equal(t, "configowner", ctx.Config.Milestones[0].Repo.Owner)
 }
 
+func TestDefaultWithInvalidRemote(t *testing.T) {
+	testlib.Mktmp(t)
+	testlib.GitInit(t)
+	testlib.GitRemoteAdd(t, "git@github.com:githubowner.git")
+
+	ctx := context.New(config.Project{
+		Milestones: []config.Milestone{{}},
+	})
+	ctx.TokenType = context.TokenTypeGitHub
+	require.Error(t, Pipe{}.Default(ctx))
+}
+
 func TestDefaultWithRepoRemote(t *testing.T) {
 	testlib.Mktmp(t)
 	testlib.GitInit(t)
diff --git a/internal/pipe/release/release_test.go b/internal/pipe/release/release_test.go
index 6c29e9c8..2414954e 100644
--- a/internal/pipe/release/release_test.go
+++ b/internal/pipe/release/release_test.go
@@ -354,6 +354,18 @@ func TestDefault(t *testing.T) {
 	require.Equal(t, "https://github.com/goreleaser/goreleaser/releases/tag/v1.0.0", ctx.ReleaseURL)
 }
 
+func TestDefaultInvalidURL(t *testing.T) {
+	testlib.Mktmp(t)
+	testlib.GitInit(t)
+	testlib.GitRemoteAdd(t, "git@github.com:goreleaser.git")
+
+	ctx := context.New(config.Project{})
+	ctx.TokenType = context.TokenTypeGitHub
+	ctx.Config.GitHubURLs.Download = "https://github.com"
+	ctx.Git.CurrentTag = "v1.0.0"
+	require.Error(t, Pipe{}.Default(ctx))
+}
+
 func TestDefaultWithGitlab(t *testing.T) {
 	testlib.Mktmp(t)
 	testlib.GitInit(t)
@@ -369,6 +381,18 @@ func TestDefaultWithGitlab(t *testing.T) {
 	require.Equal(t, "https://gitlab.com/gitlabowner/gitlabrepo/-/releases/v1.0.0", ctx.ReleaseURL)
 }
 
+func TestDefaultWithGitlabInvalidURL(t *testing.T) {
+	testlib.Mktmp(t)
+	testlib.GitInit(t)
+	testlib.GitRemoteAdd(t, "git@gitlab.com:gitlabrepo.git")
+
+	ctx := context.New(config.Project{})
+	ctx.TokenType = context.TokenTypeGitLab
+	ctx.Config.GitLabURLs.Download = "https://gitlab.com"
+	ctx.Git.CurrentTag = "v1.0.0"
+	require.Error(t, Pipe{}.Default(ctx))
+}
+
 func TestDefaultWithGitea(t *testing.T) {
 	testlib.Mktmp(t)
 	testlib.GitInit(t)
@@ -384,6 +408,18 @@ func TestDefaultWithGitea(t *testing.T) {
 	require.Equal(t, "https://git.honk.com/giteaowner/gitearepo/releases/tag/v1.0.0", ctx.ReleaseURL)
 }
 
+func TestDefaultWithGiteaInvalidURL(t *testing.T) {
+	testlib.Mktmp(t)
+	testlib.GitInit(t)
+	testlib.GitRemoteAdd(t, "git@gitea.example.com:gitearepo.git")
+
+	ctx := context.New(config.Project{})
+	ctx.TokenType = context.TokenTypeGitea
+	ctx.Config.GiteaURLs.Download = "https://git.honk.com"
+	ctx.Git.CurrentTag = "v1.0.0"
+	require.Error(t, Pipe{}.Default(ctx))
+}
+
 func TestDefaultPreRelease(t *testing.T) {
 	testlib.Mktmp(t)
 	testlib.GitInit(t)