Add migrate command (#207)

* Add base and loader command

* Support updating to latest

* Support explicit fabric versions

* Support explicit Forge version

* Support quilt

* Add support for updating Minecraft

* Add support for Forge recommended

* Fix Forge version sorting

* Various fixes

* Add documentation to the use

* More suggestions
This commit is contained in:
Matt Artist 2023-04-12 21:23:37 -04:00 committed by GitHub
parent 06f9204cd4
commit 539be71d11
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 354 additions and 57 deletions

View File

@ -2,19 +2,16 @@ package cmd
import (
"bufio"
"encoding/json"
"fmt"
"os"
"path/filepath"
"sort"
"strings"
"time"
"github.com/fatih/camelcase"
"github.com/igorsobreira/titlecase"
"github.com/packwiz/packwiz/cmdshared"
"github.com/packwiz/packwiz/core"
"github.com/spf13/cobra"
"github.com/spf13/viper"
"os"
"path/filepath"
"strings"
)
// initCmd represents the init command
@ -59,7 +56,7 @@ var initCmd = &cobra.Command{
version = initReadValue("Version [1.0.0]: ", "1.0.0")
}
mcVersions, err := getValidMCVersions()
mcVersions, err := cmdshared.GetValidMCVersions()
if err != nil {
fmt.Printf("Failed to get latest minecraft versions: %s\n", err)
os.Exit(1)
@ -79,7 +76,7 @@ var initCmd = &cobra.Command{
mcVersion = initReadValue("Minecraft version ["+latestVersion+"]: ", latestVersion)
}
}
mcVersions.checkValid(mcVersion)
mcVersions.CheckValid(mcVersion)
modLoaderName := strings.ToLower(viper.GetString("init.modloader"))
if len(modLoaderName) == 0 {
@ -239,45 +236,3 @@ func initReadValue(prompt string, def string) string {
}
return def
}
type mcVersionManifest struct {
Latest struct {
Release string `json:"release"`
Snapshot string `json:"snapshot"`
} `json:"latest"`
Versions []struct {
ID string `json:"id"`
Type string `json:"type"`
URL string `json:"url"`
Time time.Time `json:"time"`
ReleaseTime time.Time `json:"releaseTime"`
} `json:"versions"`
}
func (m mcVersionManifest) checkValid(version string) {
for _, v := range m.Versions {
if v.ID == version {
return
}
}
fmt.Println("Given version is not a valid Minecraft version!")
os.Exit(1)
}
func getValidMCVersions() (mcVersionManifest, error) {
res, err := core.GetWithUA("https://launchermeta.mojang.com/mc/game/version_manifest.json", "application/json")
if err != nil {
return mcVersionManifest{}, err
}
dec := json.NewDecoder(res.Body)
out := mcVersionManifest{}
err = dec.Decode(&out)
if err != nil {
return mcVersionManifest{}, err
}
// Sort by newest to oldest
sort.Slice(out.Versions, func(i, j int) bool {
return out.Versions[i].ReleaseTime.Before(out.Versions[j].ReleaseTime)
})
return out, nil
}

View File

@ -9,8 +9,8 @@ import (
"os"
)
// updateCmd represents the update command
var updateCmd = &cobra.Command{
// UpdateCmd represents the update command
var UpdateCmd = &cobra.Command{
Use: "update [name]",
Short: "Update an external file (or all external files) in the modpack",
Aliases: []string{"upgrade"},
@ -210,8 +210,8 @@ var updateCmd = &cobra.Command{
}
func init() {
rootCmd.AddCommand(updateCmd)
rootCmd.AddCommand(UpdateCmd)
updateCmd.Flags().BoolP("all", "a", false, "Update all external files")
_ = viper.BindPFlag("update.all", updateCmd.Flags().Lookup("all"))
UpdateCmd.Flags().BoolP("all", "a", false, "Update all external files")
_ = viper.BindPFlag("update.all", UpdateCmd.Flags().Lookup("all"))
}

52
cmdshared/mcversion.go Normal file
View File

@ -0,0 +1,52 @@
package cmdshared
import (
"encoding/json"
"fmt"
"github.com/packwiz/packwiz/core"
"os"
"sort"
"time"
)
type McVersionManifest struct {
Latest struct {
Release string `json:"release"`
Snapshot string `json:"snapshot"`
} `json:"latest"`
Versions []struct {
ID string `json:"id"`
Type string `json:"type"`
URL string `json:"url"`
Time time.Time `json:"time"`
ReleaseTime time.Time `json:"releaseTime"`
} `json:"versions"`
}
func (m McVersionManifest) CheckValid(version string) {
for _, v := range m.Versions {
if v.ID == version {
return
}
}
fmt.Println("Given version is not a valid Minecraft version!")
os.Exit(1)
}
func GetValidMCVersions() (McVersionManifest, error) {
res, err := core.GetWithUA("https://launchermeta.mojang.com/mc/game/version_manifest.json", "application/json")
if err != nil {
return McVersionManifest{}, err
}
dec := json.NewDecoder(res.Body)
out := McVersionManifest{}
err = dec.Decode(&out)
if err != nil {
return McVersionManifest{}, err
}
// Sort by newest to oldest
sort.Slice(out.Versions, func(i, j int) bool {
return out.Versions[i].ReleaseTime.Before(out.Versions[j].ReleaseTime)
})
return out, nil
}

16
cmdshared/utils.go Normal file
View File

@ -0,0 +1,16 @@
package cmdshared
import "strings"
func GetRawForgeVersion(version string) string {
var wantedVersion string
// Check if we have a "-" in the version
if strings.Contains(version, "-") {
// We have a mcVersion-loaderVersion format
// Strip the mcVersion
wantedVersion = strings.Split(version, "-")[1]
} else {
wantedVersion = version
}
return wantedVersion
}

View File

@ -1,8 +1,10 @@
package core
import (
"encoding/json"
"encoding/xml"
"errors"
"fmt"
"github.com/unascribed/FlexVer/go/flexver"
"strings"
)
@ -95,7 +97,7 @@ func FetchMavenVersionPrefixedList(url string, friendlyName string) func(mcVersi
return allowedVersions, out.Versioning.Latest, nil
}
// Sort list to get largest version
flexver.VersionSlice(out.Versioning.Versions.Version).Sort()
flexver.VersionSlice(allowedVersions).Sort()
return allowedVersions, allowedVersions[len(allowedVersions)-1], nil
}
}
@ -158,3 +160,34 @@ func HighestSliceIndex(slice []string, values []string) int {
}
return highest
}
type ForgeRecommended struct {
Homepage string `json:"homepage"`
Versions map[string]string `json:"promos"`
}
// GetForgeRecommended gets the recommended version of Forge for the given Minecraft version
func GetForgeRecommended(mcVersion string) string {
res, err := GetWithUA("https://files.minecraftforge.net/net/minecraftforge/forge/promotions_slim.json", "application/json")
if err != nil {
return ""
}
dec := json.NewDecoder(res.Body)
out := ForgeRecommended{}
err = dec.Decode(&out)
if err != nil {
fmt.Println(err)
return ""
}
// Get mcVersion-recommended, if it doesn't exist then get mcVersion-latest
// If neither exist, return empty string
recommendedString := fmt.Sprintf("%s-recommended", mcVersion)
if out.Versions[recommendedString] != "" {
return out.Versions[recommendedString]
}
latestString := fmt.Sprintf("%s-latest", mcVersion)
if out.Versions[latestString] != "" {
return out.Versions[latestString]
}
return ""
}

