github.com/wesleimp/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  }