github.1git.de/goreleaser/goreleaser@v0.92.0/internal/pipe/scoop/scoop.go (about) 1 // Package scoop provides a Pipe that generates a scoop.sh App Manifest and pushes it to a bucket 2 package scoop 3 4 import ( 5 "bytes" 6 "encoding/json" 7 "errors" 8 "fmt" 9 10 "github.com/goreleaser/goreleaser/internal/artifact" 11 "github.com/goreleaser/goreleaser/internal/client" 12 "github.com/goreleaser/goreleaser/internal/pipe" 13 "github.com/goreleaser/goreleaser/internal/tmpl" 14 "github.com/goreleaser/goreleaser/pkg/context" 15 ) 16 17 // ErrNoWindows when there is no build for windows (goos doesn't contain windows) 18 var ErrNoWindows = errors.New("scoop requires a windows build") 19 20 // Pipe for build 21 type Pipe struct{} 22 23 func (Pipe) String() string { 24 return "scoop manifest" 25 } 26 27 // Publish scoop manifest 28 func (Pipe) Publish(ctx *context.Context) error { 29 client, err := client.NewGitHub(ctx) 30 if err != nil { 31 return err 32 } 33 return doRun(ctx, client) 34 } 35 36 // Default sets the pipe defaults 37 func (Pipe) Default(ctx *context.Context) error { 38 if ctx.Config.Scoop.Name == "" { 39 ctx.Config.Scoop.Name = ctx.Config.ProjectName 40 } 41 if ctx.Config.Scoop.CommitAuthor.Name == "" { 42 ctx.Config.Scoop.CommitAuthor.Name = "goreleaserbot" 43 } 44 if ctx.Config.Scoop.CommitAuthor.Email == "" { 45 ctx.Config.Scoop.CommitAuthor.Email = "goreleaser@carlosbecker.com" 46 } 47 if ctx.Config.Scoop.URLTemplate == "" { 48 ctx.Config.Scoop.URLTemplate = fmt.Sprintf( 49 "%s/%s/%s/releases/download/{{ .Tag }}/{{ .ArtifactName }}", 50 ctx.Config.GitHubURLs.Download, 51 ctx.Config.Release.GitHub.Owner, 52 ctx.Config.Release.GitHub.Name, 53 ) 54 } 55 return nil 56 } 57 58 func doRun(ctx *context.Context, client client.Client) error { 59 if ctx.Config.Scoop.Bucket.Name == "" { 60 return pipe.Skip("scoop section is not configured") 61 } 62 if ctx.Config.Archive.Format == "binary" { 63 return pipe.Skip("archive format is binary") 64 } 65 66 var archives = ctx.Artifacts.Filter( 67 artifact.And( 68 artifact.ByGoos("windows"), 69 artifact.ByType(artifact.UploadableArchive), 70 ), 71 ).List() 72 if len(archives) == 0 { 73 return ErrNoWindows 74 } 75 76 var path = ctx.Config.Scoop.Name + ".json" 77 78 content, err := buildManifest(ctx, archives) 79 if err != nil { 80 return err 81 } 82 83 if ctx.SkipPublish { 84 return pipe.ErrSkipPublishEnabled 85 } 86 if ctx.Config.Release.Draft { 87 return pipe.Skip("release is marked as draft") 88 } 89 return client.CreateFile( 90 ctx, 91 ctx.Config.Scoop.CommitAuthor, 92 ctx.Config.Scoop.Bucket, 93 content, 94 path, 95 fmt.Sprintf("Scoop update for %s version %s", ctx.Config.ProjectName, ctx.Git.CurrentTag), 96 ) 97 } 98 99 // Manifest represents a scoop.sh App Manifest, more info: 100 // https://github.com/lukesampson/scoop/wiki/App-Manifests 101 type Manifest struct { 102 Version string `json:"version"` // The version of the app that this manifest installs. 103 Architecture map[string]Resource `json:"architecture"` // `architecture`: If the app has 32- and 64-bit versions, architecture can be used to wrap the differences. 104 Homepage string `json:"homepage,omitempty"` // `homepage`: The home page for the program. 105 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. 106 Description string `json:"description,omitempty"` // Description of the app 107 Persist []string `json:"persist,omitempty"` // Persist data between updates 108 } 109 110 // Resource represents a combination of a url and a binary name for an architecture 111 type Resource struct { 112 URL string `json:"url"` // URL to the archive 113 Bin string `json:"bin"` // name of binary inside the archive 114 Hash string `json:"hash"` // the archive checksum 115 } 116 117 func buildManifest(ctx *context.Context, artifacts []artifact.Artifact) (bytes.Buffer, error) { 118 var result bytes.Buffer 119 var manifest = Manifest{ 120 Version: ctx.Version, 121 Architecture: make(map[string]Resource), 122 Homepage: ctx.Config.Scoop.Homepage, 123 License: ctx.Config.Scoop.License, 124 Description: ctx.Config.Scoop.Description, 125 Persist: ctx.Config.Scoop.Persist, 126 } 127 128 for _, artifact := range artifacts { 129 var arch = "64bit" 130 if artifact.Goarch == "386" { 131 arch = "32bit" 132 } 133 134 url, err := tmpl.New(ctx). 135 WithArtifact(artifact, map[string]string{}). 136 Apply(ctx.Config.Scoop.URLTemplate) 137 if err != nil { 138 return result, err 139 } 140 141 sum, err := artifact.Checksum() 142 if err != nil { 143 return result, err 144 } 145 146 manifest.Architecture[arch] = Resource{ 147 URL: url, 148 Bin: ctx.Config.Builds[0].Binary + ".exe", // TODO: this is wrong 149 Hash: sum, 150 } 151 } 152 153 data, err := json.MarshalIndent(manifest, "", " ") 154 if err != nil { 155 return result, err 156 } 157 _, err = result.Write(data) 158 return result, err 159 }