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:
parent
bf6d6b0850
commit
3640e5e708
11 changed files with 392 additions and 111 deletions
|
@ -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
|
||||||
|
|
106
cli/localregistry/migrations.go
Normal file
106
cli/localregistry/migrations.go
Normal 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
|
||||||
|
}
|
|
@ -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
41
cli/provider/converter.go
Normal 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
|
||||||
|
}
|
|
@ -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) {
|
||||||
|
|
|
@ -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) {
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -70,19 +70,28 @@ func (m MockProvider) Mods(_ context.Context, f ficsit.ModFilter) (*ficsit.ModsR
|
||||||
}, nil
|
}, 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{
|
var commonTargets = []resolver.Target{
|
||||||
{
|
windowsTarget,
|
||||||
TargetName: "Windows",
|
windowsServerTarget,
|
||||||
Hash: "698df20278b3de3ec30405569a22050c6721cc682389312258c14948bd8f38ae",
|
linuxServerTarget,
|
||||||
},
|
|
||||||
{
|
|
||||||
TargetName: "WindowsServer",
|
|
||||||
Hash: "7be01ed372e0cf3287a04f5cb32bb9dcf6f6e7a5b7603b7e43669ec4c6c1457f",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
TargetName: "LinuxServer",
|
|
||||||
Hash: "bdbd4cb1b472a5316621939ae2fe270fd0e3c0f0a75666a9cbe74ff1313c3663",
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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{
|
||||||
{
|
{
|
||||||
|
@ -99,10 +107,10 @@ func (m MockProvider) ModVersionsWithDependencies(ctx context.Context, modID str
|
||||||
Optional: false,
|
Optional: false,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Targets: commonTargets,
|
Targets: commonTargets,
|
||||||
|
RequiredOnRemote: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
ID: "7QcfNdo5QAAyoC",
|
|
||||||
Version: "1.6.6",
|
Version: "1.6.6",
|
||||||
Dependencies: []resolver.Dependency{
|
Dependencies: []resolver.Dependency{
|
||||||
{
|
{
|
||||||
|
@ -111,10 +119,10 @@ func (m MockProvider) ModVersionsWithDependencies(ctx context.Context, modID str
|
||||||
Optional: false,
|
Optional: false,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Targets: commonTargets,
|
Targets: commonTargets,
|
||||||
|
RequiredOnRemote: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
ID: "7QcfNdo5QAAyoC",
|
|
||||||
Version: "1.6.5",
|
Version: "1.6.5",
|
||||||
Dependencies: []resolver.Dependency{
|
Dependencies: []resolver.Dependency{
|
||||||
{
|
{
|
||||||
|
@ -123,13 +131,13 @@ func (m MockProvider) ModVersionsWithDependencies(ctx context.Context, modID str
|
||||||
Optional: false,
|
Optional: false,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
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{
|
||||||
{
|
{
|
||||||
|
@ -138,10 +146,10 @@ func (m MockProvider) ModVersionsWithDependencies(ctx context.Context, modID str
|
||||||
Optional: false,
|
Optional: false,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Targets: commonTargets,
|
Targets: commonTargets,
|
||||||
|
RequiredOnRemote: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
ID: "7QcfNdo5QAAyoC",
|
|
||||||
Version: "0.10.0",
|
Version: "0.10.0",
|
||||||
Dependencies: []resolver.Dependency{
|
Dependencies: []resolver.Dependency{
|
||||||
{
|
{
|
||||||
|
@ -150,10 +158,10 @@ func (m MockProvider) ModVersionsWithDependencies(ctx context.Context, modID str
|
||||||
Optional: false,
|
Optional: false,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Targets: commonTargets,
|
Targets: commonTargets,
|
||||||
|
RequiredOnRemote: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
ID: "7QcfNdo5QAAyoC",
|
|
||||||
Version: "0.9.8",
|
Version: "0.9.8",
|
||||||
Dependencies: []resolver.Dependency{
|
Dependencies: []resolver.Dependency{
|
||||||
{
|
{
|
||||||
|
@ -162,7 +170,72 @@ func (m MockProvider) ModVersionsWithDependencies(ctx context.Context, modID str
|
||||||
Optional: false,
|
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
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,11 +7,12 @@ type AllVersionsResponse struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
type ModVersion struct {
|
type ModVersion struct {
|
||||||
ID string `json:"id"`
|
ID string `json:"id"`
|
||||||
Version string `json:"version"`
|
Version string `json:"version"`
|
||||||
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
2
go.mod
|
@ -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
4
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/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=
|
||||||
|
|
Loading…
Reference in a new issue