1
0
mirror of https://github.com/goreleaser/goreleaser.git synced 2025-01-14 03:51:24 +02:00

feat: winget support (#4081)

this will add support to winget into goreleaser.

Basically, the plan is:

- we generate the 3 needed yaml files 
- we commit them to a repo

and that's it.

Initially, will probably have limited options support, and will only
have the default locale.

###### TODO

- [x] docs
- [x] review by someone who knows how this works?
- [x] test install somewhere
- [x] more tests maybe?
- [x] PR templates via API?
https://github.com/goreleaser/goreleaser/pull/4105
- [x] real project test 
- [x] setup goreleaser to pr to winget as well
- [x] document sync fork stuff
https://github.com/goreleaser/goreleaser/pull/4106

---------

Signed-off-by: Carlos Alexandro Becker <caarlos0@users.noreply.github.com>
This commit is contained in:
Carlos Alexandro Becker 2023-06-14 23:59:55 -03:00 committed by GitHub
parent 81bd82b13b
commit 6afdb49c12
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
36 changed files with 1622 additions and 7 deletions

View File

@ -204,6 +204,23 @@ nix:
installManPage ./manpages/goreleaser.1.gz
installShellCompletion ./completions/*
winget:
- name: goreleaser
publisher: goreleaser
license: MIT
homepage: https://goreleaser.com
short_description: Deliver Go binaries as fast and easily as possible
repository:
owner: goreleaser
name: winget-pkgs
pull_request:
enabled: true
base:
owner: microsoft
name: winget-pkgs
branch: master
draft: true
aurs:
- homepage: https://goreleaser.com
description: Deliver Go binaries as fast and easily as possible

View File

@ -60,6 +60,12 @@ const (
BrewTap
// Nixpkg is an uploadable nix package.
Nixpkg
// WingetInstaller winget installer file.
WingetInstaller
// WingetDefaultLocale winget default locale file.
WingetDefaultLocale
// WingetVersion winget version file.
WingetVersion
// PkgBuild is an Arch Linux AUR PKGBUILD file.
PkgBuild
// SrcInfo is an Arch Linux AUR .SRCINFO file.
@ -126,6 +132,8 @@ func (t Type) String() string {
return "C Archive Library"
case CShared:
return "C Shared Library"
case WingetInstaller, WingetDefaultLocale, WingetVersion:
return "Winget Manifest"
default:
return "unknown"
}

View File

@ -21,27 +21,32 @@ func RequireEqual(tb testing.TB, out []byte) {
func RequireEqualExt(tb testing.TB, out []byte, ext string) {
tb.Helper()
doRequireEqual(tb, out, ext, golden)
doRequireEqual(tb, out, ext, golden, false)
}
func RequireEqualExtSubfolder(tb testing.TB, out []byte, ext string) {
tb.Helper()
doRequireEqual(tb, out, ext, golden, true)
}
func RequireEqualTxt(tb testing.TB, out []byte) {
tb.Helper()
doRequireEqual(tb, out, ".txt", golden)
doRequireEqual(tb, out, ".txt", golden, false)
}
func RequireEqualJSON(tb testing.TB, out []byte) {
tb.Helper()
doRequireEqual(tb, out, ".json", golden)
doRequireEqual(tb, out, ".json", golden, false)
}
func RequireEqualRb(tb testing.TB, out []byte) {
tb.Helper()
doRequireEqual(tb, out, ".rb", golden)
doRequireEqual(tb, out, ".rb", golden, false)
}
func RequireEqualYaml(tb testing.TB, out []byte) {
tb.Helper()
doRequireEqual(tb, out, ".yaml", "")
doRequireEqual(tb, out, ".yaml", "", false)
}
func RequireReadFile(tb testing.TB, path string) []byte {
@ -51,10 +56,13 @@ func RequireReadFile(tb testing.TB, path string) []byte {
return bts
}
func doRequireEqual(tb testing.TB, out []byte, ext, suffix string) {
func doRequireEqual(tb testing.TB, out []byte, ext, suffix string, folder bool) {
tb.Helper()
golden := "testdata/" + tb.Name() + ext + suffix
golden := filepath.Join("testdata", tb.Name()+ext+suffix)
if folder {
golden = filepath.Join("testdata", tb.Name(), filepath.Base(tb.Name())+ext+suffix)
}
if *update {
require.NoError(tb, os.MkdirAll(filepath.Dir(golden), 0o755))
require.NoError(tb, os.WriteFile(golden, out, 0o655))

View File

@ -23,6 +23,7 @@ import (
"github.com/goreleaser/goreleaser/internal/pipe/sign"
"github.com/goreleaser/goreleaser/internal/pipe/snapcraft"
"github.com/goreleaser/goreleaser/internal/pipe/upload"
"github.com/goreleaser/goreleaser/internal/pipe/winget"
"github.com/goreleaser/goreleaser/pkg/context"
)
@ -49,6 +50,7 @@ var publishers = []Publisher{
release.Pipe{},
// brew et al use the release URL, so, they should be last
nix.NewPublish(),
winget.Pipe{},
brew.Pipe{},
aur.Pipe{},
krew.Pipe{},

View File

@ -0,0 +1,122 @@
package winget
import (
"fmt"
"os"
"path/filepath"
"strings"
"github.com/caarlos0/log"
"github.com/goreleaser/goreleaser/internal/artifact"
"github.com/goreleaser/goreleaser/internal/yaml"
"github.com/goreleaser/goreleaser/pkg/config"
"github.com/goreleaser/goreleaser/pkg/context"
)
func createYAML(ctx *context.Context, winget config.Winget, in any, tp artifact.Type) error {
versionContent, err := yaml.Marshal(in)
if err != nil {
return err
}
filename := winget.Name + extFor(tp)
path := filepath.Join(ctx.Config.Dist, winget.Path, filename)
if err := os.MkdirAll(filepath.Dir(path), 0o755); err != nil {
return err
}
log.WithField("path", path).Info("writing")
if err := os.WriteFile(path, []byte(strings.Join([]string{
generatedHeader,
langserverLineFor(tp),
string(versionContent),
}, "\n")), 0o644); err != nil { //nolint: gosec
return fmt.Errorf("failed to write winget version: %w", err)
}
ctx.Artifacts.Add(&artifact.Artifact{
Name: filename,
Path: path,
Type: tp,
Extra: map[string]interface{}{
wingetConfigExtra: winget,
artifact.ExtraID: winget.Name,
},
})
return nil
}
const (
manifestVersion = "1.4.0"
versionLangServer = "# yaml-language-server: $schema=https://aka.ms/winget-manifest.version.1.4.0.schema.json"
installerLangServer = "# yaml-language-server: $schema=https://aka.ms/winget-manifest.installer.1.4.0.schema.json"
defaultLocaleLangServer = "# yaml-language-server: $schema=https://aka.ms/winget-manifest.defaultLocale.1.4.0.schema.json"
defaultLocale = "en-US"
generatedHeader = `# This file was generated by GoReleaser. DO NOT EDIT.`
)
// nolint: tagliatelle
type Version struct {
PackageIdentifier string `yaml:"PackageIdentifier,omitempty"`
PackageVersion string `yaml:"PackageVersion,omitempty"`
DefaultLocale string `yaml:"DefaultLocale,omitempty"`
ManifestType string `yaml:"ManifestType,omitempty"`
ManifestVersion string `yaml:"ManifestVersion,omitempty"`
}
// nolint: tagliatelle
type InstallerItemFile struct {
RelativeFilePath string `yaml:"RelativeFilePath,omitempty"`
}
// nolint: tagliatelle
type InstallerItem struct {
Architecture string `yaml:"Architecture,omitempty"`
NestedInstallerType string `yaml:"NestedInstallerType,omitempty"`
NestedInstallerFiles []InstallerItemFile `yaml:"NestedInstallerFiles,omitempty"`
InstallerURL string `yaml:"InstallerUrl,omitempty"`
InstallerSha256 string `yaml:"InstallerSha256,omitempty"`
UpgradeBehavior string `yaml:"UpgradeBehavior,omitempty"`
}
// nolint: tagliatelle
type Installer struct {
PackageIdentifier string `yaml:"PackageIdentifier,omitempty"`
PackageVersion string `yaml:"PackageVersion,omitempty"`
InstallerLocale string `yaml:"InstallerLocale,omitempty"`
InstallerType string `yaml:"InstallerType,omitempty"`
Commands []string `yaml:"Commands,omitempty"`
ReleaseDate string `yaml:"ReleaseDate,omitempty"`
Installers []InstallerItem `yaml:"Installers,omitempty"`
ManifestType string `yaml:"ManifestType,omitempty"`
ManifestVersion string `yaml:"ManifestVersion,omitempty"`
}
// nolint: tagliatelle
type Locale struct {
PackageIdentifier string `yaml:"PackageIdentifier,omitempty"`
PackageVersion string `yaml:"PackageVersion,omitempty"`
PackageLocale string `yaml:"PackageLocale,omitempty"`
Publisher string `yaml:"Publisher,omitempty"`
PublisherURL string `yaml:"PublisherUrl,omitempty"`
Author string `yaml:"Author,omitempty"`
PackageName string `yaml:"PackageName,omitempty"`
PackageURL string `yaml:"PackageUrl,omitempty"`
License string `yaml:"License,omitempty"`
LicenseURL string `yaml:"LicenseUrl,omitempty"`
Copyright string `yaml:"Copyright,omitempty"`
ShortDescription string `yaml:"ShortDescription,omitempty"`
Description string `yaml:"Description,omitempty"`
Moniker string `yaml:"Moniker,omitempty"`
Tags []string `yaml:"Tags,omitempty"`
ReleaseNotes string `yaml:"ReleaseNotes,omitempty"`
ReleaseNotesURL string `yaml:"ReleaseNotesUrl,omitempty"`
ManifestType string `yaml:"ManifestType,omitempty"`
ManifestVersion string `yaml:"ManifestVersion,omitempty"`
}
var fromGoArch = map[string]string{
"amd64": "x64",
"386": "x86",
}

View File

@ -0,0 +1,12 @@
# This file was generated by GoReleaser. DO NOT EDIT.
# yaml-language-server: $schema=https://aka.ms/winget-manifest.defaultLocale.1.4.0.schema.json
PackageIdentifier: Beckersoft.foo
PackageVersion: 1.2.1
PackageLocale: en-US
Publisher: Beckersoft
PackageName: foo
License: MIT
ShortDescription: foo bar zaz
Moniker: foo
ManifestType: defaultLocale
ManifestVersion: 1.4.0

View File

@ -0,0 +1,17 @@
# This file was generated by GoReleaser. DO NOT EDIT.
# yaml-language-server: $schema=https://aka.ms/winget-manifest.installer.1.4.0.schema.json
PackageIdentifier: Beckersoft.foo
PackageVersion: 1.2.1
InstallerLocale: en-US
InstallerType: zip
ReleaseDate: "2023-06-12"
Installers:
- Architecture: x64
NestedInstallerType: portable
NestedInstallerFiles:
- RelativeFilePath: foo.exe
InstallerUrl: https://dummyhost/download/v1.2.1/foo_windows_amd64v1.zip
InstallerSha256: e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855
UpgradeBehavior: uninstallPrevious
ManifestType: installer
ManifestVersion: 1.4.0

View File

@ -0,0 +1,7 @@
# This file was generated by GoReleaser. DO NOT EDIT.
# yaml-language-server: $schema=https://aka.ms/winget-manifest.version.1.4.0.schema.json
PackageIdentifier: Beckersoft.foo
PackageVersion: 1.2.1
DefaultLocale: en-US
ManifestType: version
ManifestVersion: 1.4.0

View File

@ -0,0 +1,24 @@
# This file was generated by GoReleaser. DO NOT EDIT.
# yaml-language-server: $schema=https://aka.ms/winget-manifest.defaultLocale.1.4.0.schema.json
PackageIdentifier: Beckersoft.foo
PackageVersion: 1.2.1
PackageLocale: en-US
Publisher: Beckersoft
PublisherUrl: https://carlosbecker.com
Author: Carlos Becker
PackageName: foo
PackageUrl: https://goreleaser.com
License: MIT
LicenseUrl: https://goreleaser.com/eula/
Copyright: bla bla bla
ShortDescription: foo
Description: |-
long foo bar
yadaa yada yada loooaaasssss
sss
Moniker: foo
ReleaseNotesUrl: https://github.com/goreleaser/goreleaser/tags/v1.2.1
ManifestType: defaultLocale
ManifestVersion: 1.4.0

View File

@ -0,0 +1,17 @@
# This file was generated by GoReleaser. DO NOT EDIT.
# yaml-language-server: $schema=https://aka.ms/winget-manifest.installer.1.4.0.schema.json
PackageIdentifier: Beckersoft.foo
PackageVersion: 1.2.1
InstallerLocale: en-US
InstallerType: zip
ReleaseDate: "2023-06-12"
Installers:
- Architecture: x64
NestedInstallerType: portable
NestedInstallerFiles:
- RelativeFilePath: foo.exe
InstallerUrl: https://dummyhost/download/v1.2.1/foo_windows_amd64v1.zip
InstallerSha256: e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855
UpgradeBehavior: uninstallPrevious
ManifestType: installer
ManifestVersion: 1.4.0

View File

@ -0,0 +1,7 @@
# This file was generated by GoReleaser. DO NOT EDIT.
# yaml-language-server: $schema=https://aka.ms/winget-manifest.version.1.4.0.schema.json
PackageIdentifier: Beckersoft.foo
PackageVersion: 1.2.1
DefaultLocale: en-US
ManifestType: version
ManifestVersion: 1.4.0

View File

@ -0,0 +1,12 @@
# This file was generated by GoReleaser. DO NOT EDIT.
# yaml-language-server: $schema=https://aka.ms/winget-manifest.defaultLocale.1.4.0.schema.json
PackageIdentifier: Foo.min
PackageVersion: 1.2.1
PackageLocale: en-US
Publisher: Foo
PackageName: min
License: MIT
ShortDescription: foo bar zaz
Moniker: min
ManifestType: defaultLocale
ManifestVersion: 1.4.0

View File

@ -0,0 +1,17 @@
# This file was generated by GoReleaser. DO NOT EDIT.
# yaml-language-server: $schema=https://aka.ms/winget-manifest.installer.1.4.0.schema.json
PackageIdentifier: Foo.min
PackageVersion: 1.2.1
InstallerLocale: en-US
InstallerType: zip
ReleaseDate: "2023-06-12"
Installers:
- Architecture: x64
NestedInstallerType: portable
NestedInstallerFiles:
- RelativeFilePath: foo.exe
InstallerUrl: https://dummyhost/download/v1.2.1/foo_windows_amd64v1.zip
InstallerSha256: e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855
UpgradeBehavior: uninstallPrevious
ManifestType: installer
ManifestVersion: 1.4.0

View File

@ -0,0 +1,7 @@
# This file was generated by GoReleaser. DO NOT EDIT.
# yaml-language-server: $schema=https://aka.ms/winget-manifest.version.1.4.0.schema.json
PackageIdentifier: Foo.min
PackageVersion: 1.2.1
DefaultLocale: en-US
ManifestType: version
ManifestVersion: 1.4.0

View File

@ -0,0 +1,14 @@
# This file was generated by GoReleaser. DO NOT EDIT.
# yaml-language-server: $schema=https://aka.ms/winget-manifest.defaultLocale.1.4.0.schema.json
PackageIdentifier: Beckersoft.foo
PackageVersion: 1.2.1
PackageLocale: en-US
Publisher: Beckersoft
PackageName: foo
PackageUrl: https://goreleaser.com
License: mit
ShortDescription: foo bar zaz
Description: my test
Moniker: foo
ManifestType: defaultLocale
ManifestVersion: 1.4.0

View File

@ -0,0 +1,17 @@
# This file was generated by GoReleaser. DO NOT EDIT.
# yaml-language-server: $schema=https://aka.ms/winget-manifest.installer.1.4.0.schema.json
PackageIdentifier: Beckersoft.foo
PackageVersion: 1.2.1
InstallerLocale: en-US
InstallerType: zip
ReleaseDate: "2023-06-12"
Installers:
- Architecture: x64
NestedInstallerType: portable
NestedInstallerFiles:
- RelativeFilePath: foo.exe
InstallerUrl: https://dummyhost/download/v1.2.1/foo_windows_amd64v1.zip
InstallerSha256: e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855
UpgradeBehavior: uninstallPrevious
ManifestType: installer
ManifestVersion: 1.4.0

View File

@ -0,0 +1,7 @@
# This file was generated by GoReleaser. DO NOT EDIT.
# yaml-language-server: $schema=https://aka.ms/winget-manifest.version.1.4.0.schema.json
PackageIdentifier: Beckersoft.foo
PackageVersion: 1.2.1
DefaultLocale: en-US
ManifestType: version
ManifestVersion: 1.4.0

View File

@ -0,0 +1,12 @@
# This file was generated by GoReleaser. DO NOT EDIT.
# yaml-language-server: $schema=https://aka.ms/winget-manifest.defaultLocale.1.4.0.schema.json
PackageIdentifier: Beckersoft.partial
PackageVersion: 1.2.1
PackageLocale: en-US
Publisher: Beckersoft
PackageName: partial
License: MIT
ShortDescription: foo bar zaz
Moniker: partial
ManifestType: defaultLocale
ManifestVersion: 1.4.0

View File

@ -0,0 +1,17 @@
# This file was generated by GoReleaser. DO NOT EDIT.
# yaml-language-server: $schema=https://aka.ms/winget-manifest.installer.1.4.0.schema.json
PackageIdentifier: Beckersoft.partial
PackageVersion: 1.2.1
InstallerLocale: en-US
InstallerType: zip
ReleaseDate: "2023-06-12"
Installers:
- Architecture: x64
NestedInstallerType: portable
NestedInstallerFiles:
- RelativeFilePath: foo.exe
InstallerUrl: https://dummyhost/download/v1.2.1/foo_windows_amd64v1.zip
InstallerSha256: e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855
UpgradeBehavior: uninstallPrevious
ManifestType: installer
ManifestVersion: 1.4.0

View File

@ -0,0 +1,7 @@
# This file was generated by GoReleaser. DO NOT EDIT.
# yaml-language-server: $schema=https://aka.ms/winget-manifest.version.1.4.0.schema.json
PackageIdentifier: Beckersoft.partial
PackageVersion: 1.2.1
DefaultLocale: en-US
ManifestType: version
ManifestVersion: 1.4.0

View File

@ -0,0 +1,12 @@
# This file was generated by GoReleaser. DO NOT EDIT.
# yaml-language-server: $schema=https://aka.ms/winget-manifest.defaultLocale.1.4.0.schema.json
PackageIdentifier: Beckersoft.doesnotmatter
PackageVersion: 1.2.1
PackageLocale: en-US
Publisher: Beckersoft
PackageName: doesnotmatter
License: MIT
ShortDescription: foo bar zaz
Moniker: doesnotmatter
ManifestType: defaultLocale
ManifestVersion: 1.4.0

View File

@ -0,0 +1,17 @@
# This file was generated by GoReleaser. DO NOT EDIT.
# yaml-language-server: $schema=https://aka.ms/winget-manifest.installer.1.4.0.schema.json
PackageIdentifier: Beckersoft.doesnotmatter
PackageVersion: 1.2.1
InstallerLocale: en-US
InstallerType: zip
ReleaseDate: "2023-06-12"
Installers:
- Architecture: x64
NestedInstallerType: portable
NestedInstallerFiles:
- RelativeFilePath: foo.exe
InstallerUrl: https://dummyhost/download/v1.2.1/foo_windows_amd64v1.zip
InstallerSha256: e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855
UpgradeBehavior: uninstallPrevious
ManifestType: installer
ManifestVersion: 1.4.0

View File

@ -0,0 +1,7 @@
# This file was generated by GoReleaser. DO NOT EDIT.
# yaml-language-server: $schema=https://aka.ms/winget-manifest.version.1.4.0.schema.json
PackageIdentifier: Beckersoft.doesnotmatter
PackageVersion: 1.2.1
DefaultLocale: en-US
ManifestType: version
ManifestVersion: 1.4.0

View File

@ -0,0 +1,12 @@
# This file was generated by GoReleaser. DO NOT EDIT.
# yaml-language-server: $schema=https://aka.ms/winget-manifest.defaultLocale.1.4.0.schema.json
PackageIdentifier: Beckersoft.doesnotmatter
PackageVersion: 1.2.1
PackageLocale: en-US
Publisher: Beckersoft
PackageName: doesnotmatter
License: MIT
ShortDescription: foo bar zaz
Moniker: doesnotmatter
ManifestType: defaultLocale
ManifestVersion: 1.4.0

View File

@ -0,0 +1,17 @@
# This file was generated by GoReleaser. DO NOT EDIT.
# yaml-language-server: $schema=https://aka.ms/winget-manifest.installer.1.4.0.schema.json
PackageIdentifier: Beckersoft.doesnotmatter
PackageVersion: 1.2.1
InstallerLocale: en-US
InstallerType: zip
ReleaseDate: "2023-06-12"
Installers:
- Architecture: x64
NestedInstallerType: portable
NestedInstallerFiles:
- RelativeFilePath: foo.exe
InstallerUrl: https://dummyhost/download/v1.2.1/foo_windows_amd64v1.zip
InstallerSha256: e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855
UpgradeBehavior: uninstallPrevious
ManifestType: installer
ManifestVersion: 1.4.0

View File

@ -0,0 +1,7 @@
# This file was generated by GoReleaser. DO NOT EDIT.
# yaml-language-server: $schema=https://aka.ms/winget-manifest.version.1.4.0.schema.json
PackageIdentifier: Beckersoft.doesnotmatter
PackageVersion: 1.2.1
DefaultLocale: en-US
ManifestType: version
ManifestVersion: 1.4.0

View File

@ -0,0 +1,16 @@
# This file was generated by GoReleaser. DO NOT EDIT.
# yaml-language-server: $schema=https://aka.ms/winget-manifest.defaultLocale.1.4.0.schema.json
PackageIdentifier: Beckersoft.wrapped-in-dir
PackageVersion: 1.2.1
PackageLocale: en-US
Publisher: Beckersoft
PackageName: wrapped-in-dir
PackageUrl: https://goreleaser.com
License: mit
LicenseUrl: https://goreleaser.com/license
ShortDescription: foo bar zaz
Description: my test
Moniker: wrapped-in-dir
ReleaseNotesUrl: https://github.com/goreleaser/goreleaser/tags/v1.2.1
ManifestType: defaultLocale
ManifestVersion: 1.4.0

View File

@ -0,0 +1,17 @@
# This file was generated by GoReleaser. DO NOT EDIT.
# yaml-language-server: $schema=https://aka.ms/winget-manifest.installer.1.4.0.schema.json
PackageIdentifier: Beckersoft.wrapped-in-dir
PackageVersion: 1.2.1
InstallerLocale: en-US
InstallerType: zip
ReleaseDate: "2023-06-12"
Installers:
- Architecture: x64
NestedInstallerType: portable
NestedInstallerFiles:
- RelativeFilePath: foo\foo.exe
InstallerUrl: https://dummyhost/download/v1.2.1/foo_windows_amd64v1.zip
InstallerSha256: e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855
UpgradeBehavior: uninstallPrevious
ManifestType: installer
ManifestVersion: 1.4.0

View File

@ -0,0 +1,7 @@
# This file was generated by GoReleaser. DO NOT EDIT.
# yaml-language-server: $schema=https://aka.ms/winget-manifest.version.1.4.0.schema.json
PackageIdentifier: Beckersoft.wrapped-in-dir
PackageVersion: 1.2.1
DefaultLocale: en-US
ManifestType: version
ManifestVersion: 1.4.0

View File

@ -0,0 +1,432 @@
package winget
import (
"fmt"
"os"
"path/filepath"
"regexp"
"strings"
"time"
"github.com/caarlos0/log"
"github.com/goreleaser/goreleaser/internal/artifact"
"github.com/goreleaser/goreleaser/internal/client"
"github.com/goreleaser/goreleaser/internal/commitauthor"
"github.com/goreleaser/goreleaser/internal/pipe"
"github.com/goreleaser/goreleaser/internal/tmpl"
"github.com/goreleaser/goreleaser/pkg/config"
"github.com/goreleaser/goreleaser/pkg/context"
)
var (
errNoRepoName = pipe.Skip("winget.repository.name name is required")
errNoPublisher = pipe.Skip("winget.publisher is required")
errNoLicense = pipe.Skip("winget.license is required")
errNoShortDescription = pipe.Skip("winget.short_description is required")
errInvalidPackageIdentifier = pipe.Skip("winget.package_identifier is invalid")
errSkipUpload = pipe.Skip("winget.skip_upload is set")
errSkipUploadAuto = pipe.Skip("winget.skip_upload is set to 'auto', and current version is a pre-release")
errMultipleArchives = pipe.Skip("found multiple archives for the same platform, please consider filtering by id")
packageIdentifierValid = regexp.MustCompile("^[^\\.\\s\\\\/:\\*\\?\"<>\\|\\x01-\\x1f]{1,32}(\\.[^\\.\\s\\\\/:\\*\\?\"<>\\|\\x01-\\x1f]{1,32}){1,7}$")
)
type errNoArchivesFound struct {
goamd64 string
ids []string
}
func (e errNoArchivesFound) Error() string {
return fmt.Sprintf("no zip archives found matching goos=[windows] goarch=[amd64 386] goamd64=%s ids=%v", e.goamd64, e.ids)
}
const wingetConfigExtra = "WingetConfig"
type Pipe struct{}
func (Pipe) String() string { return "winget" }
func (p Pipe) Skip(ctx *context.Context) bool {
return len(ctx.Config.Winget) == 0
}
func (Pipe) Default(ctx *context.Context) error {
for i := range ctx.Config.Winget {
winget := &ctx.Config.Winget[i]
winget.CommitAuthor = commitauthor.Default(winget.CommitAuthor)
if winget.CommitMessageTemplate == "" {
winget.CommitMessageTemplate = "{{ .ProjectName }}: {{ .PreviousTag }} -> {{ .Tag }}"
}
if winget.Name == "" {
winget.Name = ctx.Config.ProjectName
}
if winget.Goamd64 == "" {
winget.Goamd64 = "v1"
}
}
return nil
}
func (p Pipe) Run(ctx *context.Context) error {
cli, err := client.New(ctx)
if err != nil {
return err
}
return p.runAll(ctx, cli)
}
// Publish .
func (p Pipe) Publish(ctx *context.Context) error {
cli, err := client.New(ctx)
if err != nil {
return err
}
return p.publishAll(ctx, cli)
}
func (p Pipe) runAll(ctx *context.Context, cli client.Client) error {
for _, winget := range ctx.Config.Winget {
err := p.doRun(ctx, winget, cli)
if err != nil {
return err
}
}
return nil
}
func (p Pipe) doRun(ctx *context.Context, winget config.Winget, cl client.ReleaserURLTemplater) error {
if winget.Repository.Name == "" {
return errNoRepoName
}
publisher, err := tmpl.New(ctx).Apply(winget.Publisher)
if err != nil {
return err
}
if publisher == "" {
return errNoPublisher
}
winget.Publisher = publisher
if winget.License == "" {
return errNoLicense
}
name, err := tmpl.New(ctx).Apply(winget.Name)
if err != nil {
return err
}
winget.Name = name
author, err := tmpl.New(ctx).Apply(winget.Author)
if err != nil {
return err
}
winget.Author = author
publisherURL, err := tmpl.New(ctx).Apply(winget.PublisherURL)
if err != nil {
return err
}
winget.PublisherURL = publisherURL
homepage, err := tmpl.New(ctx).Apply(winget.Homepage)
if err != nil {
return err
}
winget.Homepage = homepage
ref, err := client.TemplateRef(tmpl.New(ctx).Apply, winget.Repository)
if err != nil {
return err
}
winget.Repository = ref
skipUpload, err := tmpl.New(ctx).Apply(winget.SkipUpload)
if err != nil {
return err
}
winget.SkipUpload = skipUpload
description, err := tmpl.New(ctx).Apply(winget.Description)
if err != nil {
return err
}
winget.Description = description
shortDescription, err := tmpl.New(ctx).Apply(winget.ShortDescription)
if err != nil {
return err
}
winget.ShortDescription = shortDescription
if winget.ShortDescription == "" {
return errNoShortDescription
}
releaseNotesURL, err := tmpl.New(ctx).Apply(winget.ReleaseNotesURL)
if err != nil {
return err
}
winget.ReleaseNotesURL = releaseNotesURL
if winget.URLTemplate == "" {
url, err := cl.ReleaseURLTemplate(ctx)
if err != nil {
return err
}
winget.URLTemplate = url
}
path, err := tmpl.New(ctx).Apply(winget.Path)
if err != nil {
return err
}
if path == "" {
path = filepath.Join("manifests", strings.ToLower(string(winget.Publisher[0])), winget.Publisher, winget.Name, ctx.Version)
}
winget.Path = path
filters := []artifact.Filter{
artifact.ByGoos("windows"),
artifact.ByFormats("zip"),
artifact.ByType(artifact.UploadableArchive),
artifact.Or(
artifact.ByGoarch("386"),
artifact.And(
artifact.ByGoamd64(winget.Goamd64),
artifact.ByGoarch("amd64"),
),
),
}
if len(winget.IDs) > 0 {
filters = append(filters, artifact.ByIDs(winget.IDs...))
}
archives := ctx.Artifacts.Filter(artifact.And(filters...)).List()
if len(archives) == 0 {
return errNoArchivesFound{
goamd64: winget.Goamd64,
ids: winget.IDs,
}
}
if winget.PackageIdentifier == "" {
winget.PackageIdentifier = publisher + "." + name
}
if !packageIdentifierValid.MatchString(winget.PackageIdentifier) {
return fmt.Errorf("%w: %s", errInvalidPackageIdentifier, winget.PackageIdentifier)
}
if err := createYAML(ctx, winget, Version{
PackageIdentifier: winget.PackageIdentifier,
PackageVersion: ctx.Version,
DefaultLocale: defaultLocale,
ManifestType: "version",
ManifestVersion: manifestVersion,
}, artifact.WingetVersion); err != nil {
return err
}
installer := Installer{
PackageIdentifier: winget.PackageIdentifier,
PackageVersion: ctx.Version,
InstallerLocale: defaultLocale,
InstallerType: "zip",
Commands: []string{},
ReleaseDate: ctx.Date.Format(time.DateOnly),
Installers: []InstallerItem{},
ManifestType: "installer",
ManifestVersion: manifestVersion,
}
var amd64Count, i386count int
for _, archive := range archives {
sha256, err := archive.Checksum("sha256")
if err != nil {
return err
}
var files []InstallerItemFile
folder := artifact.ExtraOr(*archive, artifact.ExtraWrappedIn, ".")
for _, bin := range artifact.ExtraOr(*archive, artifact.ExtraBinaries, []string{}) {
files = append(files, InstallerItemFile{
RelativeFilePath: windowsJoin([2]string{folder, bin}),
})
}
url, err := tmpl.New(ctx).WithArtifact(archive).Apply(winget.URLTemplate)
if err != nil {
return err
}
installer.Installers = append(installer.Installers, InstallerItem{
Architecture: fromGoArch[archive.Goarch],
NestedInstallerType: "portable",
NestedInstallerFiles: files,
InstallerURL: url,
InstallerSha256: sha256,
UpgradeBehavior: "uninstallPrevious",
})
switch archive.Goarch {
case "386":
i386count++
case "amd64":
amd64Count++
}
}
if i386count > 1 || amd64Count > 1 {
return errMultipleArchives
}
if err := createYAML(ctx, winget, installer, artifact.WingetInstaller); err != nil {
return err
}
return createYAML(ctx, winget, Locale{
PackageIdentifier: winget.PackageIdentifier,
PackageVersion: ctx.Version,
PackageLocale: defaultLocale,
Publisher: publisher,
PublisherURL: winget.PublisherURL,
Author: author,
PackageName: name,
PackageURL: winget.Homepage,
License: winget.License,
LicenseURL: winget.LicenseURL,
Copyright: winget.Copyright,
ShortDescription: shortDescription,
Description: description,
Moniker: name,
Tags: []string{},
ReleaseNotes: ctx.ReleaseNotes,
ReleaseNotesURL: winget.ReleaseNotesURL,
ManifestType: "defaultLocale",
ManifestVersion: manifestVersion,
}, artifact.WingetDefaultLocale)
}
func (p Pipe) publishAll(ctx *context.Context, cli client.Client) error {
skips := pipe.SkipMemento{}
for _, files := range ctx.Artifacts.Filter(artifact.Or(
artifact.ByType(artifact.WingetInstaller),
artifact.ByType(artifact.WingetVersion),
artifact.ByType(artifact.WingetDefaultLocale),
)).GroupByID() {
err := doPublish(ctx, cli, files)
if err != nil && pipe.IsSkip(err) {
skips.Remember(err)
continue
}
if err != nil {
return err
}
}
return skips.Evaluate()
}
func doPublish(ctx *context.Context, cl client.Client, wingets []*artifact.Artifact) error {
winget, err := artifact.Extra[config.Winget](*wingets[0], wingetConfigExtra)
if err != nil {
return err
}
if strings.TrimSpace(winget.SkipUpload) == "true" {
return errSkipUpload
}
if strings.TrimSpace(winget.SkipUpload) == "auto" && ctx.Semver.Prerelease != "" {
return errSkipUploadAuto
}
msg, err := tmpl.New(ctx).Apply(winget.CommitMessageTemplate)
if err != nil {
return err
}
author, err := commitauthor.Get(ctx, winget.CommitAuthor)
if err != nil {
return err
}
repo := client.RepoFromRef(winget.Repository)
var files []client.RepoFile
for _, pkg := range wingets {
content, err := os.ReadFile(pkg.Path)
if err != nil {
return err
}
files = append(files, client.RepoFile{
Content: content,
Path: filepath.Join(winget.Path, pkg.Name),
})
}
if winget.Repository.Git.URL != "" {
return client.NewGitUploadClient(repo.Branch).
CreateFiles(ctx, author, repo, msg, files)
}
cl, err = client.NewIfToken(ctx, cl, winget.Repository.Token)
if err != nil {
return err
}
for _, file := range files {
if err := cl.CreateFile(ctx, author, repo, file.Content, file.Path, msg); err != nil {
return err
}
}
if !winget.Repository.PullRequest.Enabled {
log.Debug("winget.pull_request disabled")
return nil
}
log.Info("winget.pull_request enabled, creating a PR")
pcl, ok := cl.(client.PullRequestOpener)
if !ok {
return fmt.Errorf("client does not support pull requests")
}
title := fmt.Sprintf("Updated %s to %s", ctx.Config.ProjectName, ctx.Version)
return pcl.OpenPullRequest(ctx, client.Repo{
Name: winget.Repository.PullRequest.Base.Name,
Owner: winget.Repository.PullRequest.Base.Owner,
Branch: winget.Repository.PullRequest.Base.Branch,
}, repo, title, winget.Repository.PullRequest.Draft)
}
func langserverLineFor(tp artifact.Type) string {
switch tp {
case artifact.WingetInstaller:
return installerLangServer
case artifact.WingetDefaultLocale:
return defaultLocaleLangServer
default:
return versionLangServer
}
}
func extFor(tp artifact.Type) string {
switch tp {
case artifact.WingetVersion:
return ".yaml"
case artifact.WingetInstaller:
return ".installer.yaml"
case artifact.WingetDefaultLocale:
return "." + defaultLocale + ".yaml"
default:
// should never happen
return ""
}
}
func windowsJoin(elem [2]string) string {
if elem[0] == "" {
return elem[1]
}
return elem[0] + "\\" + elem[1]
}

View File

@ -0,0 +1,579 @@
package winget
import (
"html/template"
"os"
"path/filepath"
"strings"
"testing"
"time"
"github.com/goreleaser/goreleaser/internal/artifact"
"github.com/goreleaser/goreleaser/internal/client"
"github.com/goreleaser/goreleaser/internal/golden"
"github.com/goreleaser/goreleaser/internal/testctx"
"github.com/goreleaser/goreleaser/pkg/config"
"github.com/stretchr/testify/require"
)
func TestString(t *testing.T) {
require.NotEmpty(t, Pipe{}.String())
}
func TestSkip(t *testing.T) {
t.Run("should", func(t *testing.T) {
require.True(t, Pipe{}.Skip(testctx.New()))
})
t.Run("should not", func(t *testing.T) {
require.False(t, Pipe{}.Skip(testctx.NewWithCfg(config.Project{
Winget: []config.Winget{{}},
})))
})
}
func TestRunPipe(t *testing.T) {
for _, tt := range []struct {
name string
expectRunErrorIs error
expectPublishErrorIs error
expectPath string
winget config.Winget
}{
{
name: "minimal",
expectPath: "manifests/f/Foo/min/1.2.1/min.",
winget: config.Winget{
Name: "min",
Publisher: "Foo",
License: "MIT",
ShortDescription: "foo bar zaz",
IDs: []string{"foo"},
Repository: config.RepoRef{
Owner: "foo",
Name: "bar",
},
},
},
{
name: "full",
expectPath: "manifests/b/Beckersoft LTDA/foo/1.2.1",
winget: config.Winget{
Name: "foo",
Publisher: "Beckersoft",
PublisherURL: "https://carlosbecker.com",
Copyright: "bla bla bla",
Author: "Carlos Becker",
Path: "manifests/b/Beckersoft LTDA/foo/{{.Version}}",
Repository: config.RepoRef{Owner: "foo", Name: "bar"},
CommitAuthor: config.CommitAuthor{},
CommitMessageTemplate: "update foo to latest and greatest",
IDs: []string{"foo"},
Goamd64: "v1",
SkipUpload: "false",
ShortDescription: "foo",
Description: `long foo bar
yadaa yada yada loooaaasssss
sss`,
Homepage: "https://goreleaser.com",
License: "MIT",
LicenseURL: "https://goreleaser.com/eula/",
ReleaseNotesURL: "https://github.com/goreleaser/goreleaser/tags/{{.Tag}}",
},
},
{
name: "open-pr",
winget: config.Winget{
Name: "foo",
Publisher: "Beckersoft",
IDs: []string{"foo"},
Description: "my test",
Homepage: "https://goreleaser.com",
License: "mit",
Path: "pkgs/foo.winget",
ShortDescription: "foo bar zaz",
Repository: config.RepoRef{
Owner: "foo",
Name: "bar",
Branch: "update-{{.Version}}",
PullRequest: config.PullRequest{
Enabled: true,
},
},
},
},
{
name: "wrapped-in-dir",
winget: config.Winget{
Name: "wrapped-in-dir",
Publisher: "Beckersoft",
IDs: []string{"wrapped-in-dir"},
Description: "my test",
Homepage: "https://goreleaser.com",
License: "mit",
LicenseURL: "https://goreleaser.com/license",
ReleaseNotesURL: "https://github.com/goreleaser/goreleaser/tags/{{.Tag}}",
ShortDescription: "foo bar zaz",
Repository: config.RepoRef{
Owner: "foo",
Name: "bar",
},
},
},
{
name: "no-archives",
expectRunErrorIs: errNoArchivesFound{
goamd64: "v2",
ids: []string{"nopenopenope"},
},
winget: config.Winget{
Name: "no-archives",
Publisher: "Beckersoft",
IDs: []string{"nopenopenope"},
Goamd64: "v2",
License: "MIT",
ShortDescription: "foo bar zaz",
Repository: config.RepoRef{
Owner: "foo",
Name: "bar",
},
},
},
{
name: "too-many-archives",
expectRunErrorIs: errMultipleArchives,
winget: config.Winget{
Name: "min",
Publisher: "Foo",
License: "MIT",
ShortDescription: "foo bar zaz",
IDs: []string{},
Repository: config.RepoRef{
Owner: "foo",
Name: "bar",
},
},
},
{
name: "partial",
winget: config.Winget{
Name: "partial",
Publisher: "Beckersoft",
IDs: []string{"partial"},
License: "MIT",
ShortDescription: "foo bar zaz",
Repository: config.RepoRef{
Owner: "foo",
Name: "bar",
},
},
},
{
name: "no-repo-name",
expectRunErrorIs: errNoRepoName,
winget: config.Winget{
Name: "doesnotmatter",
Publisher: "Beckersoft",
License: "MIT",
ShortDescription: "foo bar zaz",
Repository: config.RepoRef{
Owner: "foo",
},
},
},
{
name: "no-license",
expectRunErrorIs: errNoLicense,
winget: config.Winget{
Name: "doesnotmatter",
Publisher: "Beckersoft",
ShortDescription: "aa",
Repository: config.RepoRef{
Name: "foo",
Owner: "foo",
},
},
},
{
name: "no-short-description",
expectRunErrorIs: errNoShortDescription,
winget: config.Winget{
Name: "doesnotmatter",
Publisher: "Beckersoft",
License: "MIT",
Repository: config.RepoRef{
Name: "foo",
Owner: "foo",
},
},
},
{
name: "invalid-package-identifier",
expectRunErrorIs: errInvalidPackageIdentifier,
winget: config.Winget{
Name: "min",
PackageIdentifier: "foobar",
Publisher: "Foo",
License: "MIT",
ShortDescription: "foo bar zaz",
Repository: config.RepoRef{
Owner: "foo",
Name: "bar",
},
},
},
{
name: "no-publisher",
expectRunErrorIs: errNoPublisher,
winget: config.Winget{
Name: "min",
License: "MIT",
ShortDescription: "foo bar zaz",
Repository: config.RepoRef{
Owner: "foo",
Name: "bar",
},
},
},
{
name: "bad-name-tmpl",
expectRunErrorIs: &template.Error{},
winget: config.Winget{
Name: "{{ .Nope }}",
Publisher: "Beckersoft",
License: "MIT",
ShortDescription: "foo bar zaz",
Repository: config.RepoRef{
Owner: "foo",
Name: "bar",
},
},
},
{
name: "bad-publisher-tmpl",
expectRunErrorIs: &template.Error{},
winget: config.Winget{
Name: "foo",
Publisher: "{{ .Nope }}",
License: "MIT",
ShortDescription: "foo bar zaz",
Repository: config.RepoRef{
Owner: "foo",
Name: "bar",
},
},
},
{
name: "bad-publisher-url-tmpl",
expectRunErrorIs: &template.Error{},
winget: config.Winget{
Name: "foo",
Publisher: "Beckersoft",
PublisherURL: "{{ .Nope }}",
License: "MIT",
ShortDescription: "foo bar zaz",
Repository: config.RepoRef{
Owner: "foo",
Name: "bar",
},
},
},
{
name: "bad-author-tmpl",
expectRunErrorIs: &template.Error{},
winget: config.Winget{
Name: "foobar",
Publisher: "Beckersoft",
Author: "{{ .Nope }}",
License: "MIT",
ShortDescription: "foo bar zaz",
Repository: config.RepoRef{
Owner: "foo",
Name: "bar",
},
},
},
{
name: "bad-homepage-tmpl",
expectRunErrorIs: &template.Error{},
winget: config.Winget{
Name: "foobar",
Publisher: "Beckersoft",
Homepage: "{{ .Nope }}",
License: "MIT",
ShortDescription: "foo bar zaz",
Repository: config.RepoRef{
Owner: "foo",
Name: "bar",
},
},
},
{
name: "bad-description-tmpl",
expectRunErrorIs: &template.Error{},
winget: config.Winget{
Name: "foobar",
Publisher: "Beckersoft",
Description: "{{ .Nope }}",
License: "MIT",
ShortDescription: "foo bar zaz",
Repository: config.RepoRef{
Owner: "foo",
Name: "bar",
},
},
},
{
name: "bad-short-description-tmpl",
expectRunErrorIs: &template.Error{},
winget: config.Winget{
Name: "foobar",
Publisher: "Beckersoft",
ShortDescription: "{{ .Nope }}",
License: "MIT",
Repository: config.RepoRef{
Owner: "foo",
Name: "bar",
},
},
},
{
name: "bad-repo-tmpl",
expectRunErrorIs: &template.Error{},
winget: config.Winget{
Name: "doesnotmatter",
Publisher: "Beckersoft",
License: "MIT",
ShortDescription: "foo bar zaz",
Repository: config.RepoRef{
Owner: "foo",
Name: "{{ .Nope }}",
},
},
},
{
name: "bad-skip-upload-tmpl",
expectRunErrorIs: &template.Error{},
winget: config.Winget{
Name: "doesnotmatter",
Publisher: "Beckersoft",
SkipUpload: "{{ .Nope }}",
License: "MIT",
ShortDescription: "foo bar zaz",
Repository: config.RepoRef{
Owner: "foo",
Name: "bar",
},
},
},
{
name: "bad-release-notes-url-tmpl",
expectRunErrorIs: &template.Error{},
winget: config.Winget{
Name: "foo",
Publisher: "Beckersoft",
ReleaseNotesURL: `https://goo/bar/asdfsd/{{.nope}}`,
License: "MIT",
ShortDescription: "foo bar zaz",
Repository: config.RepoRef{
Owner: "foo",
Name: "bar",
},
},
},
{
name: "bad-release-url-tmpl",
expectRunErrorIs: &template.Error{},
winget: config.Winget{
Name: "foo",
Publisher: "Beckersoft",
URLTemplate: "{{.BadURL}}",
License: "MIT",
ShortDescription: "foo bar zaz",
Repository: config.RepoRef{
Owner: "foo",
Name: "bar",
},
},
},
{
name: "bad-path-tmpl",
expectRunErrorIs: &template.Error{},
winget: config.Winget{
Name: "foo",
Publisher: "Beckersoft",
License: "MIT",
Path: "{{ .Nope }}",
ShortDescription: "foo bar zaz",
Repository: config.RepoRef{
Owner: "foo",
Name: "bar",
},
},
},
{
name: "bad-commit-msg-tmpl",
expectPublishErrorIs: &template.Error{},
winget: config.Winget{
Name: "foo",
Publisher: "Beckersoft",
License: "MIT",
ShortDescription: "foo bar zaz",
CommitMessageTemplate: "{{.Foo}}",
IDs: []string{"foo"},
Repository: config.RepoRef{
Owner: "foo",
Name: "bar",
},
},
},
{
name: "skip-upload",
expectPublishErrorIs: errSkipUpload,
winget: config.Winget{
Name: "doesnotmatter",
Publisher: "Beckersoft",
SkipUpload: "true",
License: "MIT",
IDs: []string{"foo"},
ShortDescription: "foo bar zaz",
Repository: config.RepoRef{
Owner: "foo",
Name: "bar",
},
},
},
{
name: "skip-upload-auto",
expectPublishErrorIs: errSkipUploadAuto,
winget: config.Winget{
Name: "doesnotmatter",
Publisher: "Beckersoft",
License: "MIT",
ShortDescription: "foo bar zaz",
IDs: []string{"foo"},
SkipUpload: "auto",
Repository: config.RepoRef{
Owner: "foo",
Name: "bar",
},
},
},
} {
t.Run(tt.name, func(t *testing.T) {
folder := t.TempDir()
ctx := testctx.NewWithCfg(
config.Project{
Dist: folder,
ProjectName: "foo",
Winget: []config.Winget{tt.winget},
},
testctx.WithVersion("1.2.1"),
testctx.WithCurrentTag("v1.2.1"),
testctx.WithSemver(1, 2, 1, "rc1"),
testctx.WithDate(time.Date(2023, 6, 12, 20, 32, 10, 12, time.Local)),
)
createFakeArtifact := func(id, goos, goarch, goamd64, goarm string, extra map[string]any) {
path := filepath.Join(folder, "dist/foo_"+goos+goarch+goamd64+goarm+".zip")
art := artifact.Artifact{
Name: "foo_" + goos + "_" + goarch + goamd64 + goarm + ".zip",
Path: path,
Goos: goos,
Goarch: goarch,
Goarm: goarm,
Goamd64: goamd64,
Type: artifact.UploadableArchive,
Extra: map[string]interface{}{
artifact.ExtraID: id,
artifact.ExtraFormat: "zip",
artifact.ExtraBinaries: []string{"foo.exe"},
artifact.ExtraWrappedIn: "",
},
}
for k, v := range extra {
art.Extra[k] = v
}
ctx.Artifacts.Add(&art)
require.NoError(t, os.MkdirAll(filepath.Dir(path), 0o755))
f, err := os.Create(path)
require.NoError(t, err)
require.NoError(t, f.Close())
}
goos := "windows"
goarch := "amd64"
createFakeArtifact("partial", goos, goarch, "v1", "", nil)
createFakeArtifact("foo", goos, goarch, "v1", "", nil)
createFakeArtifact("wrapped-in-dir", goos, goarch, "v1", "", map[string]any{artifact.ExtraWrappedIn: "foo"})
goarch = "3864"
createFakeArtifact("foo", goos, goarch, "", "", nil)
createFakeArtifact("wrapped-in-dir", goos, goarch, "", "", map[string]any{artifact.ExtraWrappedIn: "foo"})
client := client.NewMock()
pipe := Pipe{}
// default
require.NoError(t, pipe.Default(ctx))
// run
if tt.expectRunErrorIs != nil {
err := pipe.runAll(ctx, client)
require.Error(t, err)
require.ErrorAs(t, err, &tt.expectPublishErrorIs)
return
}
require.NoError(t, pipe.runAll(ctx, client))
for _, winget := range ctx.Artifacts.Filter(artifact.Or(
artifact.ByType(artifact.WingetInstaller),
artifact.ByType(artifact.WingetVersion),
artifact.ByType(artifact.WingetDefaultLocale),
)).List() {
bts, err := os.ReadFile(winget.Path)
require.NoError(t, err)
golden.RequireEqualExtSubfolder(t, bts, extFor(winget.Type))
}
// publish
if tt.expectPublishErrorIs != nil {
err := pipe.publishAll(ctx, client)
require.Error(t, err)
require.ErrorAs(t, err, &tt.expectPublishErrorIs)
return
}
require.NoError(t, pipe.publishAll(ctx, client))
require.True(t, client.CreatedFile)
require.NotEmpty(t, client.Path)
if tt.expectPath != "" {
require.Truef(t, strings.HasPrefix(client.Path, tt.expectPath), "expected %q to begin with %q", client.Path, tt.expectPath)
}
if tt.winget.Repository.PullRequest.Enabled {
require.True(t, client.OpenedPullRequest)
}
})
}
}
func TestErrNoArchivesFound(t *testing.T) {
require.EqualError(t, errNoArchivesFound{
goamd64: "v1",
ids: []string{"foo", "bar"},
}, "no zip archives found matching goos=[windows] goarch=[amd64 386] goamd64=v1 ids=[foo bar]")
}
func TestDefault(t *testing.T) {
ctx := testctx.NewWithCfg(config.Project{
ProjectName: "foo",
Winget: []config.Winget{{}},
})
require.NoError(t, Pipe{}.Default(ctx))
winget := ctx.Config.Winget[0]
require.Equal(t, "v1", winget.Goamd64)
require.NotEmpty(t, winget.CommitMessageTemplate)
require.Equal(t, "foo", winget.Name)
}

View File

@ -36,6 +36,7 @@ import (
"github.com/goreleaser/goreleaser/internal/pipe/sourcearchive"
"github.com/goreleaser/goreleaser/internal/pipe/universalbinary"
"github.com/goreleaser/goreleaser/internal/pipe/upx"
"github.com/goreleaser/goreleaser/internal/pipe/winget"
"github.com/goreleaser/goreleaser/pkg/context"
)
@ -112,6 +113,8 @@ var Pipeline = append(
aur.Pipe{},
// create nixpkgs
nix.NewBuild(),
// winget installers
winget.Pipe{},
// create brew tap
brew.Pipe{},
// krew plugins

View File

@ -260,6 +260,29 @@ type Nix struct {
License string `yaml:"license,omitempty" json:"license,omitempty"`
}
type Winget struct {
Name string `yaml:"name,omitempty" json:"name,omitempty"`
PackageIdentifier string `yaml:"package_identifier,omitempty" json:"package_identifier,omitempty"`
Publisher string `yaml:"publisher,omitempty" json:"publisher,omitempty"`
PublisherURL string `yaml:"publisher_url,omitempty" json:"publisher_url,omitempty"`
Copyright string `yaml:"copyright,omitempty" json:"copyright,omitempty"`
Author string `yaml:"author,omitempty" json:"author,omitempty"`
Path string `yaml:"path,omitempty" json:"path,omitempty"`
Repository RepoRef `yaml:"repository,omitempty" json:"repository,omitempty"`
CommitAuthor CommitAuthor `yaml:"commit_author,omitempty" json:"commit_author,omitempty"`
CommitMessageTemplate string `yaml:"commit_msg_template,omitempty" json:"commit_msg_template,omitempty"`
IDs []string `yaml:"ids,omitempty" json:"ids,omitempty"`
Goamd64 string `yaml:"goamd64,omitempty" json:"goamd64,omitempty"`
SkipUpload string `yaml:"skip_upload,omitempty" json:"skip_upload,omitempty" jsonschema:"oneof_type=string;boolean"`
URLTemplate string `yaml:"url_template,omitempty" json:"url_template,omitempty"`
ShortDescription string `yaml:"short_description,omitempty" json:"short_description,omitempty"`
Description string `yaml:"description,omitempty" json:"description,omitempty"`
Homepage string `yaml:"homepage,omitempty" json:"homepage,omitempty"`
License string `yaml:"license,omitempty" json:"license,omitempty"`
LicenseURL string `yaml:"license_url,omitempty" json:"license_url,omitempty"`
ReleaseNotesURL string `yaml:"release_notes_url,omitempty" json:"release_notes_url,omitempty"`
}
// Krew contains the krew section.
type Krew struct {
IDs []string `yaml:"ids,omitempty" json:"ids,omitempty"`
@ -1036,6 +1059,7 @@ type Project struct {
Milestones []Milestone `yaml:"milestones,omitempty" json:"milestones,omitempty"`
Brews []Homebrew `yaml:"brews,omitempty" json:"brews,omitempty"`
Nix []Nix `yaml:"nix,omitempty" json:"nix,omitempty"`
Winget []Winget `yaml:"winget,omitempty" json:"winget,omitempty"`
AURs []AUR `yaml:"aurs,omitempty" json:"aurs,omitempty"`
Krews []Krew `yaml:"krews,omitempty" json:"krews,omitempty"`
Kos []Ko `yaml:"kos,omitempty" json:"kos,omitempty"`

View File

@ -43,6 +43,7 @@ import (
"github.com/goreleaser/goreleaser/internal/pipe/upload"
"github.com/goreleaser/goreleaser/internal/pipe/upx"
"github.com/goreleaser/goreleaser/internal/pipe/webhook"
"github.com/goreleaser/goreleaser/internal/pipe/winget"
"github.com/goreleaser/goreleaser/pkg/context"
)
@ -80,6 +81,7 @@ var Defaulters = []Defaulter{
upload.Pipe{},
aur.Pipe{},
nix.Pipe{},
winget.Pipe{},
brew.Pipe{},
krew.Pipe{},
ko.Pipe{},

View File

@ -0,0 +1,111 @@
# Winget
> Since: v1.19
After releasing to GitHub, GitLab, or Gitea, GoReleaser can generate and publish
a _winget manifest_ and commit to a git repository, and PR it to `winget-pkgs`
if instructed to.
The `winget` section specifies how the **manifests** should be created:
```yaml
# .goreleaser.yaml
winget:
- # Name of the recipe
#
# Default: ProjectName
# Templates: allowed
name: myproject
# Publisher name.
#
# Templates: allowed
# Required.
publisher: Foo Inc
# Your app's description.
#
# Templates: allowed
# Required.
short_description: "Software to create fast and easy drum rolls."
# License name.
# Required.
license: "mit"
# Publisher URL.
#
# Templates: allowed
publisher_url: https://goreleaser.com
# Package identifier.
#
# Default: Publisher.ProjectName
# Templates: allowed
package_identifier: myproject.myproject
# IDs of the archives to use.
# Empty means all IDs.
ids:
- foo
- bar
# GOAMD64 to specify which amd64 version to use if there are multiple
# versions from the build section.
#
# Default: v1
goamd64: v1
# URL which is determined by the given Token (github, gitlab or gitea).
#
# Default depends on the client.
# Templates: allowed
url_template: "https://github.mycompany.com/foo/bar/releases/download/{{ .Tag }}/{{ .ArtifactName }}"
# Git author used to commit to the repository.
commit_author:
name: goreleaserbot
email: bot@goreleaser.com
# The project name and current git tag are used in the format string.
#
# Templates: allowed
commit_msg_template: "{{ .ProjectName }}: {{ .Tag }}"
# Path for the file inside the repository.
#
# Default: manifests/<lowercased first char of publisher>/<publisher>/<version>
path: manifests/g/goreleaser/1.19
# Your app's homepage.
homepage: "https://example.com/"
# Your app's long description.
#
# Templates: allowed
description: "Software to create fast and easy drum rolls."
# License URL.
license_url: "https://goreleaser.com/license"
# Copyright.
copyright: "Becker Software LTDA"
# Setting this will prevent goreleaser to actually try to commit the updated
# package - instead, it will be stored on the dist folder only,
# leaving the responsibility of publishing it to the user.
#
# If set to auto, the release will not be uploaded to the repository
# in case there is an indicator for prerelease in the tag e.g. v1.0.0-rc1
#
# Templates: allowed
skip_upload: true
{% include-markdown "../includes/repository.md" comments=false %}
```
!!! tip
Learn more about the [name template engine](/customization/templates/).
{% include-markdown "../includes/prs.md" comments=false %}

View File

@ -131,6 +131,7 @@ nav:
- customization/fury.md
- customization/homebrew.md
- customization/nix.md
- customization/winget.md
- customization/aur.md
- customization/krew.md
- customization/scoop.md