From 3640e5e708172fc2642f3930ac51ec416d6b0800 Mon Sep 17 00:00:00 2001 From: mircearoata Date: Fri, 4 Oct 2024 19:23:51 +0200 Subject: [PATCH] feat: client and server-only mods (#71) * feat: local registry database migrations * chore: store raw API responses in local registry * feat: update ficsit-resolver * feat: client and server-only mods * fix: remove mods that no longer support the current target --- cli/installations.go | 13 +++- cli/localregistry/migrations.go | 106 ++++++++++++++++++++++++++++ cli/localregistry/registry.go | 64 ++++++----------- cli/provider/converter.go | 41 +++++++++++ cli/provider/ficsit.go | 35 +-------- cli/provider/local.go | 2 +- cli/resolving_test.go | 104 +++++++++++++++++++++++++++ cli/test_helpers.go | 121 +++++++++++++++++++++++++------- ficsit/types_rest.go | 11 +-- go.mod | 2 +- go.sum | 4 +- 11 files changed, 392 insertions(+), 111 deletions(-) create mode 100644 cli/localregistry/migrations.go create mode 100644 cli/provider/converter.go diff --git a/cli/installations.go b/cli/installations.go index cb65155..84b648d 100644 --- a/cli/installations.go +++ b/cli/installations.go @@ -429,7 +429,13 @@ func (i *Installation) Install(ctx *GlobalContext, updates chan<- InstallUpdate) var deleteWait errgroup.Group for _, entry := range dir { if entry.IsDir() { - if _, ok := lockfile.Mods[entry.Name()]; !ok { + modName := entry.Name() + mod, hasMod := lockfile.Mods[modName] + if hasMod { + _, hasTarget := mod.Targets[platform.TargetName] + hasMod = hasTarget + } + if !hasMod { modName := entry.Name() modDir := filepath.Join(modsDirectory, modName) deleteWait.Go(func() error { @@ -493,7 +499,10 @@ func (i *Installation) Install(ctx *GlobalContext, updates chan<- InstallUpdate) target, ok := version.Targets[platform.TargetName] if !ok { - return fmt.Errorf("%s@%s not available for %s", modReference, version.Version, platform.TargetName) + // The resolver validates that the resulting lockfile mods can be installed on the sides where they are required + // so if the mod is missing this target, it means it is not required on this target + slog.Info("skipping mod not available for target", slog.String("mod_reference", modReference), slog.String("version", version.Version), slog.String("target", platform.TargetName)) + return nil } // Only install if a link is provided, otherwise assume mod is already installed diff --git a/cli/localregistry/migrations.go b/cli/localregistry/migrations.go new file mode 100644 index 0000000..8ecc54f --- /dev/null +++ b/cli/localregistry/migrations.go @@ -0,0 +1,106 @@ +package localregistry + +import ( + "database/sql" + "fmt" +) + +var migrations = []func(*sql.Tx) error{ + initialSetup, + addRequiredOnRemote, +} + +func applyMigrations(db *sql.DB) error { + // user_version will store the 1-indexed migration that was last applied + var nextMigration int + err := db.QueryRow("PRAGMA user_version;").Scan(&nextMigration) + if err != nil { + return fmt.Errorf("failed to get user_version: %w", err) + } + + for i := nextMigration; i < len(migrations); i++ { + err := applyMigration(db, i) + if err != nil { + return fmt.Errorf("failed to apply migration %d: %w", i, err) + } + } + + return nil +} + +func applyMigration(db *sql.DB, migrationIndex int) error { + tx, err := db.Begin() + if err != nil { + return fmt.Errorf("failed to start transaction: %w", err) + } + // Will noop if the transaction was committed + defer tx.Rollback() //nolint:errcheck + + err = migrations[migrationIndex](tx) + if err != nil { + return err + } + + _, err = tx.Exec(fmt.Sprintf("PRAGMA user_version = %d;", migrationIndex+1)) + if err != nil { + return fmt.Errorf("failed to set user_version: %w", err) + } + + err = tx.Commit() + if err != nil { + return fmt.Errorf("failed to commit transaction: %w", err) + } + + return nil +} + +func initialSetup(tx *sql.Tx) error { + // Create the initial user + _, err := tx.Exec(` + CREATE TABLE IF NOT EXISTS "versions" ( + "id" TEXT NOT NULL PRIMARY KEY, + "mod_reference" TEXT NOT NULL, + "version" TEXT NOT NULL, + "game_version" TEXT NOT NULL + ); + CREATE INDEX IF NOT EXISTS "mod_reference" ON "versions" ("mod_reference"); + CREATE UNIQUE INDEX IF NOT EXISTS "mod_version" ON "versions" ("mod_reference", "version"); + + CREATE TABLE IF NOT EXISTS "dependencies" ( + "version_id" TEXT NOT NULL, + "dependency" TEXT NOT NULL, + "condition" TEXT NOT NULL, + "optional" INT NOT NULL, + FOREIGN KEY ("version_id") REFERENCES "versions" ("id") ON DELETE CASCADE, + PRIMARY KEY ("version_id", "dependency") + ); + + CREATE TABLE IF NOT EXISTS "targets" ( + "version_id" TEXT NOT NULL, + "target_name" TEXT NOT NULL, + "link" TEXT NOT NULL, + "hash" TEXT NOT NULL, + "size" INT NOT NULL, + FOREIGN KEY ("version_id") REFERENCES "versions" ("id") ON DELETE CASCADE, + PRIMARY KEY ("version_id", "target_name") + ); + `) + + if err != nil { + return fmt.Errorf("failed to create initial tables: %w", err) + } + + return nil +} + +func addRequiredOnRemote(tx *sql.Tx) error { + _, err := tx.Exec(` + ALTER TABLE "versions" ADD COLUMN "required_on_remote" INT NOT NULL DEFAULT 1; + `) + + if err != nil { + return fmt.Errorf("failed to add required_on_remote column: %w", err) + } + + return nil +} diff --git a/cli/localregistry/registry.go b/cli/localregistry/registry.go index d170d43..e75aaf8 100644 --- a/cli/localregistry/registry.go +++ b/cli/localregistry/registry.go @@ -8,9 +8,10 @@ import ( "path/filepath" "sync" - resolver "github.com/satisfactorymodding/ficsit-resolver" "github.com/spf13/viper" + "github.com/satisfactorymodding/ficsit-cli/ficsit" + // sqlite driver _ "modernc.org/sqlite" ) @@ -36,43 +37,20 @@ func Init() error { PRAGMA journal_mode = WAL; PRAGMA foreign_keys = ON; PRAGMA busy_timeout = 5000; - - CREATE TABLE IF NOT EXISTS "versions" ( - "id" TEXT NOT NULL PRIMARY KEY, - "mod_reference" TEXT NOT NULL, - "version" TEXT NOT NULL, - "game_version" TEXT NOT NULL - ); - CREATE INDEX IF NOT EXISTS "mod_reference" ON "versions" ("mod_reference"); - CREATE UNIQUE INDEX IF NOT EXISTS "mod_version" ON "versions" ("mod_reference", "version"); - - CREATE TABLE IF NOT EXISTS "dependencies" ( - "version_id" TEXT NOT NULL, - "dependency" TEXT NOT NULL, - "condition" TEXT NOT NULL, - "optional" INT NOT NULL, - FOREIGN KEY ("version_id") REFERENCES "versions" ("id") ON DELETE CASCADE, - PRIMARY KEY ("version_id", "dependency") - ); - - CREATE TABLE IF NOT EXISTS "targets" ( - "version_id" TEXT NOT NULL, - "target_name" TEXT NOT NULL, - "link" TEXT NOT NULL, - "hash" TEXT NOT NULL, - "size" INT NOT NULL, - FOREIGN KEY ("version_id") REFERENCES "versions" ("id") ON DELETE CASCADE, - PRIMARY KEY ("version_id", "target_name") - ); `) if err != nil { - return fmt.Errorf("failed to setup tables: %w", err) + return fmt.Errorf("failed to setup connection pragmas: %w", err) + } + + err = applyMigrations(db) + if err != nil { + return fmt.Errorf("failed to apply migrations: %w", err) } return nil } -func Add(modReference string, modVersions []resolver.ModVersion) { +func Add(modReference string, modVersions []ficsit.ModVersion) { dbWriteMutex.Lock() defer dbWriteMutex.Unlock() @@ -93,7 +71,7 @@ func Add(modReference string, modVersions []resolver.ModVersion) { for _, modVersion := range modVersions { l := slog.With(slog.String("mod", modReference), slog.String("version", modVersion.Version)) - _, err = tx.Exec("INSERT INTO versions (id, mod_reference, version, game_version) VALUES (?, ?, ?, ?)", modVersion.ID, modReference, modVersion.Version, modVersion.GameVersion) + _, err = tx.Exec("INSERT INTO versions (id, mod_reference, version, game_version, required_on_remote) VALUES (?, ?, ?, ?, ?)", modVersion.ID, modReference, modVersion.Version, modVersion.GameVersion, modVersion.RequiredOnRemote) if err != nil { l.Error("failed to insert mod version into local registry", slog.Any("err", err)) return @@ -121,17 +99,17 @@ func Add(modReference string, modVersions []resolver.ModVersion) { } } -func GetModVersions(modReference string) ([]resolver.ModVersion, error) { - versionRows, err := db.Query("SELECT id, version, game_version FROM versions WHERE mod_reference = ?", modReference) +func GetModVersions(modReference string) ([]ficsit.ModVersion, error) { + versionRows, err := db.Query("SELECT id, version, game_version, required_on_remote FROM versions WHERE mod_reference = ?", modReference) if err != nil { return nil, fmt.Errorf("failed to fetch mod versions from local registry: %w", err) } defer versionRows.Close() - var versions []resolver.ModVersion + var versions []ficsit.ModVersion for versionRows.Next() { - var version resolver.ModVersion - err = versionRows.Scan(&version.ID, &version.Version, &version.GameVersion) + var version ficsit.ModVersion + err = versionRows.Scan(&version.ID, &version.Version, &version.GameVersion, &version.RequiredOnRemote) if err != nil { return nil, fmt.Errorf("failed to scan version row: %w", err) } @@ -156,8 +134,8 @@ func GetModVersions(modReference string) ([]resolver.ModVersion, error) { return versions, nil } -func getVersionDependencies(versionID string) ([]resolver.Dependency, error) { - var dependencies []resolver.Dependency +func getVersionDependencies(versionID string) ([]ficsit.Dependency, error) { + var dependencies []ficsit.Dependency dependencyRows, err := db.Query("SELECT dependency, condition, optional FROM dependencies WHERE version_id = ?", versionID) if err != nil { return nil, fmt.Errorf("failed to fetch dependencies from local registry: %w", err) @@ -165,7 +143,7 @@ func getVersionDependencies(versionID string) ([]resolver.Dependency, error) { defer dependencyRows.Close() for dependencyRows.Next() { - var dependency resolver.Dependency + var dependency ficsit.Dependency err = dependencyRows.Scan(&dependency.ModID, &dependency.Condition, &dependency.Optional) if err != nil { return nil, fmt.Errorf("failed to scan dependency row: %w", err) @@ -176,8 +154,8 @@ func getVersionDependencies(versionID string) ([]resolver.Dependency, error) { return dependencies, nil } -func getVersionTargets(versionID string) ([]resolver.Target, error) { - var targets []resolver.Target +func getVersionTargets(versionID string) ([]ficsit.Target, error) { + var targets []ficsit.Target targetRows, err := db.Query("SELECT target_name, link, hash, size FROM targets WHERE version_id = ?", versionID) if err != nil { return nil, fmt.Errorf("failed to fetch targets from local registry: %w", err) @@ -185,7 +163,7 @@ func getVersionTargets(versionID string) ([]resolver.Target, error) { defer targetRows.Close() for targetRows.Next() { - var target resolver.Target + var target ficsit.Target err = targetRows.Scan(&target.TargetName, &target.Link, &target.Hash, &target.Size) if err != nil { return nil, fmt.Errorf("failed to scan target row: %w", err) diff --git a/cli/provider/converter.go b/cli/provider/converter.go new file mode 100644 index 0000000..1e6dd39 --- /dev/null +++ b/cli/provider/converter.go @@ -0,0 +1,41 @@ +package provider + +import ( + resolver "github.com/satisfactorymodding/ficsit-resolver" + "github.com/spf13/viper" + + "github.com/satisfactorymodding/ficsit-cli/ficsit" +) + +func convertFicsitVersionsToResolver(versions []ficsit.ModVersion) []resolver.ModVersion { + modVersions := make([]resolver.ModVersion, len(versions)) + for i, modVersion := range versions { + dependencies := make([]resolver.Dependency, len(modVersion.Dependencies)) + for j, dependency := range modVersion.Dependencies { + dependencies[j] = resolver.Dependency{ + ModID: dependency.ModID, + Condition: dependency.Condition, + Optional: dependency.Optional, + } + } + + targets := make([]resolver.Target, len(modVersion.Targets)) + for j, target := range modVersion.Targets { + targets[j] = resolver.Target{ + TargetName: resolver.TargetName(target.TargetName), + Link: viper.GetString("api-base") + target.Link, + Hash: target.Hash, + Size: target.Size, + } + } + + modVersions[i] = resolver.ModVersion{ + Version: modVersion.Version, + GameVersion: modVersion.GameVersion, + Dependencies: dependencies, + Targets: targets, + RequiredOnRemote: modVersion.RequiredOnRemote, + } + } + return modVersions +} diff --git a/cli/provider/ficsit.go b/cli/provider/ficsit.go index 9c8a8cc..dcff05c 100644 --- a/cli/provider/ficsit.go +++ b/cli/provider/ficsit.go @@ -6,7 +6,6 @@ import ( "github.com/Khan/genqlient/graphql" resolver "github.com/satisfactorymodding/ficsit-resolver" - "github.com/spf13/viper" "github.com/satisfactorymodding/ficsit-cli/cli/localregistry" "github.com/satisfactorymodding/ficsit-cli/ficsit" @@ -40,39 +39,9 @@ func (p FicsitProvider) ModVersionsWithDependencies(_ context.Context, modID str return nil, errors.New(response.Error.Message) } - modVersions := make([]resolver.ModVersion, len(response.Data)) - for i, modVersion := range response.Data { - dependencies := make([]resolver.Dependency, len(modVersion.Dependencies)) - for j, dependency := range modVersion.Dependencies { - dependencies[j] = resolver.Dependency{ - ModID: dependency.ModID, - Condition: dependency.Condition, - Optional: dependency.Optional, - } - } + localregistry.Add(modID, response.Data) - targets := make([]resolver.Target, len(modVersion.Targets)) - for j, target := range modVersion.Targets { - targets[j] = resolver.Target{ - TargetName: resolver.TargetName(target.TargetName), - Link: viper.GetString("api-base") + target.Link, - Hash: target.Hash, - Size: target.Size, - } - } - - modVersions[i] = resolver.ModVersion{ - ID: modVersion.ID, - Version: modVersion.Version, - GameVersion: modVersion.GameVersion, - Dependencies: dependencies, - Targets: targets, - } - } - - localregistry.Add(modID, modVersions) - - return modVersions, err + return convertFicsitVersionsToResolver(response.Data), nil } func (p FicsitProvider) GetModName(context context.Context, modReference string) (*resolver.ModName, error) { diff --git a/cli/provider/local.go b/cli/provider/local.go index b5eabc9..e129251 100644 --- a/cli/provider/local.go +++ b/cli/provider/local.go @@ -127,7 +127,7 @@ func (p LocalProvider) ModVersionsWithDependencies(_ context.Context, modID stri // TODO: only list as available the versions that have at least one target cached - return modVersions, nil + return convertFicsitVersionsToResolver(modVersions), nil } func (p LocalProvider) GetModName(_ context.Context, modReference string) (*resolver.ModName, error) { diff --git a/cli/resolving_test.go b/cli/resolving_test.go index e7b17ea..25d4dd2 100644 --- a/cli/resolving_test.go +++ b/cli/resolving_test.go @@ -4,7 +4,9 @@ import ( "log/slog" "math" "os" + "path/filepath" "testing" + "time" "github.com/MarvinJWendt/testza" resolver "github.com/satisfactorymodding/ficsit-resolver" @@ -32,6 +34,108 @@ func installWatcher() chan<- InstallUpdate { return c } +func TestClientOnlyMod(t *testing.T) { + ctx, err := InitCLI(false) + testza.AssertNoError(t, err) + + err = ctx.Wipe() + testza.AssertNoError(t, err) + + ctx.Provider = MockProvider{} + + profileName := "ClientOnlyModTest" + profile, err := ctx.Profiles.AddProfile(profileName) + profile.RequiredTargets = []resolver.TargetName{resolver.TargetNameWindows, resolver.TargetNameWindowsServer, resolver.TargetNameLinuxServer} + testza.AssertNoError(t, err) + testza.AssertNoError(t, profile.AddMod("ClientOnlyMod", "<=0.0.1")) + + serverLocation := os.Getenv("SF_DEDICATED_SERVER") + if serverLocation != "" { + time.Sleep(time.Second) + testza.AssertNoError(t, os.RemoveAll(filepath.Join(serverLocation, "FactoryGame", "Mods"))) + time.Sleep(time.Second) + + installation, err := ctx.Installations.AddInstallation(ctx, serverLocation, profileName) + testza.AssertNoError(t, err) + testza.AssertNotNil(t, installation) + + err = installation.Install(ctx, installWatcher()) + testza.AssertNoError(t, err) + } +} + +func TestServerOnlyMod(t *testing.T) { + ctx, err := InitCLI(false) + testza.AssertNoError(t, err) + + err = ctx.Wipe() + testza.AssertNoError(t, err) + + ctx.Provider = MockProvider{} + + profileName := "ServerOnlyModTest" + profile, err := ctx.Profiles.AddProfile(profileName) + profile.RequiredTargets = []resolver.TargetName{resolver.TargetNameWindows, resolver.TargetNameWindowsServer, resolver.TargetNameLinuxServer} + testza.AssertNoError(t, err) + testza.AssertNoError(t, profile.AddMod("ServerOnlyMod", "<=0.0.1")) + + serverLocation := os.Getenv("SF_DEDICATED_SERVER") + if serverLocation != "" { + time.Sleep(time.Second) + testza.AssertNoError(t, os.RemoveAll(filepath.Join(serverLocation, "FactoryGame", "Mods"))) + time.Sleep(time.Second) + + installation, err := ctx.Installations.AddInstallation(ctx, serverLocation, profileName) + testza.AssertNoError(t, err) + testza.AssertNotNil(t, installation) + + err = installation.Install(ctx, installWatcher()) + testza.AssertNoError(t, err) + } +} + +func TestRemoveWhenNotSupported(t *testing.T) { + ctx, err := InitCLI(false) + testza.AssertNoError(t, err) + + err = ctx.Wipe() + testza.AssertNoError(t, err) + + ctx.Provider = MockProvider{} + + profileName := "ClientOnlyModTest" + profile, err := ctx.Profiles.AddProfile(profileName) + profile.RequiredTargets = []resolver.TargetName{resolver.TargetNameWindows, resolver.TargetNameWindowsServer, resolver.TargetNameLinuxServer} + testza.AssertNoError(t, err) + testza.AssertNoError(t, profile.AddMod("LaterClientOnlyMod", "0.0.1")) + + serverLocation := os.Getenv("SF_DEDICATED_SERVER") + if serverLocation != "" { + time.Sleep(time.Second) + testza.AssertNoError(t, os.RemoveAll(filepath.Join(serverLocation, "FactoryGame", "Mods"))) + time.Sleep(time.Second) + + installation, err := ctx.Installations.AddInstallation(ctx, serverLocation, profileName) + testza.AssertNoError(t, err) + testza.AssertNotNil(t, installation) + + err = installation.Install(ctx, installWatcher()) + testza.AssertNoError(t, err) + + _, err = os.Stat(filepath.Join(serverLocation, "FactoryGame", "Mods", "LaterClientOnlyMod")) + testza.AssertNoError(t, err) + + testza.AssertNoError(t, profile.AddMod("LaterClientOnlyMod", "0.0.2")) + + err = installation.Install(ctx, installWatcher()) + testza.AssertNoError(t, err) + + _, err = os.Stat(filepath.Join(serverLocation, "FactoryGame", "Mods", "LaterClientOnlyMod")) + testza.AssertNotNil(t, err) + testza.AssertErrorIs(t, err, os.ErrNotExist) + } +} + func TestUpdateMods(t *testing.T) { ctx, err := InitCLI(false) testza.AssertNoError(t, err) diff --git a/cli/test_helpers.go b/cli/test_helpers.go index 2a719c4..73ff9e7 100644 --- a/cli/test_helpers.go +++ b/cli/test_helpers.go @@ -70,19 +70,28 @@ func (m MockProvider) Mods(_ context.Context, f ficsit.ModFilter) (*ficsit.ModsR }, nil } +var windowsTarget = resolver.Target{ + TargetName: "Windows", + Link: "https://api.ficsit.dev/v1/version/7QcfNdo5QAAyoC/Windows/download", + Hash: "698df20278b3de3ec30405569a22050c6721cc682389312258c14948bd8f38ae", +} + +var windowsServerTarget = resolver.Target{ + TargetName: "WindowsServer", + Link: "https://api.ficsit.dev/v1/version/7QcfNdo5QAAyoC/WindowsServer/download", + Hash: "7be01ed372e0cf3287a04f5cb32bb9dcf6f6e7a5b7603b7e43669ec4c6c1457f", +} + +var linuxServerTarget = resolver.Target{ + TargetName: "LinuxServer", + Link: "https://api.ficsit.dev/v1/version/7QcfNdo5QAAyoC/LinuxServer/download", + Hash: "bdbd4cb1b472a5316621939ae2fe270fd0e3c0f0a75666a9cbe74ff1313c3663", +} + var commonTargets = []resolver.Target{ - { - TargetName: "Windows", - Hash: "698df20278b3de3ec30405569a22050c6721cc682389312258c14948bd8f38ae", - }, - { - TargetName: "WindowsServer", - Hash: "7be01ed372e0cf3287a04f5cb32bb9dcf6f6e7a5b7603b7e43669ec4c6c1457f", - }, - { - TargetName: "LinuxServer", - Hash: "bdbd4cb1b472a5316621939ae2fe270fd0e3c0f0a75666a9cbe74ff1313c3663", - }, + windowsTarget, + windowsServerTarget, + linuxServerTarget, } func (m MockProvider) ModVersionsWithDependencies(ctx context.Context, modID string) ([]resolver.ModVersion, error) { @@ -90,7 +99,6 @@ func (m MockProvider) ModVersionsWithDependencies(ctx context.Context, modID str case "AreaActions": return []resolver.ModVersion{ { - ID: "7QcfNdo5QAAyoC", Version: "1.6.7", Dependencies: []resolver.Dependency{ { @@ -99,10 +107,10 @@ func (m MockProvider) ModVersionsWithDependencies(ctx context.Context, modID str Optional: false, }, }, - Targets: commonTargets, + Targets: commonTargets, + RequiredOnRemote: true, }, { - ID: "7QcfNdo5QAAyoC", Version: "1.6.6", Dependencies: []resolver.Dependency{ { @@ -111,10 +119,10 @@ func (m MockProvider) ModVersionsWithDependencies(ctx context.Context, modID str Optional: false, }, }, - Targets: commonTargets, + Targets: commonTargets, + RequiredOnRemote: true, }, { - ID: "7QcfNdo5QAAyoC", Version: "1.6.5", Dependencies: []resolver.Dependency{ { @@ -123,13 +131,13 @@ func (m MockProvider) ModVersionsWithDependencies(ctx context.Context, modID str Optional: false, }, }, - Targets: commonTargets, + Targets: commonTargets, + RequiredOnRemote: true, }, }, nil case "FicsitRemoteMonitoring": return []resolver.ModVersion{ { - ID: "7QcfNdo5QAAyoC", Version: "0.10.1", Dependencies: []resolver.Dependency{ { @@ -138,10 +146,10 @@ func (m MockProvider) ModVersionsWithDependencies(ctx context.Context, modID str Optional: false, }, }, - Targets: commonTargets, + Targets: commonTargets, + RequiredOnRemote: true, }, { - ID: "7QcfNdo5QAAyoC", Version: "0.10.0", Dependencies: []resolver.Dependency{ { @@ -150,10 +158,10 @@ func (m MockProvider) ModVersionsWithDependencies(ctx context.Context, modID str Optional: false, }, }, - Targets: commonTargets, + Targets: commonTargets, + RequiredOnRemote: true, }, { - ID: "7QcfNdo5QAAyoC", Version: "0.9.8", Dependencies: []resolver.Dependency{ { @@ -162,7 +170,72 @@ func (m MockProvider) ModVersionsWithDependencies(ctx context.Context, modID str Optional: false, }, }, - Targets: commonTargets, + Targets: commonTargets, + RequiredOnRemote: true, + }, + }, nil + case "ClientOnlyMod": + return []resolver.ModVersion{ + { + Version: "0.0.1", + Dependencies: []resolver.Dependency{ + { + ModID: "SML", + Condition: "^3.6.0", + Optional: false, + }, + }, + Targets: []resolver.Target{ + windowsTarget, + }, + RequiredOnRemote: false, + }, + }, nil + case "ServerOnlyMod": + return []resolver.ModVersion{ + { + Version: "0.0.1", + Dependencies: []resolver.Dependency{ + { + ModID: "SML", + Condition: "^3.6.0", + Optional: false, + }, + }, + Targets: []resolver.Target{ + windowsServerTarget, + linuxServerTarget, + }, + RequiredOnRemote: false, + }, + }, nil + case "LaterClientOnlyMod": + return []resolver.ModVersion{ + { + Version: "0.0.1", + Dependencies: []resolver.Dependency{ + { + ModID: "SML", + Condition: "^3.6.0", + Optional: false, + }, + }, + Targets: commonTargets, + RequiredOnRemote: true, + }, + { + Version: "0.0.2", + Dependencies: []resolver.Dependency{ + { + ModID: "SML", + Condition: "^3.6.0", + Optional: false, + }, + }, + Targets: []resolver.Target{ + windowsTarget, + }, + RequiredOnRemote: false, }, }, nil } diff --git a/ficsit/types_rest.go b/ficsit/types_rest.go index 4e0f381..38b303e 100644 --- a/ficsit/types_rest.go +++ b/ficsit/types_rest.go @@ -7,11 +7,12 @@ type AllVersionsResponse struct { } type ModVersion struct { - ID string `json:"id"` - Version string `json:"version"` - GameVersion string `json:"game_version"` - Dependencies []Dependency `json:"dependencies"` - Targets []Target `json:"targets"` + ID string `json:"id"` + Version string `json:"version"` + GameVersion string `json:"game_version"` + Dependencies []Dependency `json:"dependencies"` + Targets []Target `json:"targets"` + RequiredOnRemote bool `json:"required_on_remote"` } type Dependency struct { diff --git a/go.mod b/go.mod index 8f7f4de..2143e87 100644 --- a/go.mod +++ b/go.mod @@ -26,7 +26,7 @@ require ( github.com/puzpuzpuz/xsync/v3 v3.0.2 github.com/sahilm/fuzzy v0.1.1-0.20230530133925-c48e322e2a8f github.com/samber/slog-multi v1.0.2 - github.com/satisfactorymodding/ficsit-resolver v0.0.3 + github.com/satisfactorymodding/ficsit-resolver v0.0.6 github.com/spf13/cobra v1.8.0 github.com/spf13/viper v1.18.1 goftp.io/server/v2 v2.0.1 diff --git a/go.sum b/go.sum index 384ba54..7163500 100644 --- a/go.sum +++ b/go.sum @@ -215,8 +215,8 @@ github.com/samber/lo v1.38.1 h1:j2XEAqXKb09Am4ebOg31SpvzUTTs6EN3VfgeLUhPdXM= github.com/samber/lo v1.38.1/go.mod h1:+m/ZKRl6ClXCE2Lgf3MsQlWfh4bn1bz6CXEOxnEXnEA= github.com/samber/slog-multi v1.0.2 h1:6BVH9uHGAsiGkbbtQgAOQJMpKgV8unMrHhhJaw+X1EQ= github.com/samber/slog-multi v1.0.2/go.mod h1:uLAvHpGqbYgX4FSL0p1ZwoLuveIAJvBECtE07XmYvFo= -github.com/satisfactorymodding/ficsit-resolver v0.0.3 h1:Q+BV1w1S42accsHbew9BmwcYdbAtSYfeVlQpRJiBhGg= -github.com/satisfactorymodding/ficsit-resolver v0.0.3/go.mod h1:ckKMmMvDoYbbkEbWXEsMes608uvv6EKphXPhHX8LKSc= +github.com/satisfactorymodding/ficsit-resolver v0.0.6 h1:4iCIHOg3z+AvwSVeWtu+k9aysLOL9+FIszCbiKOG2oo= +github.com/satisfactorymodding/ficsit-resolver v0.0.6/go.mod h1:ckKMmMvDoYbbkEbWXEsMes608uvv6EKphXPhHX8LKSc= github.com/sebdah/goldie/v2 v2.5.3 h1:9ES/mNN+HNUbNWpVAlrzuZ7jE+Nrczbj8uFRjM7624Y= github.com/sebdah/goldie/v2 v2.5.3/go.mod h1:oZ9fp0+se1eapSRjfYbsV/0Hqhbuu3bJVvKI/NNtssI= github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo=