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
This commit is contained in:
mircearoata 2024-10-04 19:23:51 +02:00 committed by GitHub
parent bf6d6b0850
commit 3640e5e708
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
11 changed files with 392 additions and 111 deletions

View file

@ -429,7 +429,13 @@ func (i *Installation) Install(ctx *GlobalContext, updates chan<- InstallUpdate)
var deleteWait errgroup.Group var deleteWait errgroup.Group
for _, entry := range dir { for _, entry := range dir {
if entry.IsDir() { 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() modName := entry.Name()
modDir := filepath.Join(modsDirectory, modName) modDir := filepath.Join(modsDirectory, modName)
deleteWait.Go(func() error { deleteWait.Go(func() error {
@ -493,7 +499,10 @@ func (i *Installation) Install(ctx *GlobalContext, updates chan<- InstallUpdate)
target, ok := version.Targets[platform.TargetName] target, ok := version.Targets[platform.TargetName]
if !ok { 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 // Only install if a link is provided, otherwise assume mod is already installed

View file

@ -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
}

View file

@ -8,9 +8,10 @@ import (
"path/filepath" "path/filepath"
"sync" "sync"
resolver "github.com/satisfactorymodding/ficsit-resolver"
"github.com/spf13/viper" "github.com/spf13/viper"
"github.com/satisfactorymodding/ficsit-cli/ficsit"
// sqlite driver // sqlite driver
_ "modernc.org/sqlite" _ "modernc.org/sqlite"
) )
@ -36,43 +37,20 @@ func Init() error {
PRAGMA journal_mode = WAL; PRAGMA journal_mode = WAL;
PRAGMA foreign_keys = ON; PRAGMA foreign_keys = ON;
PRAGMA busy_timeout = 5000; 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 { 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 return nil
} }
func Add(modReference string, modVersions []resolver.ModVersion) { func Add(modReference string, modVersions []ficsit.ModVersion) {
dbWriteMutex.Lock() dbWriteMutex.Lock()
defer dbWriteMutex.Unlock() defer dbWriteMutex.Unlock()
@ -93,7 +71,7 @@ func Add(modReference string, modVersions []resolver.ModVersion) {
for _, modVersion := range modVersions { for _, modVersion := range modVersions {
l := slog.With(slog.String("mod", modReference), slog.String("version", modVersion.Version)) 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 { if err != nil {
l.Error("failed to insert mod version into local registry", slog.Any("err", err)) l.Error("failed to insert mod version into local registry", slog.Any("err", err))
return return
@ -121,17 +99,17 @@ func Add(modReference string, modVersions []resolver.ModVersion) {
} }
} }
func GetModVersions(modReference string) ([]resolver.ModVersion, error) { func GetModVersions(modReference string) ([]ficsit.ModVersion, error) {
versionRows, err := db.Query("SELECT id, version, game_version FROM versions WHERE mod_reference = ?", modReference) versionRows, err := db.Query("SELECT id, version, game_version, required_on_remote FROM versions WHERE mod_reference = ?", modReference)
if err != nil { if err != nil {
return nil, fmt.Errorf("failed to fetch mod versions from local registry: %w", err) return nil, fmt.Errorf("failed to fetch mod versions from local registry: %w", err)
} }
defer versionRows.Close() defer versionRows.Close()
var versions []resolver.ModVersion var versions []ficsit.ModVersion
for versionRows.Next() { for versionRows.Next() {
var version resolver.ModVersion var version ficsit.ModVersion
err = versionRows.Scan(&version.ID, &version.Version, &version.GameVersion) err = versionRows.Scan(&version.ID, &version.Version, &version.GameVersion, &version.RequiredOnRemote)
if err != nil { if err != nil {
return nil, fmt.Errorf("failed to scan version row: %w", err) 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 return versions, nil
} }
func getVersionDependencies(versionID string) ([]resolver.Dependency, error) { func getVersionDependencies(versionID string) ([]ficsit.Dependency, error) {
var dependencies []resolver.Dependency var dependencies []ficsit.Dependency
dependencyRows, err := db.Query("SELECT dependency, condition, optional FROM dependencies WHERE version_id = ?", versionID) dependencyRows, err := db.Query("SELECT dependency, condition, optional FROM dependencies WHERE version_id = ?", versionID)
if err != nil { if err != nil {
return nil, fmt.Errorf("failed to fetch dependencies from local registry: %w", err) 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() defer dependencyRows.Close()
for dependencyRows.Next() { for dependencyRows.Next() {
var dependency resolver.Dependency var dependency ficsit.Dependency
err = dependencyRows.Scan(&dependency.ModID, &dependency.Condition, &dependency.Optional) err = dependencyRows.Scan(&dependency.ModID, &dependency.Condition, &dependency.Optional)
if err != nil { if err != nil {
return nil, fmt.Errorf("failed to scan dependency row: %w", err) 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 return dependencies, nil
} }
func getVersionTargets(versionID string) ([]resolver.Target, error) { func getVersionTargets(versionID string) ([]ficsit.Target, error) {
var targets []resolver.Target var targets []ficsit.Target
targetRows, err := db.Query("SELECT target_name, link, hash, size FROM targets WHERE version_id = ?", versionID) targetRows, err := db.Query("SELECT target_name, link, hash, size FROM targets WHERE version_id = ?", versionID)
if err != nil { if err != nil {
return nil, fmt.Errorf("failed to fetch targets from local registry: %w", err) 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() defer targetRows.Close()
for targetRows.Next() { for targetRows.Next() {
var target resolver.Target var target ficsit.Target
err = targetRows.Scan(&target.TargetName, &target.Link, &target.Hash, &target.Size) err = targetRows.Scan(&target.TargetName, &target.Link, &target.Hash, &target.Size)
if err != nil { if err != nil {
return nil, fmt.Errorf("failed to scan target row: %w", err) return nil, fmt.Errorf("failed to scan target row: %w", err)

41
cli/provider/converter.go Normal file
View file

@ -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
}

View file

@ -6,7 +6,6 @@ import (
"github.com/Khan/genqlient/graphql" "github.com/Khan/genqlient/graphql"
resolver "github.com/satisfactorymodding/ficsit-resolver" resolver "github.com/satisfactorymodding/ficsit-resolver"
"github.com/spf13/viper"
"github.com/satisfactorymodding/ficsit-cli/cli/localregistry" "github.com/satisfactorymodding/ficsit-cli/cli/localregistry"
"github.com/satisfactorymodding/ficsit-cli/ficsit" "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) return nil, errors.New(response.Error.Message)
} }
modVersions := make([]resolver.ModVersion, len(response.Data)) localregistry.Add(modID, 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,
}
}
targets := make([]resolver.Target, len(modVersion.Targets)) return convertFicsitVersionsToResolver(response.Data), nil
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
} }
func (p FicsitProvider) GetModName(context context.Context, modReference string) (*resolver.ModName, error) { func (p FicsitProvider) GetModName(context context.Context, modReference string) (*resolver.ModName, error) {

View file

@ -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 // 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) { func (p LocalProvider) GetModName(_ context.Context, modReference string) (*resolver.ModName, error) {

View file

@ -4,7 +4,9 @@ import (
"log/slog" "log/slog"
"math" "math"
"os" "os"
"path/filepath"
"testing" "testing"
"time"
"github.com/MarvinJWendt/testza" "github.com/MarvinJWendt/testza"
resolver "github.com/satisfactorymodding/ficsit-resolver" resolver "github.com/satisfactorymodding/ficsit-resolver"
@ -32,6 +34,108 @@ func installWatcher() chan<- InstallUpdate {
return c 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) { func TestUpdateMods(t *testing.T) {
ctx, err := InitCLI(false) ctx, err := InitCLI(false)
testza.AssertNoError(t, err) testza.AssertNoError(t, err)

View file

@ -70,19 +70,28 @@ func (m MockProvider) Mods(_ context.Context, f ficsit.ModFilter) (*ficsit.ModsR
}, nil }, nil
} }
var commonTargets = []resolver.Target{ var windowsTarget = resolver.Target{
{
TargetName: "Windows", TargetName: "Windows",
Link: "https://api.ficsit.dev/v1/version/7QcfNdo5QAAyoC/Windows/download",
Hash: "698df20278b3de3ec30405569a22050c6721cc682389312258c14948bd8f38ae", Hash: "698df20278b3de3ec30405569a22050c6721cc682389312258c14948bd8f38ae",
}, }
{
var windowsServerTarget = resolver.Target{
TargetName: "WindowsServer", TargetName: "WindowsServer",
Link: "https://api.ficsit.dev/v1/version/7QcfNdo5QAAyoC/WindowsServer/download",
Hash: "7be01ed372e0cf3287a04f5cb32bb9dcf6f6e7a5b7603b7e43669ec4c6c1457f", Hash: "7be01ed372e0cf3287a04f5cb32bb9dcf6f6e7a5b7603b7e43669ec4c6c1457f",
}, }
{
var linuxServerTarget = resolver.Target{
TargetName: "LinuxServer", TargetName: "LinuxServer",
Link: "https://api.ficsit.dev/v1/version/7QcfNdo5QAAyoC/LinuxServer/download",
Hash: "bdbd4cb1b472a5316621939ae2fe270fd0e3c0f0a75666a9cbe74ff1313c3663", Hash: "bdbd4cb1b472a5316621939ae2fe270fd0e3c0f0a75666a9cbe74ff1313c3663",
}, }
var commonTargets = []resolver.Target{
windowsTarget,
windowsServerTarget,
linuxServerTarget,
} }
func (m MockProvider) ModVersionsWithDependencies(ctx context.Context, modID string) ([]resolver.ModVersion, error) { 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": case "AreaActions":
return []resolver.ModVersion{ return []resolver.ModVersion{
{ {
ID: "7QcfNdo5QAAyoC",
Version: "1.6.7", Version: "1.6.7",
Dependencies: []resolver.Dependency{ Dependencies: []resolver.Dependency{
{ {
@ -100,9 +108,9 @@ func (m MockProvider) ModVersionsWithDependencies(ctx context.Context, modID str
}, },
}, },
Targets: commonTargets, Targets: commonTargets,
RequiredOnRemote: true,
}, },
{ {
ID: "7QcfNdo5QAAyoC",
Version: "1.6.6", Version: "1.6.6",
Dependencies: []resolver.Dependency{ Dependencies: []resolver.Dependency{
{ {
@ -112,9 +120,9 @@ func (m MockProvider) ModVersionsWithDependencies(ctx context.Context, modID str
}, },
}, },
Targets: commonTargets, Targets: commonTargets,
RequiredOnRemote: true,
}, },
{ {
ID: "7QcfNdo5QAAyoC",
Version: "1.6.5", Version: "1.6.5",
Dependencies: []resolver.Dependency{ Dependencies: []resolver.Dependency{
{ {
@ -124,12 +132,12 @@ func (m MockProvider) ModVersionsWithDependencies(ctx context.Context, modID str
}, },
}, },
Targets: commonTargets, Targets: commonTargets,
RequiredOnRemote: true,
}, },
}, nil }, nil
case "FicsitRemoteMonitoring": case "FicsitRemoteMonitoring":
return []resolver.ModVersion{ return []resolver.ModVersion{
{ {
ID: "7QcfNdo5QAAyoC",
Version: "0.10.1", Version: "0.10.1",
Dependencies: []resolver.Dependency{ Dependencies: []resolver.Dependency{
{ {
@ -139,9 +147,9 @@ func (m MockProvider) ModVersionsWithDependencies(ctx context.Context, modID str
}, },
}, },
Targets: commonTargets, Targets: commonTargets,
RequiredOnRemote: true,
}, },
{ {
ID: "7QcfNdo5QAAyoC",
Version: "0.10.0", Version: "0.10.0",
Dependencies: []resolver.Dependency{ Dependencies: []resolver.Dependency{
{ {
@ -151,9 +159,9 @@ func (m MockProvider) ModVersionsWithDependencies(ctx context.Context, modID str
}, },
}, },
Targets: commonTargets, Targets: commonTargets,
RequiredOnRemote: true,
}, },
{ {
ID: "7QcfNdo5QAAyoC",
Version: "0.9.8", Version: "0.9.8",
Dependencies: []resolver.Dependency{ Dependencies: []resolver.Dependency{
{ {
@ -163,6 +171,71 @@ func (m MockProvider) ModVersionsWithDependencies(ctx context.Context, modID str
}, },
}, },
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 }, nil
} }

View file

@ -12,6 +12,7 @@ type ModVersion struct {
GameVersion string `json:"game_version"` GameVersion string `json:"game_version"`
Dependencies []Dependency `json:"dependencies"` Dependencies []Dependency `json:"dependencies"`
Targets []Target `json:"targets"` Targets []Target `json:"targets"`
RequiredOnRemote bool `json:"required_on_remote"`
} }
type Dependency struct { type Dependency struct {

2
go.mod
View file

@ -26,7 +26,7 @@ require (
github.com/puzpuzpuz/xsync/v3 v3.0.2 github.com/puzpuzpuz/xsync/v3 v3.0.2
github.com/sahilm/fuzzy v0.1.1-0.20230530133925-c48e322e2a8f github.com/sahilm/fuzzy v0.1.1-0.20230530133925-c48e322e2a8f
github.com/samber/slog-multi v1.0.2 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/cobra v1.8.0
github.com/spf13/viper v1.18.1 github.com/spf13/viper v1.18.1
goftp.io/server/v2 v2.0.1 goftp.io/server/v2 v2.0.1

4
go.sum
View file

@ -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/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 h1:6BVH9uHGAsiGkbbtQgAOQJMpKgV8unMrHhhJaw+X1EQ=
github.com/samber/slog-multi v1.0.2/go.mod h1:uLAvHpGqbYgX4FSL0p1ZwoLuveIAJvBECtE07XmYvFo= 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.6 h1:4iCIHOg3z+AvwSVeWtu+k9aysLOL9+FIszCbiKOG2oo=
github.com/satisfactorymodding/ficsit-resolver v0.0.3/go.mod h1:ckKMmMvDoYbbkEbWXEsMes608uvv6EKphXPhHX8LKSc= 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 h1:9ES/mNN+HNUbNWpVAlrzuZ7jE+Nrczbj8uFRjM7624Y=
github.com/sebdah/goldie/v2 v2.5.3/go.mod h1:oZ9fp0+se1eapSRjfYbsV/0Hqhbuu3bJVvKI/NNtssI= 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= github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo=