You've already forked goreleaser
							
							
				mirror of
				https://github.com/goreleaser/goreleaser.git
				synced 2025-10-30 23:58:09 +02:00 
			
		
		
		
	add: Scoop manifest support
Finished the Scoop.sh manifest generator for Windows releases! See #387
This commit is contained in:
		
				
					committed by
					
						 Carlos Alexandro Becker
						Carlos Alexandro Becker
					
				
			
			
				
	
			
			
			
						parent
						
							0a37305745
						
					
				
				
					commit
					c8d36813cb
				
			| @@ -49,6 +49,15 @@ type Homebrew struct { | ||||
| 	DownloadStrategy string       `yaml:"download_strategy,omitempty"` | ||||
| } | ||||
|  | ||||
| // Scoop contains the scoop.sh section | ||||
| type Scoop struct { | ||||
| 	Bucket       Repo         `yaml:",omitempty"` | ||||
| 	CommitAuthor CommitAuthor `yaml:"commit_author,omitempty"` | ||||
| 	Homepage     string       `yaml:",omitempty"` | ||||
| 	Description  string       `yaml:",omitempty"` | ||||
| 	License      string       `yaml:",omitempty"` | ||||
| } | ||||
|  | ||||
| // CommitAuthor is the author of a Git commit | ||||
| type CommitAuthor struct { | ||||
| 	Name  string `yaml:",omitempty"` | ||||
| @@ -205,6 +214,7 @@ type Project struct { | ||||
| 	ProjectName   string        `yaml:"project_name,omitempty"` | ||||
| 	Release       Release       `yaml:",omitempty"` | ||||
| 	Brew          Homebrew      `yaml:",omitempty"` | ||||
| 	Scoop         Scoop         `yaml:",omitempty"` | ||||
| 	Builds        []Build       `yaml:",omitempty"` | ||||
| 	Archive       Archive       `yaml:",omitempty"` | ||||
| 	FPM           FPM           `yaml:",omitempty"` | ||||
|   | ||||
| @@ -30,6 +30,7 @@ import ( | ||||
| 	"github.com/goreleaser/goreleaser/pipeline/fpm" | ||||
| 	"github.com/goreleaser/goreleaser/pipeline/git" | ||||
| 	"github.com/goreleaser/goreleaser/pipeline/release" | ||||
| 	"github.com/goreleaser/goreleaser/pipeline/scoop" | ||||
| 	"github.com/goreleaser/goreleaser/pipeline/sign" | ||||
| 	"github.com/goreleaser/goreleaser/pipeline/snapcraft" | ||||
| ) | ||||
| @@ -60,6 +61,7 @@ var pipes = []pipeline.Piper{ | ||||
| 	artifactory.Pipe{},     // push to artifactory | ||||
| 	release.Pipe{},         // release to github | ||||
| 	brew.Pipe{},            // push to brew tap | ||||
| 	scoop.Pipe{},           // push to scoop bucket | ||||
| } | ||||
|  | ||||
| // Flags interface represents an extractor of cli flags | ||||
|   | ||||
| @@ -15,6 +15,7 @@ import ( | ||||
| 	"github.com/goreleaser/goreleaser/pipeline/env" | ||||
| 	"github.com/goreleaser/goreleaser/pipeline/fpm" | ||||
| 	"github.com/goreleaser/goreleaser/pipeline/release" | ||||
| 	"github.com/goreleaser/goreleaser/pipeline/scoop" | ||||
| 	"github.com/goreleaser/goreleaser/pipeline/sign" | ||||
| 	"github.com/goreleaser/goreleaser/pipeline/snapcraft" | ||||
| 	"github.com/goreleaser/goreleaser/pipeline/snapshot" | ||||
| @@ -40,6 +41,7 @@ var defaulters = []pipeline.Defaulter{ | ||||
| 	docker.Pipe{}, | ||||
| 	artifactory.Pipe{}, | ||||
| 	brew.Pipe{}, | ||||
| 	scoop.Pipe{}, | ||||
| } | ||||
|  | ||||
| // Run the pipe | ||||
|   | ||||
							
								
								
									
										165
									
								
								pipeline/scoop/scoop.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										165
									
								
								pipeline/scoop/scoop.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,165 @@ | ||||
| // Package scoop provides a Pipe that generates a scoop.sh App Manifest and pushes it to a bucket | ||||
| package scoop | ||||
|  | ||||
| import ( | ||||
| 	"bytes" | ||||
| 	"encoding/json" | ||||
| 	"errors" | ||||
| 	"fmt" | ||||
|  | ||||
| 	"github.com/goreleaser/goreleaser/config" | ||||
| 	"github.com/goreleaser/goreleaser/context" | ||||
| 	"github.com/goreleaser/goreleaser/internal/artifact" | ||||
| 	"github.com/goreleaser/goreleaser/internal/client" | ||||
| 	"github.com/goreleaser/goreleaser/pipeline" | ||||
| ) | ||||
|  | ||||
| // ErrNoWindows when there is no build for windows (goos doesn't contain windows) | ||||
| var ErrNoWindows = errors.New("scoop requires a windows build") | ||||
|  | ||||
| // Pipe for build | ||||
| type Pipe struct{} | ||||
|  | ||||
| func (Pipe) String() string { | ||||
| 	return "creating Scoop Manifest" | ||||
| } | ||||
|  | ||||
| // Run the pipe | ||||
| func (Pipe) Run(ctx *context.Context) error { | ||||
| 	client, err := client.NewGitHub(ctx) | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 	return doRun(ctx, client) | ||||
| } | ||||
|  | ||||
| // Default sets the pipe defaults | ||||
| func (Pipe) Default(ctx *context.Context) error { | ||||
| 	if ctx.Config.Scoop.CommitAuthor.Name == "" { | ||||
| 		ctx.Config.Scoop.CommitAuthor.Name = "goreleaserbot" | ||||
| 	} | ||||
| 	if ctx.Config.Scoop.CommitAuthor.Email == "" { | ||||
| 		ctx.Config.Scoop.CommitAuthor.Email = "goreleaser@carlosbecker.com" | ||||
| 	} | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| func isScoopBuild(build config.Build) bool { | ||||
| 	for _, ignore := range build.Ignore { | ||||
| 		if ignore.Goos == "windows" { | ||||
| 			return false | ||||
| 		} | ||||
| 	} | ||||
| 	return contains(build.Goos, "darwin") | ||||
| } | ||||
|  | ||||
| func contains(ss []string, s string) bool { | ||||
| 	for _, zs := range ss { | ||||
| 		if zs == s { | ||||
| 			return true | ||||
| 		} | ||||
| 	} | ||||
| 	return false | ||||
| } | ||||
|  | ||||
| func doRun(ctx *context.Context, client client.Client) error { | ||||
| 	if ctx.Config.Scoop.Bucket.Name == "" { | ||||
| 		return pipeline.Skip("scoop section is not configured") | ||||
| 	} | ||||
| 	if ctx.Config.Archive.Format == "binary" { | ||||
| 		return pipeline.Skip("archive format is binary") | ||||
| 	} | ||||
|  | ||||
| 	var archives = ctx.Artifacts.Filter( | ||||
| 		artifact.And( | ||||
| 			artifact.ByGoos("windows"), | ||||
| 			artifact.ByType(artifact.UploadableArchive), | ||||
| 		), | ||||
| 	).List() | ||||
| 	if len(archives) == 0 { | ||||
| 		return ErrNoWindows | ||||
| 	} | ||||
|  | ||||
| 	path := ctx.Config.ProjectName + ".json" | ||||
|  | ||||
| 	content, err := buildManifest(ctx, client, archives) | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
|  | ||||
| 	if !ctx.Publish { | ||||
| 		return pipeline.ErrSkipPublish | ||||
| 	} | ||||
| 	if ctx.Config.Release.Draft { | ||||
| 		return pipeline.Skip("release is marked as draft") | ||||
| 	} | ||||
|  | ||||
| 	return client.CreateFile( | ||||
| 		ctx, | ||||
| 		ctx.Config.Scoop.CommitAuthor, | ||||
| 		ctx.Config.Scoop.Bucket, | ||||
| 		content, | ||||
| 		path) | ||||
| } | ||||
|  | ||||
| // Manifest represents a scoop.sh App Manifest, more info: | ||||
| // https://github.com/lukesampson/scoop/wiki/App-Manifests | ||||
| type Manifest struct { | ||||
| 	Version      string              `json:"version"`               // The version of the app that this manifest installs. | ||||
| 	Architecture map[string]Resource `json:"architecture"`          // `architecture`: If the app has 32- and 64-bit versions, architecture can be used to wrap the differences. | ||||
| 	Homepage     string              `json:"homepage,omitempty"`    // `homepage`: The home page for the program. | ||||
| 	License      string              `json:"license,omitempty"`     // `license`: The software license for the program. For well-known licenses, this will be a string like "MIT" or "GPL2". For custom licenses, this should be the URL of the license. | ||||
| 	Description  string              `json:"description,omitempty"` // Description of the app | ||||
| } | ||||
|  | ||||
| // Resource represents a combination of a url and a binary name for an architecture | ||||
| type Resource struct { | ||||
| 	URL string `json:"url"` // URL to the archive | ||||
| 	Bin string `json:"bin"` // name of binary inside the archive | ||||
| } | ||||
|  | ||||
| func buildManifest(ctx *context.Context, client client.Client, artifacts []artifact.Artifact) (result bytes.Buffer, err error) { | ||||
| 	var githubURL = "https://github.com" | ||||
| 	if ctx.Config.GitHubURLs.Download != "" { | ||||
| 		githubURL = ctx.Config.GitHubURLs.Download | ||||
| 	} | ||||
|  | ||||
| 	manifest := Manifest{ | ||||
| 		Version:      ctx.Version, | ||||
| 		Architecture: make(map[string]Resource), | ||||
| 		Homepage:     ctx.Config.Scoop.Homepage, | ||||
| 		License:      ctx.Config.Scoop.License, | ||||
| 		Description:  ctx.Config.Scoop.Description, | ||||
| 	} | ||||
|  | ||||
| 	for _, artifact := range artifacts { | ||||
| 		if artifact.Goarch == "amd64" { | ||||
| 			manifest.Architecture["64bit"] = Resource{ | ||||
| 				URL: getDownloadURL(ctx, githubURL, artifact.Name), | ||||
| 				Bin: ctx.Config.Builds[0].Binary + ".exe", | ||||
| 			} | ||||
| 		} else if artifact.Goarch == "386" { | ||||
| 			manifest.Architecture["32bit"] = Resource{ | ||||
| 				URL: getDownloadURL(ctx, githubURL, artifact.Name), | ||||
| 				Bin: ctx.Config.Builds[0].Binary + ".exe", | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	data, err := json.MarshalIndent(manifest, "", "    ") | ||||
| 	if err != nil { | ||||
| 		return | ||||
| 	} | ||||
| 	_, err = result.Write(data) | ||||
|  | ||||
| 	return | ||||
| } | ||||
|  | ||||
| func getDownloadURL(ctx *context.Context, githubURL, file string) (url string) { | ||||
| 	return fmt.Sprintf("%s/%s/%s/releases/download/%s/%s", | ||||
| 		githubURL, | ||||
| 		ctx.Config.Release.GitHub.Owner, | ||||
| 		ctx.Config.Release.GitHub.Name, | ||||
| 		ctx.Version, | ||||
| 		file) | ||||
| } | ||||
		Reference in New Issue
	
	Block a user