View File

@ -4,6 +4,7 @@ import (
// Modules of packwiz
"github.com/packwiz/packwiz/cmd"
_ "github.com/packwiz/packwiz/curseforge"
_ "github.com/packwiz/packwiz/migrate"
_ "github.com/packwiz/packwiz/modrinth"
_ "github.com/packwiz/packwiz/settings"
_ "github.com/packwiz/packwiz/url"

154
migrate/loader.go Normal file
View File

@ -0,0 +1,154 @@
package migrate
import (
"fmt"
"github.com/packwiz/packwiz/cmdshared"
"github.com/packwiz/packwiz/core"
"github.com/spf13/cobra"
"golang.org/x/exp/slices"
"os"
)
var loaderCommand = &cobra.Command{
Use: "loader [version|latest|recommended]",
Short: "Migrate your modloader version to a newer version.",
Args: cobra.ExactArgs(1),
Run: func(cmd *cobra.Command, args []string) {
modpack, err := core.LoadPack()
if err != nil {
// Check if it's a no such file or directory error
if os.IsNotExist(err) {
fmt.Println("No pack.toml file found, run 'packwiz init' to create one!")
os.Exit(1)
}
fmt.Printf("Error loading pack: %s\n", err)
os.Exit(1)
}
// Get our current loader, would use the modpack.GetLoaders function
// but, it supplements Quilt with Fabric, which isn't needed for this
var currentLoaders []string
// Add all the keys from modpack.Versions
for key := range modpack.Versions {
// If it ain't in core.ModLoaders, we don't want it
if _, ok := core.ModLoaders[key]; !ok {
continue
}
currentLoaders = append(currentLoaders, key)
}
// Do some sanity checks on the current loader slice
if len(currentLoaders) == 0 {
fmt.Println("No loader is currently set in your pack.toml!")
os.Exit(1)
} else if len(currentLoaders) > 1 {
fmt.Println("You have multiple loaders set in your pack.toml, this is not supported!")
os.Exit(1)
}
// Get the Minecraft version for the pack
mcVersion, err := modpack.GetMCVersion()
if err != nil {
fmt.Printf("Error getting Minecraft version: %s\n", err)
os.Exit(1)
}
if args[0] == "latest" {
fmt.Println("Updating to latest loader version")
// We'll be updating to the latest loader version
for _, loader := range currentLoaders {
_, latest, gottenLoader := getVersionsForLoader(loader, mcVersion)
if !updatePackToVersion(latest, modpack, gottenLoader) {
continue
}
// Write the pack to disk
err = modpack.Write()
if err != nil {
fmt.Printf("Error writing pack.toml: %s\n", err)
continue
}
}
} else if args[0] == "recommended" {
// TODO: Figure out a way to get the recommended version, this is Forge only
// Ensure we're on Forge
if !slices.Contains(currentLoaders, "forge") {
fmt.Println("The recommended loader version is only available on Forge!")
os.Exit(1)
}
// We'll be updating to the recommended loader version
recommendedVer := core.GetForgeRecommended(mcVersion)
if recommendedVer == "" {
fmt.Println("Error getting recommended Forge version!")
os.Exit(1)
}
if ok := updatePackToVersion(recommendedVer, modpack, core.ModLoaders["forge"]); !ok {
os.Exit(1)
}
// Write the pack to disk
err = modpack.Write()
if err != nil {
fmt.Printf("Error writing pack.toml: %s", err)
os.Exit(1)
}
} else {
fmt.Println("Updating to explicit loader version")
// This one is easy :D
versions, _, loader := getVersionsForLoader(currentLoaders[0], mcVersion)
// Check if the loader happens to be Forge, since there's two version formats
if loader.Name == "forge" {
wantedVersion := cmdshared.GetRawForgeVersion(args[0])
validateVersion(versions, wantedVersion, loader)
_ = updatePackToVersion(wantedVersion, modpack, loader)
} else if loader.Name == "liteloader" {
// These are weird and just have a MC version
fmt.Println("LiteLoader only has 1 version per Minecraft version so we're unable to update!")
os.Exit(0)
} else {
// We're on Fabric or quilt
validateVersion(versions, args[0], loader)
if ok := updatePackToVersion(args[0], modpack, loader); !ok {
os.Exit(1)
}
}
// Write the pack to disk
err = modpack.Write()
if err != nil {
fmt.Printf("Error writing pack.toml: %s\n", err)
os.Exit(1)
}
}
},
}
func init() {
migrateCmd.AddCommand(loaderCommand)
}
func getVersionsForLoader(loader, mcVersion string) ([]string, string, core.ModLoaderComponent) {
gottenLoader, ok := core.ModLoaders[loader]
if !ok {
fmt.Printf("Unknown loader %s\n", loader)
os.Exit(1)
}
versions, latestVersion, err := gottenLoader.VersionListGetter(mcVersion)
if err != nil {
fmt.Printf("Error getting version list for %s: %s\n", gottenLoader.FriendlyName, err)
os.Exit(1)
}
return versions, latestVersion, gottenLoader
}
func validateVersion(versions []string, version string, gottenLoader core.ModLoaderComponent) {
if !slices.Contains(versions, version) {
fmt.Printf("Version %s is not a valid version for %s\n", version, gottenLoader.FriendlyName)
os.Exit(1)
}
}
func updatePackToVersion(version string, modpack core.Pack, loader core.ModLoaderComponent) bool {
// Check if the version is already set
if version == modpack.Versions[loader.Name] {
fmt.Printf("%s is already on version %s!\n", loader.FriendlyName, version)
return false
}
// Set the latest version
modpack.Versions[loader.Name] = version
fmt.Printf("Updated %s to version %s\n", loader.FriendlyName, version)
return true
}

16
migrate/migrate.go Normal file
View File

@ -0,0 +1,16 @@
package migrate
import (
"github.com/packwiz/packwiz/cmd"
"github.com/spf13/cobra"
)
// migrateCmd represents the base command when called without any subcommands
var migrateCmd = &cobra.Command{
Use: "migrate [minecraft|loader]",
Short: "Migrate your Minecraft and loader versions to newer versions.",
}
func init() {
cmd.Add(migrateCmd)
}

70
migrate/minecraft.go Normal file
View File

@ -0,0 +1,70 @@
package migrate
import (
"fmt"
packCmd "github.com/packwiz/packwiz/cmd"
"github.com/packwiz/packwiz/cmdshared"
"github.com/packwiz/packwiz/core"
"github.com/spf13/cobra"
"github.com/spf13/viper"
"os"
)
var minecraftCommand = &cobra.Command{
Use: "minecraft [version]",
Short: "Migrate your Minecraft version to a newer version.",
Aliases: []string{"mc"},
Args: cobra.ExactArgs(1),
Run: func(cmd *cobra.Command, args []string) {
modpack, err := core.LoadPack()
if err != nil {
// Check if it's a no such file or directory error
if os.IsNotExist(err) {
fmt.Println("No pack.toml file found, run 'packwiz init' to create one!")
os.Exit(1)
}
fmt.Printf("Error loading pack: %s\n", err)
os.Exit(1)
}
currentVersion, err := modpack.GetMCVersion()
if err != nil {
fmt.Printf("Error getting Minecraft version from pack: %s\n", err)
os.Exit(1)
}
wantedMCVersion := args[0]
if wantedMCVersion == currentVersion {
fmt.Printf("Minecraft version is already %s!\n", wantedMCVersion)
os.Exit(0)
}
mcVersions, err := cmdshared.GetValidMCVersions()
if err != nil {
fmt.Printf("Error getting Minecraft versions: %s\n", err)
os.Exit(1)
}
mcVersions.CheckValid(wantedMCVersion)
// Set the version in the pack
modpack.Versions["minecraft"] = wantedMCVersion
// Write the pack to disk
err = modpack.Write()
if err != nil {
fmt.Printf("Error writing pack.toml: %s\n", err)
os.Exit(1)
}
fmt.Printf("Successfully updated Minecraft version to %s\n", wantedMCVersion)
// Prompt the user if they want to update the loader too while they're at it.
if cmdshared.PromptYesNo("Would you like to update your loader version to the latest version for this Minecraft version? [Y/n] ") {
// We'll run the loader command to update to latest
loaderCommand.Run(loaderCommand, []string{"latest"})
}
// Prompt the user to update their mods too.
if cmdshared.PromptYesNo("Would you like to update your mods to the latest versions for this Minecraft version? [Y/n] ") {
// Run the update command
viper.Set("update.all", true)
packCmd.UpdateCmd.Run(packCmd.UpdateCmd, []string{})
}
},
}
func init() {
migrateCmd.AddCommand(minecraftCommand)
}