From d54da349d5542a1c5a730fb10a992249590d7116 Mon Sep 17 00:00:00 2001
From: unilock <unilock@fennet.rentals>
Date: Mon, 15 Apr 2024 15:30:22 -0400
Subject: [PATCH] github: allow using a regular expression to match assets

safeguard against "undefined behavior" when a release has more than one asset

Signed-off-by: unilock <unilock@fennet.rentals>
---
 github/install.go | 45 +++++++++++++++++++++++++++++++++------------
 github/updater.go | 19 ++++++++++++++++---
 2 files changed, 49 insertions(+), 15 deletions(-)

diff --git a/github/install.go b/github/install.go
index add69e9..ea862f4 100644
--- a/github/install.go
+++ b/github/install.go
@@ -8,7 +8,6 @@ import (
 	"os"
 	"path/filepath"
 	"regexp"
-	"strings"
 
 	"github.com/packwiz/packwiz/core"
 	"github.com/spf13/cobra"
@@ -39,6 +38,15 @@ var installCmd = &cobra.Command{
 		var slug string
 		var branch string
 
+		// Regex to match potential release assets against.
+		// The default will match any asset with a name that does *not* end with:
+		// - "-api.jar"
+		// - "-dev.jar"
+		// - "-sources.jar"
+		// In most cases, this will only match one asset.
+		// TODO: Hopefully.
+		regex := `^.+(?<!-api|-dev|-sources)\.jar$`
+
 		// Check if the argument is a valid GitHub repository URL; if so, extract the slug from the URL.
 		// Otherwise, interpret the argument as a slug directly.
 		matches := GithubRegex.FindStringSubmatch(args[0])
@@ -58,8 +66,11 @@ var installCmd = &cobra.Command{
 		if branchFlag != "" {
 			branch = branchFlag
 		}
+		if regexFlag != "" {
+			regex = regexFlag
+		}
 
-		err = installMod(repo, branch, pack)
+		err = installMod(repo, branch, regex, pack)
 		if err != nil {
 			fmt.Printf("Failed to add project: %s\n", err)
 			os.Exit(1)
@@ -67,13 +78,13 @@ var installCmd = &cobra.Command{
 	},
 }
 
-func installMod(repo Repo, branch string, pack core.Pack) error {
+func installMod(repo Repo, branch string, regex string, pack core.Pack) error {
 	latestRelease, err := getLatestRelease(repo.FullName, branch)
 	if err != nil {
 		return fmt.Errorf("failed to get latest release: %v", err)
 	}
 
-	return installRelease(repo, latestRelease, pack)
+	return installRelease(repo, latestRelease, regex, pack)
 }
 
 func getLatestRelease(slug string, branch string) (Release, error) {
@@ -108,21 +119,28 @@ func getLatestRelease(slug string, branch string) (Release, error) {
 	return releases[0], nil
 }
 
-func installRelease(repo Repo, release Release, pack core.Pack) error {
-	var files = release.Assets
+func installRelease(repo Repo, release Release, regex string, pack core.Pack) error {
+	expr := regexp.MustCompile(regex)
 
-	if len(files) == 0 {
-		return errors.New("release doesn't have any files attached")
+	if len(release.Assets) == 0 {
+		return errors.New("release doesn't have any assets attached")
 	}
 
-	// TODO: add some way to allow users to pick which file to install?
-	var file = files[0]
+	var files []Asset
+
 	for _, v := range release.Assets {
-		if strings.HasSuffix(v.Name, ".jar") {
-			file = v
+		if expr.MatchString(v.Name) {
+			files = append(files, v)
 		}
 	}
 
+	if len(files) > 1 {
+		// TODO: also print file names
+		return errors.New("release has more than one asset matching regex")
+	}
+
+	file := files[0]
+
 	// Install the file
 	fmt.Printf("Installing %s from release %s\n", file.Name, release.TagName)
 	index, err := pack.LoadIndex()
@@ -136,6 +154,7 @@ func installRelease(repo Repo, release Release, pack core.Pack) error {
 		Slug:   repo.FullName,
 		Tag:    release.TagName,
 		Branch: release.TargetCommitish, // TODO: if no branch is specified by the user, we shouldn't record it - in order to remain branch-agnostic in getLatestRelease()
+		Regex: regex, // TODO: ditto!
 	}.ToMap()
 	if err != nil {
 		return err
@@ -196,9 +215,11 @@ func installRelease(repo Repo, release Release, pack core.Pack) error {
 }
 
 var branchFlag string
+var regexFlag string
 
 func init() {
 	githubCmd.AddCommand(installCmd)
 
 	installCmd.Flags().StringVar(&branchFlag, "branch", "", "The GitHub repository branch to retrieve releases for")
+	installCmd.Flags().StringVar(&regexFlag, "regex", "", "The regular expression to match releases against")
 }
diff --git a/github/updater.go b/github/updater.go
index 157d067..9f66cb8 100644
--- a/github/updater.go
+++ b/github/updater.go
@@ -3,6 +3,7 @@ package github
 import (
 	"errors"
 	"fmt"
+	"regexp"
 	"strings"
 
 	"github.com/mitchellh/mapstructure"
@@ -13,6 +14,7 @@ type ghUpdateData struct {
 	Slug   string `mapstructure:"slug"`
 	Tag    string `mapstructure:"tag"`
 	Branch string `mapstructure:"branch"`
+	Regex  string `mapstructure:"regex"`
 }
 
 type ghUpdater struct{}
@@ -51,18 +53,29 @@ func (u ghUpdater) CheckUpdate(mods []*core.Mod, pack core.Pack) ([]core.UpdateC
 			continue
 		}
 
+		expr := regexp.MustCompile(data.Regex)
+
 		if len(newRelease.Assets) == 0 {
 			results[i] = core.UpdateCheck{Error: errors.New("new release doesn't have any assets")}
 			continue
 		}
 
-		newFile := newRelease.Assets[0]
+		var newFiles []Asset
+
 		for _, v := range newRelease.Assets {
-			if strings.HasSuffix(v.Name, ".jar") {
-				newFile = v
+			if expr.MatchString(v.Name) {
+				newFiles = append(newFiles, v)
 			}
 		}
 
+		if len(newFiles) > 1 {
+			// TODO: also print file names
+			results[i] = core.UpdateCheck{Error: errors.New("release has more than one asset matching regex")}
+			continue
+		}
+
+		newFile := newFiles[0]
+
 		results[i] = core.UpdateCheck{
 			UpdateAvailable: true,
 			UpdateString:    mod.FileName + " -> " + newFile.Name,