Compare commits

...

10 commits

Author SHA1 Message Date
9045893b1d chore: added it as an injectable flake
Some checks failed
push / Build (push) Has been cancelled
push / Lint (push) Has been cancelled
push / Test (push) Has been cancelled
2024-10-21 22:42:38 -05:00
mircearoata
5dc8bdbaf6 build: drop windows_arm_7 support 2024-10-10 22:48:26 +02:00
mircearoata
a4d8bea800 ci: don't use goreleaser --debug 2024-10-10 22:26:06 +02:00
mircearoata
9fc258edf6 ci: use goreleaser --clean 2024-10-10 22:20:01 +02:00
mircearoata
3640e5e708
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
2024-10-04 19:23:51 +02:00
mircearoata
bf6d6b0850
feat: store API responses in a local registry for offline mode (#68) 2024-10-02 10:59:56 +02:00
Rob B
7cd93926c6
fix: add FactoryGameSteam and FactoryGameEGS rootExecutable and platform (#69)
* fix: add `FactoryGameSteam` rootExecutable and platform

* fix: add `FactoryGameEGS` rootExecutable and platform
2024-09-10 16:26:28 -07:00
Rob B
8acb690014
feat: docs update, darwin dirs (#66)
* chore: update linter and spellchecker for readme

* docs: add basic directions for managing installations

* chore: replace old repo links

* feat: dirs for darwin support

---------

Co-authored-by: Vilsol <me@vil.so>
2024-08-14 03:36:41 +03:00
mircearoata
d744884f25
feat: remove SML references (#64)
* feat: update ficsit-resolver

* feat: remove SML references

* feat: read GameVersion from uplugin

* ci: docker-compose => docker compose
2024-08-14 03:27:07 +03:00
mircearoata
d051b5800a
perf(ftp): reduce number of ftp commands run in install preparation (#63)
* perf(ftp): use MLST or LIST first to determine if path exists over ftp

* perf(ftp): optimistically check directories from target path up when creating directory

* fix(ftp): skip . and .. in ReadDir

* perf(remote): parallelize old mod removal

* perf(remote): parallelize install validation

* perf(remote): remove unnecessary validation in GetGameVersion

* pref(remote): reduce amount of Validate and GetPlatform calls

* chore: remove unnecessary error handling
2024-05-05 23:01:17 +02:00
60 changed files with 1499 additions and 1062 deletions

28
.editorconfig Normal file
View file

@ -0,0 +1,28 @@
root = true
["*"]
charset = "utf-8"
curly_bracket_next_line = true
end_of_line = "lf"
indent_brace_style = "K&R"
indent_size = 4
indent_style = "space"
insert_final_newline = true
max_line_length = 80
tab_width = 4
trim_trailing_whitespace = true
["*.md"]
max_line_length = "off"
["package.json"]
indent_size = 2
indent_style = "space"
tab_width = 2
["{LICENSES/**,LICENSE}"]
charset = "unset"
end_of_line = "unset"
indent_size = "unset"
indent_style = "unset"
insert_final_newline = "unset"
trim_trailing_whitespace = "unset"

View file

@ -96,7 +96,7 @@ jobs:
- name: Boot ftp and sftp - name: Boot ftp and sftp
if: ${{ matrix.os == 'ubuntu-latest' }} if: ${{ matrix.os == 'ubuntu-latest' }}
run: docker-compose -f docker-compose-test.yml up -d run: docker compose -f docker-compose-test.yml up -d
- name: Download GQL schema - name: Download GQL schema
run: "npx graphqurl https://api.ficsit.dev/v2/query --introspect -H 'content-type: application/json' > schema.graphql" run: "npx graphqurl https://api.ficsit.dev/v2/query --introspect -H 'content-type: application/json' > schema.graphql"

View file

@ -42,7 +42,7 @@ jobs:
uses: goreleaser/goreleaser-action@v2 uses: goreleaser/goreleaser-action@v2
with: with:
version: latest version: latest
args: release --rm-dist --debug args: release --clean
env: env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
AUR_KEY: ${{ secrets.AUR_KEY }} AUR_KEY: ${{ secrets.AUR_KEY }}

9
.gitignore vendored
View file

@ -129,4 +129,11 @@ dist/
schema.graphql schema.graphql
*.log *.log
.direnv .direnv
/SatisfactoryDedicatedServer /SatisfactoryDedicatedServer
# nixago: ignore-linked-files
/treefmt.toml
/.prettierrc.json
/lefthook.yml
/.conform.yaml
# mfgames-project-setup: ignore-files
/.direnv/

View file

@ -29,11 +29,8 @@ builds:
- windows - windows
goarch: goarch:
- amd64 - amd64
- arm
- arm64 - arm64
- 386 - 386
goarm:
- 7
universal_binaries: universal_binaries:
- replace: true - replace: true

View file

@ -1,8 +1,10 @@
<img align="right" width="310" src="./.github/screenshot.png" /> <!-- markdownlint-disable MD033 -->
<!-- markdownlint-disable MD041 -->
<img align="right" width="310" src="./.github/screenshot.png" alt="ficsit-cli screenshot" />
# ficsit-cli [![push](https://github.com/Vilsol/ficsit-cli/actions/workflows/push.yaml/badge.svg)](https://github.com/Vilsol/ficsit-cli/actions/workflows/push.yaml) ![GitHub go.mod Go version](https://img.shields.io/github/go-mod/go-version/vilsol/ficsit-cli) ![GitHub tag (latest by date)](https://img.shields.io/github/v/tag/vilsol/ficsit-cli) [![GitHub license](https://img.shields.io/github/license/Vilsol/ficsit-cli)](https://github.com/Vilsol/ficsit-cli/blob/master/LICENSE) ![GitHub all releases](https://img.shields.io/github/downloads/vilsol/ficsit-cli/total) # ficsit-cli [![push](https://github.com/satisfactorymodding/ficsit-cli/actions/workflows/push.yaml/badge.svg)](https://github.com/satisfactorymodding/ficsit-cli/actions/workflows/push.yaml) ![GitHub go.mod Go version](https://img.shields.io/github/go-mod/go-version/satisfactorymodding/ficsit-cli) ![GitHub tag (latest by date)](https://img.shields.io/github/v/tag/satisfactorymodding/ficsit-cli) [![GitHub license](https://img.shields.io/github/license/satisfactorymodding/ficsit-cli)](https://github.com/satisfactorymodding/ficsit-cli/blob/master/LICENSE) ![GitHub all releases](https://img.shields.io/github/downloads/satisfactorymodding/ficsit-cli/total)
A CLI tool for managing mods for the game Satisfactory A CLI tool for managing mods for the game [Satisfactory](https://www.satisfactorygame.com/).
--- ---
@ -19,10 +21,10 @@ A CLI tool for managing mods for the game Satisfactory
</tr> </tr>
<tr> <tr>
<th>Windows</th> <th>Windows</th>
<td><a href="https://github.com/Vilsol/ficsit-cli/releases/latest/download/ficsit_windows_amd64.exe">amd64</a></td> <td><a href="https://github.com/satisfactorymodding/ficsit-cli/releases/latest/download/ficsit_windows_amd64.exe">amd64</a></td>
<td><a href="https://github.com/Vilsol/ficsit-cli/releases/latest/download/ficsit_windows_386.exe">386</a></td> <td><a href="https://github.com/satisfactorymodding/ficsit-cli/releases/latest/download/ficsit_windows_386.exe">386</a></td>
<td><a href="https://github.com/Vilsol/ficsit-cli/releases/latest/download/ficsit_windows_arm64.exe">arm64</a></td> <td><a href="https://github.com/satisfactorymodding/ficsit-cli/releases/latest/download/ficsit_windows_arm64.exe">arm64</a></td>
<td><a href="https://github.com/Vilsol/ficsit-cli/releases/latest/download/ficsit_windows_armv7.exe">armv7</a></td> <td>N/A</td>
<td>N/A</td> <td>N/A</td>
</tr> </tr>
<tr> <tr>
@ -31,39 +33,39 @@ A CLI tool for managing mods for the game Satisfactory
</tr> </tr>
<tr> <tr>
<th>Debian</th> <th>Debian</th>
<td><a href="https://github.com/Vilsol/ficsit-cli/releases/latest/download/ficsit_linux_amd64.deb">amd64</a></td> <td><a href="https://github.com/satisfactorymodding/ficsit-cli/releases/latest/download/ficsit_linux_amd64.deb">amd64</a></td>
<td><a href="https://github.com/Vilsol/ficsit-cli/releases/latest/download/ficsit_linux_386.deb">386</a></td> <td><a href="https://github.com/satisfactorymodding/ficsit-cli/releases/latest/download/ficsit_linux_386.deb">386</a></td>
<td><a href="https://github.com/Vilsol/ficsit-cli/releases/latest/download/ficsit_linux_arm64.deb">arm64</a></td> <td><a href="https://github.com/satisfactorymodding/ficsit-cli/releases/latest/download/ficsit_linux_arm64.deb">arm64</a></td>
<td><a href="https://github.com/Vilsol/ficsit-cli/releases/latest/download/ficsit_linux_armv7.deb">armv7</a></td> <td><a href="https://github.com/satisfactorymodding/ficsit-cli/releases/latest/download/ficsit_linux_armv7.deb">armv7</a></td>
<td><a href="https://github.com/Vilsol/ficsit-cli/releases/latest/download/ficsit_linux_ppc64le.deb">ppc64le</a></td> <td><a href="https://github.com/satisfactorymodding/ficsit-cli/releases/latest/download/ficsit_linux_ppc64le.deb">ppc64le</a></td>
</tr> </tr>
<tr> <tr>
<th>Fedora</th> <th>Fedora</th>
<td><a href="https://github.com/Vilsol/ficsit-cli/releases/latest/download/ficsit_linux_amd64.rpm">amd64</a></td> <td><a href="https://github.com/satisfactorymodding/ficsit-cli/releases/latest/download/ficsit_linux_amd64.rpm">amd64</a></td>
<td><a href="https://github.com/Vilsol/ficsit-cli/releases/latest/download/ficsit_linux_386.rpm">386</a></td> <td><a href="https://github.com/satisfactorymodding/ficsit-cli/releases/latest/download/ficsit_linux_386.rpm">386</a></td>
<td><a href="https://github.com/Vilsol/ficsit-cli/releases/latest/download/ficsit_linux_arm64.rpm">arm64</a></td> <td><a href="https://github.com/satisfactorymodding/ficsit-cli/releases/latest/download/ficsit_linux_arm64.rpm">arm64</a></td>
<td><a href="https://github.com/Vilsol/ficsit-cli/releases/latest/download/ficsit_linux_armv7.rpm">armv7</a></td> <td><a href="https://github.com/satisfactorymodding/ficsit-cli/releases/latest/download/ficsit_linux_armv7.rpm">armv7</a></td>
<td><a href="https://github.com/Vilsol/ficsit-cli/releases/latest/download/ficsit_linux_ppc64le.rpm">ppc64le</a></td> <td><a href="https://github.com/satisfactorymodding/ficsit-cli/releases/latest/download/ficsit_linux_ppc64le.rpm">ppc64le</a></td>
</tr> </tr>
<tr> <tr>
<th>Alpine</th> <th>Alpine</th>
<td><a href="https://github.com/Vilsol/ficsit-cli/releases/latest/download/ficsit_linux_amd64.apk">amd64</a></td> <td><a href="https://github.com/satisfactorymodding/ficsit-cli/releases/latest/download/ficsit_linux_amd64.apk">amd64</a></td>
<td><a href="https://github.com/Vilsol/ficsit-cli/releases/latest/download/ficsit_linux_386.apk">386</a></td> <td><a href="https://github.com/satisfactorymodding/ficsit-cli/releases/latest/download/ficsit_linux_386.apk">386</a></td>
<td><a href="https://github.com/Vilsol/ficsit-cli/releases/latest/download/ficsit_linux_arm64.apk">arm64</a></td> <td><a href="https://github.com/satisfactorymodding/ficsit-cli/releases/latest/download/ficsit_linux_arm64.apk">arm64</a></td>
<td><a href="https://github.com/Vilsol/ficsit-cli/releases/latest/download/ficsit_linux_armv7.apk">armv7</a></td> <td><a href="https://github.com/satisfactorymodding/ficsit-cli/releases/latest/download/ficsit_linux_armv7.apk">armv7</a></td>
<td><a href="https://github.com/Vilsol/ficsit-cli/releases/latest/download/ficsit_linux_ppc64le.apk">ppc64le</a></td> <td><a href="https://github.com/satisfactorymodding/ficsit-cli/releases/latest/download/ficsit_linux_ppc64le.apk">ppc64le</a></td>
</tr> </tr>
<tr> <tr>
<th>Linux</th> <th>Linux</th>
<td><a href="https://github.com/Vilsol/ficsit-cli/releases/latest/download/ficsit_linux_amd64">amd64</a></td> <td><a href="https://github.com/satisfactorymodding/ficsit-cli/releases/latest/download/ficsit_linux_amd64">amd64</a></td>
<td><a href="https://github.com/Vilsol/ficsit-cli/releases/latest/download/ficsit_linux_386">386</a></td> <td><a href="https://github.com/satisfactorymodding/ficsit-cli/releases/latest/download/ficsit_linux_386">386</a></td>
<td><a href="https://github.com/Vilsol/ficsit-cli/releases/latest/download/ficsit_linux_arm64">arm64</a></td> <td><a href="https://github.com/satisfactorymodding/ficsit-cli/releases/latest/download/ficsit_linux_arm64">arm64</a></td>
<td><a href="https://github.com/Vilsol/ficsit-cli/releases/latest/download/ficsit_linux_armv7">armv7</a></td> <td><a href="https://github.com/satisfactorymodding/ficsit-cli/releases/latest/download/ficsit_linux_armv7">armv7</a></td>
<td><a href="https://github.com/Vilsol/ficsit-cli/releases/latest/download/ficsit_linux_ppc64le">ppc64le</a></td> <td><a href="https://github.com/satisfactorymodding/ficsit-cli/releases/latest/download/ficsit_linux_ppc64le">ppc64le</a></td>
</tr> </tr>
<tr> <tr>
<th>macOS</th> <th>macOS</th>
<td colspan="4" style="text-align: center"><a href="https://github.com/Vilsol/ficsit-cli/releases/latest/download/ficsit_darwin_all">darwin_all</a></td> <td colspan="4" style="text-align: center"><a href="https://github.com/satisfactorymodding/ficsit-cli/releases/latest/download/ficsit_darwin_all">darwin_all</a></td>
<td>N/A</td> <td>N/A</td>
</tr> </tr>
</table> </table>
@ -74,21 +76,38 @@ A CLI tool for managing mods for the game Satisfactory
To launch the interactive CLI, run the executable without any arguments. To launch the interactive CLI, run the executable without any arguments.
All screens display control hints at the bottom.
### Command Line ### Command Line
Run `ficsit help` to see a list of available commands. Run `ficsit help` to see a list of available commands and flags.
## Managing Installations
Unlike [Satisfactory Mod Manager](https://github.com/satisfactorymodding/SatisfactoryModManager/),
ficsit-cli does not automatically detect installations.
First, locate your game install path.
Check the [Modding FAQ](https://docs.ficsit.app/satisfactory-modding/latest/faq.html#Files_GameInstall)
to learn how to find it given your specific install situation.
To add installations in the interactive CLI, use `Installations` > `new installation`.
To add installations from the command line, use `ficsit-cli installation add yourPathHere`.
## Troubleshooting ## Troubleshooting
* Profile and installation records are located in `%APPDATA%\ficsit\` - Profile and installation records are located in `%APPDATA%\ficsit\`
* Downloads are cached in `%LOCALAPPDATA%\ficsit\downloadCache\` - Downloads are cached in `%LOCALAPPDATA%\ficsit\downloadCache\`
Get help on the [modding Discord](https://discord.ficsit.app/).
## Development ## Development
### Dependencies ### Dependencies
* [Go 1.21](https://go.dev/doc/install) - [Go 1.21](https://go.dev/doc/install)
* IDE of Choice. Goland or VSCode suggested. - IDE of Choice. Goland or VSCode suggested.
### Code Generation ### Code Generation

118
cli/cache/integrity.go vendored
View file

@ -1,118 +0,0 @@
package cache
import (
"encoding/json"
"fmt"
"io"
"log/slog"
"os"
"path/filepath"
"time"
"github.com/puzpuzpuz/xsync/v3"
"github.com/spf13/viper"
"github.com/satisfactorymodding/ficsit-cli/utils"
)
type hashInfo struct {
Modified time.Time
Hash string
Size int64
}
var hashCache *xsync.MapOf[string, hashInfo]
var integrityFilename = ".integrity"
func getFileHash(file string) (string, error) {
if hashCache == nil {
loadHashCache()
}
cachedHash, ok := hashCache.Load(file)
if !ok {
return cacheFileHash(file)
}
downloadCache := filepath.Join(viper.GetString("cache-dir"), "downloadCache")
stat, err := os.Stat(filepath.Join(downloadCache, file))
if err != nil {
return "", fmt.Errorf("failed to stat file: %w", err)
}
if stat.Size() != cachedHash.Size || stat.ModTime() != cachedHash.Modified {
return cacheFileHash(file)
}
return cachedHash.Hash, nil
}
func cacheFileHash(file string) (string, error) {
downloadCache := filepath.Join(viper.GetString("cache-dir"), "downloadCache")
stat, err := os.Stat(filepath.Join(downloadCache, file))
if err != nil {
return "", fmt.Errorf("failed to stat file: %w", err)
}
f, err := os.Open(filepath.Join(downloadCache, file))
if err != nil {
return "", fmt.Errorf("failed to open file: %w", err)
}
defer f.Close()
hash, err := utils.SHA256Data(f)
if err != nil {
return "", fmt.Errorf("failed to hash file: %w", err)
}
hashCache.Store(file, hashInfo{
Hash: hash,
Size: stat.Size(),
Modified: stat.ModTime(),
})
saveHashCache()
return hash, nil
}
func loadHashCache() {
hashCache = xsync.NewMapOf[string, hashInfo]()
cacheFile := filepath.Join(viper.GetString("cache-dir"), "downloadCache", integrityFilename)
if _, err := os.Stat(cacheFile); os.IsNotExist(err) {
return
}
f, err := os.Open(cacheFile)
if err != nil {
slog.Warn("failed to open hash cache, recreating", slog.Any("err", err))
return
}
defer f.Close()
hashCacheJSON, err := io.ReadAll(f)
if err != nil {
slog.Warn("failed to read hash cache, recreating", slog.Any("err", err))
return
}
var plainCache map[string]hashInfo
if err := json.Unmarshal(hashCacheJSON, &plainCache); err != nil {
slog.Warn("failed to unmarshal hash cache, recreating", slog.Any("err", err))
return
}
for k, v := range plainCache {
hashCache.Store(k, v)
}
}
func saveHashCache() {
cacheFile := filepath.Join(viper.GetString("cache-dir"), "downloadCache", integrityFilename)
plainCache := make(map[string]hashInfo, hashCache.Size())
hashCache.Range(func(k string, v hashInfo) bool {
plainCache[k] = v
return true
})
hashCacheJSON, err := json.Marshal(plainCache)
if err != nil {
slog.Warn("failed to marshal hash cache", slog.Any("err", err))
return
}
if err := os.WriteFile(cacheFile, hashCacheJSON, 0o755); err != nil {
slog.Warn("failed to write hash cache", slog.Any("err", err))
return
}
}

View file

@ -12,43 +12,44 @@ import (
"path/filepath" "path/filepath"
"strings" "strings"
"github.com/mircearoata/pubgrub-go/pubgrub/semver"
"github.com/puzpuzpuz/xsync/v3" "github.com/puzpuzpuz/xsync/v3"
"github.com/spf13/viper" "github.com/spf13/viper"
) )
const IconFilename = "Resources/Icon128.png" // This is the path UE expects for the icon const IconFilename = "Resources/Icon128.png" // This is the path UE expects for the icon
type File struct { type Mod struct {
Icon *string ModReference string
ModReference string Name string
Hash string Author string
Plugin UPlugin Icon *string
Size int64 LatestVersion string
} }
var loadedCache *xsync.MapOf[string, []File] var loadedMods *xsync.MapOf[string, Mod]
func GetCache() (*xsync.MapOf[string, []File], error) { func GetCacheMods() (*xsync.MapOf[string, Mod], error) {
if loadedCache != nil { if loadedMods != nil {
return loadedCache, nil return loadedMods, nil
} }
return LoadCache() return LoadCacheMods()
} }
func GetCacheMod(mod string) ([]File, error) { func GetCacheMod(mod string) (Mod, error) {
cache, err := GetCache() cache, err := GetCacheMods()
if err != nil { if err != nil {
return nil, err return Mod{}, err
} }
value, _ := cache.Load(mod) value, _ := cache.Load(mod)
return value, nil return value, nil
} }
func LoadCache() (*xsync.MapOf[string, []File], error) { func LoadCacheMods() (*xsync.MapOf[string, Mod], error) {
loadedCache = xsync.NewMapOf[string, []File]() loadedMods = xsync.NewMapOf[string, Mod]()
downloadCache := filepath.Join(viper.GetString("cache-dir"), "downloadCache") downloadCache := filepath.Join(viper.GetString("cache-dir"), "downloadCache")
if _, err := os.Stat(downloadCache); os.IsNotExist(err) { if _, err := os.Stat(downloadCache); os.IsNotExist(err) {
return loadedCache, nil return loadedMods, nil
} }
items, err := os.ReadDir(downloadCache) items, err := os.ReadDir(downloadCache)
@ -60,32 +61,45 @@ func LoadCache() (*xsync.MapOf[string, []File], error) {
if item.IsDir() { if item.IsDir() {
continue continue
} }
if item.Name() == integrityFilename {
continue
}
_, err = addFileToCache(item.Name()) _, err = addFileToCache(item.Name())
if err != nil { if err != nil {
slog.Error("failed to add file to cache", slog.String("file", item.Name()), slog.Any("err", err)) slog.Error("failed to add file to cache", slog.String("file", item.Name()), slog.Any("err", err))
} }
} }
return loadedCache, nil return loadedMods, nil
} }
func addFileToCache(filename string) (*File, error) { func addFileToCache(filename string) (*Mod, error) {
cacheFile, err := readCacheFile(filename) cacheFile, err := readCacheFile(filename)
if err != nil { if err != nil {
return nil, fmt.Errorf("failed to read cache file: %w", err) return nil, fmt.Errorf("failed to read cache file: %w", err)
} }
loadedCache.Compute(cacheFile.ModReference, func(oldValue []File, _ bool) ([]File, bool) { loadedMods.Compute(cacheFile.ModReference, func(oldValue Mod, loaded bool) (Mod, bool) {
return append(oldValue, *cacheFile), false if !loaded {
return *cacheFile, false
}
oldVersion, err := semver.NewVersion(oldValue.LatestVersion)
if err != nil {
slog.Error("failed to parse version", slog.String("version", oldValue.LatestVersion), slog.Any("err", err))
return *cacheFile, false
}
newVersion, err := semver.NewVersion(cacheFile.LatestVersion)
if err != nil {
slog.Error("failed to parse version", slog.String("version", cacheFile.LatestVersion), slog.Any("err", err))
return oldValue, false
}
if newVersion.Compare(oldVersion) > 0 {
return *cacheFile, false
}
return oldValue, false
}) })
return cacheFile, nil return cacheFile, nil
} }
func readCacheFile(filename string) (*File, error) { func readCacheFile(filename string) (*Mod, error) {
downloadCache := filepath.Join(viper.GetString("cache-dir"), "downloadCache") downloadCache := filepath.Join(viper.GetString("cache-dir"), "downloadCache")
path := filepath.Join(downloadCache, filename) path := filepath.Join(downloadCache, filename)
stat, err := os.Stat(path) stat, err := os.Stat(path)
@ -132,11 +146,6 @@ func readCacheFile(filename string) (*File, error) {
modReference := strings.TrimSuffix(upluginFile.Name, ".uplugin") modReference := strings.TrimSuffix(upluginFile.Name, ".uplugin")
hash, err := getFileHash(filename)
if err != nil {
return nil, fmt.Errorf("failed to get file hash: %w", err)
}
var iconFile *zip.File var iconFile *zip.File
for _, file := range reader.File { for _, file := range reader.File {
if file.Name == IconFilename { if file.Name == IconFilename {
@ -160,11 +169,11 @@ func readCacheFile(filename string) (*File, error) {
icon = &iconData icon = &iconData
} }
return &File{ return &Mod{
ModReference: modReference, ModReference: modReference,
Hash: hash, Name: uplugin.FriendlyName,
Size: size, Author: uplugin.CreatedBy,
Icon: icon, Icon: icon,
Plugin: uplugin, LatestVersion: uplugin.SemVersion,
}, nil }, nil
} }

View file

@ -5,6 +5,7 @@ type UPlugin struct {
FriendlyName string `json:"FriendlyName"` FriendlyName string `json:"FriendlyName"`
Description string `json:"Description"` Description string `json:"Description"`
CreatedBy string `json:"CreatedBy"` CreatedBy string `json:"CreatedBy"`
GameVersion string `json:"GameVersion"`
Plugins []Plugins `json:"Plugins"` Plugins []Plugins `json:"Plugins"`
} }
type Plugins struct { type Plugins struct {

View file

@ -8,6 +8,7 @@ import (
"github.com/spf13/viper" "github.com/spf13/viper"
"github.com/satisfactorymodding/ficsit-cli/cli/cache" "github.com/satisfactorymodding/ficsit-cli/cli/cache"
"github.com/satisfactorymodding/ficsit-cli/cli/localregistry"
"github.com/satisfactorymodding/ficsit-cli/cli/provider" "github.com/satisfactorymodding/ficsit-cli/cli/provider"
"github.com/satisfactorymodding/ficsit-cli/ficsit" "github.com/satisfactorymodding/ficsit-cli/ficsit"
) )
@ -45,11 +46,16 @@ func InitCLI(apiOnly bool) (*GlobalContext, error) {
return nil, fmt.Errorf("failed to initialize installations: %w", err) return nil, fmt.Errorf("failed to initialize installations: %w", err)
} }
_, err = cache.LoadCache() _, err = cache.LoadCacheMods()
if err != nil { if err != nil {
return nil, fmt.Errorf("failed to load cache: %w", err) return nil, fmt.Errorf("failed to load cache: %w", err)
} }
err = localregistry.Init()
if err != nil {
return nil, fmt.Errorf("failed to initialize local registry: %w", err)
}
globalContext = &GlobalContext{ globalContext = &GlobalContext{
Installations: installations, Installations: installations,
Profiles: profiles, Profiles: profiles,

View file

@ -3,12 +3,15 @@ package disk
import ( import (
"bytes" "bytes"
"context" "context"
"errors"
"fmt" "fmt"
"io" "io"
"log" "log"
"log/slog" "log/slog"
"net/textproto"
"net/url" "net/url"
"path/filepath" "path"
"slices"
"strings" "strings"
"time" "time"
@ -91,7 +94,86 @@ func testFTP(u *url.URL, options ...ftp.DialOption) (*ftp.ServerConn, bool, erro
return c, false, nil return c, false, nil
} }
func (l *ftpDisk) Exists(path string) (bool, error) { func (l *ftpDisk) existsWithLock(res *puddle.Resource[*ftp.ServerConn], p string) (bool, error) {
slog.Debug("checking if file exists", slog.String("path", clean(p)), slog.String("schema", "ftp"))
var protocolError *textproto.Error
_, err := res.Value().GetEntry(clean(p))
if err == nil {
return true, nil
}
if errors.As(err, &protocolError) {
switch protocolError.Code {
case ftp.StatusFileUnavailable:
return false, nil
case ftp.StatusNotImplemented:
// GetEntry uses MLST, which might not be supported by the server.
// Even though in this case the error is not coming from the server,
// the ftp library still returns it as a protocol error.
default:
// We won't handle any other kind of error, such as
// * temporary errors (4xx) - should be retried after a while, so we won't deal with the delay
// * connection errors (x2x) - can't really do anything about them
// * authentication errors (x3x) - can't do anything about them
return false, fmt.Errorf("failed to get path info: %w", err)
}
} else {
// This is a non-protocol error, so we can't be sure what it means.
return false, fmt.Errorf("failed to get path info: %w", err)
}
// In case MLST is not supported, we can try to LIST the target path.
// We can be sure that List() will actually execute LIST and not MLSD,
// since MLST was not supported in the previous step.
entries, err := res.Value().List(clean(p))
if err == nil {
if len(entries) > 0 {
// Some server implementations return an empty list for a nonexistent path,
// so we cannot be sure that no error means a directory exists unless it also contains some items.
// For files, when they exist, they will be listed as a single entry.
// TODO: so far the servers (just one) this was happening on also listed . and .. for valid dirs, because it was using `LIST -a`. Is that behaviour consistent that we can rely on it?
return true, nil
}
} else {
if errors.As(err, &protocolError) {
if protocolError.Code == ftp.StatusFileUnavailable {
return false, nil
}
}
// We won't handle any other kind of error, see above.
return false, fmt.Errorf("failed to list path: %w", err)
}
// If we got here, either the path is an empty directory,
// or it does not exist and the server is a weird implementation.
// List the parent directory to determine if the path exists
dir, err := l.readDirLock(res, path.Dir(clean(p)))
if err == nil {
found := false
for _, entry := range dir {
if entry.Name() == path.Base(clean(p)) {
found = true
break
}
}
return found, nil
}
if errors.As(err, &protocolError) {
if protocolError.Code == ftp.StatusFileUnavailable {
return false, nil
}
}
// We won't handle any other kind of error, see above.
return false, fmt.Errorf("failed to list parent path: %w", err)
}
func (l *ftpDisk) Exists(p string) (bool, error) {
res, err := l.acquire() res, err := l.acquire()
if err != nil { if err != nil {
return false, err return false, err
@ -99,49 +181,7 @@ func (l *ftpDisk) Exists(path string) (bool, error) {
defer res.Release() defer res.Release()
slog.Debug("checking if file exists", slog.String("path", clean(path)), slog.String("schema", "ftp")) return l.existsWithLock(res, p)
split := strings.Split(clean(path)[1:], "/")
for _, s := range split[:len(split)-1] {
dir, err := l.readDirLock(res, "")
if err != nil {
return false, err
}
currentDir, _ := res.Value().CurrentDir()
foundDir := false
for _, entry := range dir {
if entry.IsDir() && entry.Name() == s {
foundDir = true
break
}
}
if !foundDir {
return false, nil
}
slog.Debug("entering directory", slog.String("dir", s), slog.String("cwd", currentDir), slog.String("schema", "ftp"))
if err := res.Value().ChangeDir(s); err != nil {
return false, fmt.Errorf("failed to enter directory: %w", err)
}
}
dir, err := l.readDirLock(res, "")
if err != nil {
return false, fmt.Errorf("failed listing directory: %w", err)
}
found := false
for _, entry := range dir {
if entry.Name() == clean(filepath.Base(path)) {
found = true
break
}
}
return found, nil
} }
func (l *ftpDisk) Read(path string) ([]byte, error) { func (l *ftpDisk) Read(path string) ([]byte, error) {
@ -203,7 +243,7 @@ func (l *ftpDisk) Remove(path string) error {
return nil return nil
} }
func (l *ftpDisk) MkDir(path string) error { func (l *ftpDisk) MkDir(p string) error {
res, err := l.acquire() res, err := l.acquire()
if err != nil { if err != nil {
return err return err
@ -211,34 +251,47 @@ func (l *ftpDisk) MkDir(path string) error {
defer res.Release() defer res.Release()
split := strings.Split(clean(path)[1:], "/") lastExistingDir := clean(p)
for _, s := range split { for lastExistingDir != "/" && lastExistingDir != "." {
dir, err := l.readDirLock(res, "") foundDir, err := l.existsWithLock(res, lastExistingDir)
if err != nil { if err != nil {
return err return err
} }
currentDir, _ := res.Value().CurrentDir() if foundDir {
break
foundDir := false
for _, entry := range dir {
if entry.IsDir() && entry.Name() == s {
foundDir = true
break
}
} }
if !foundDir { lastExistingDir = path.Dir(lastExistingDir)
slog.Debug("making directory", slog.String("dir", s), slog.String("cwd", currentDir), slog.String("schema", "ftp")) }
if err := res.Value().MakeDir(s); err != nil {
return fmt.Errorf("failed to make directory: %w", err) remainingDirs := clean(p)
}
if lastExistingDir != "/" && lastExistingDir != "." {
remainingDirs = strings.TrimPrefix(remainingDirs, lastExistingDir)
}
if len(remainingDirs) == 0 {
// Already exists
return nil
}
if err := res.Value().ChangeDir(lastExistingDir); err != nil {
return fmt.Errorf("failed to enter directory: %w", err)
}
split := strings.Split(clean(remainingDirs)[1:], "/")
for _, s := range split {
slog.Debug("making directory", slog.String("dir", s), slog.String("cwd", lastExistingDir), slog.String("schema", "ftp"))
if err := res.Value().MakeDir(s); err != nil {
return fmt.Errorf("failed to make directory: %w", err)
} }
slog.Debug("entering directory", slog.String("dir", s), slog.String("cwd", currentDir), slog.String("schema", "ftp")) slog.Debug("entering directory", slog.String("dir", s), slog.String("cwd", lastExistingDir), slog.String("schema", "ftp"))
if err := res.Value().ChangeDir(s); err != nil { if err := res.Value().ChangeDir(s); err != nil {
return fmt.Errorf("failed to enter directory: %w", err) return fmt.Errorf("failed to enter directory: %w", err)
} }
lastExistingDir = path.Join(lastExistingDir, s)
} }
return nil return nil
@ -252,7 +305,14 @@ func (l *ftpDisk) ReadDir(path string) ([]Entry, error) {
defer res.Release() defer res.Release()
return l.readDirLock(res, path) entries, err := l.readDirLock(res, path)
if err != nil {
return nil, err
}
entries = slices.DeleteFunc(entries, func(i Entry) bool {
return i.Name() == "." || i.Name() == ".."
})
return entries, nil
} }
func (l *ftpDisk) readDirLock(res *puddle.Resource[*ftp.ServerConn], path string) ([]Entry, error) { func (l *ftpDisk) readDirLock(res *puddle.Resource[*ftp.ServerConn], path string) ([]Entry, error) {

View file

@ -183,6 +183,8 @@ func (i *Installations) DeleteInstallation(installPath string) error {
return nil return nil
} }
var rootExecutables = []string{"FactoryGame.exe", "FactoryServer.sh", "FactoryServer.exe", "FactoryGameSteam.exe", "FactoryGameEGS.exe"}
func (i *Installation) Validate(ctx *GlobalContext) error { func (i *Installation) Validate(ctx *GlobalContext) error {
found := false found := false
for _, p := range ctx.Profiles.Profiles { for _, p := range ctx.Profiles.Profiles {
@ -203,31 +205,25 @@ func (i *Installation) Validate(ctx *GlobalContext) error {
foundExecutable := false foundExecutable := false
exists, err := d.Exists(filepath.Join(i.BasePath(), "FactoryGame.exe")) var checkWait errgroup.Group
if !exists {
if err != nil { for _, executable := range rootExecutables {
return fmt.Errorf("failed reading FactoryGame.exe: %w", err) e := executable
} checkWait.Go(func() error {
} else { exists, err := d.Exists(filepath.Join(i.BasePath(), e))
foundExecutable = true if !exists {
if err != nil {
return fmt.Errorf("failed reading %s: %w", e, err)
}
} else {
foundExecutable = true
}
return nil
})
} }
exists, err = d.Exists(filepath.Join(i.BasePath(), "FactoryServer.sh")) if err = checkWait.Wait(); err != nil {
if !exists { return err //nolint:wrapcheck
if err != nil {
return fmt.Errorf("failed reading FactoryServer.sh: %w", err)
}
} else {
foundExecutable = true
}
exists, err = d.Exists(filepath.Join(i.BasePath(), "FactoryServer.exe"))
if !exists {
if err != nil {
return fmt.Errorf("failed reading FactoryServer.exe: %w", err)
}
} else {
foundExecutable = true
} }
if !foundExecutable { if !foundExecutable {
@ -243,26 +239,18 @@ var (
matchAllCap = regexp.MustCompile(`([a-z\d])([A-Z])`) matchAllCap = regexp.MustCompile(`([a-z\d])([A-Z])`)
) )
func (i *Installation) LockFilePath(ctx *GlobalContext) (string, error) { func (i *Installation) lockFilePath(ctx *GlobalContext, platform *Platform) string {
platform, err := i.GetPlatform(ctx)
if err != nil {
return "", err
}
lockFileName := ctx.Profiles.Profiles[i.Profile].Name lockFileName := ctx.Profiles.Profiles[i.Profile].Name
lockFileName = matchFirstCap.ReplaceAllString(lockFileName, "${1}_${2}") lockFileName = matchFirstCap.ReplaceAllString(lockFileName, "${1}_${2}")
lockFileName = matchAllCap.ReplaceAllString(lockFileName, "${1}_${2}") lockFileName = matchAllCap.ReplaceAllString(lockFileName, "${1}_${2}")
lockFileName = lockFileCleaner.ReplaceAllLiteralString(lockFileName, "-") lockFileName = lockFileCleaner.ReplaceAllLiteralString(lockFileName, "-")
lockFileName = strings.ToLower(lockFileName) + "-lock.json" lockFileName = strings.ToLower(lockFileName) + "-lock.json"
return filepath.Join(i.BasePath(), platform.LockfilePath, lockFileName), nil return filepath.Join(i.BasePath(), platform.LockfilePath, lockFileName)
} }
func (i *Installation) LockFile(ctx *GlobalContext) (*resolver.LockFile, error) { func (i *Installation) lockfile(ctx *GlobalContext, platform *Platform) (*resolver.LockFile, error) {
lockfilePath, err := i.LockFilePath(ctx) lockfilePath := i.lockFilePath(ctx, platform)
if err != nil {
return nil, err
}
d, err := i.GetDisk() d, err := i.GetDisk()
if err != nil { if err != nil {
@ -291,11 +279,8 @@ func (i *Installation) LockFile(ctx *GlobalContext) (*resolver.LockFile, error)
return lockFile, nil return lockFile, nil
} }
func (i *Installation) WriteLockFile(ctx *GlobalContext, lockfile *resolver.LockFile) error { func (i *Installation) writeLockFile(ctx *GlobalContext, platform *Platform, lockfile *resolver.LockFile) error {
lockfilePath, err := i.LockFilePath(ctx) lockfilePath := i.lockFilePath(ctx, platform)
if err != nil {
return err
}
d, err := i.GetDisk() d, err := i.GetDisk()
if err != nil { if err != nil {
@ -341,15 +326,15 @@ func (i *Installation) Wipe() error {
return nil return nil
} }
func (i *Installation) ResolveProfile(ctx *GlobalContext) (*resolver.LockFile, error) { func (i *Installation) resolveProfile(ctx *GlobalContext, platform *Platform) (*resolver.LockFile, error) {
lockFile, err := i.LockFile(ctx) lockFile, err := i.lockfile(ctx, platform)
if err != nil { if err != nil {
return nil, err return nil, err
} }
depResolver := resolver.NewDependencyResolver(ctx.Provider, viper.GetString("api-base")) depResolver := resolver.NewDependencyResolver(ctx.Provider)
gameVersion, err := i.GetGameVersion(ctx) gameVersion, err := i.getGameVersion(platform)
if err != nil { if err != nil {
return nil, fmt.Errorf("failed to detect game version: %w", err) return nil, fmt.Errorf("failed to detect game version: %w", err)
} }
@ -359,13 +344,37 @@ func (i *Installation) ResolveProfile(ctx *GlobalContext) (*resolver.LockFile, e
return nil, fmt.Errorf("could not resolve mods: %w", err) return nil, fmt.Errorf("could not resolve mods: %w", err)
} }
if err := i.WriteLockFile(ctx, lockfile); err != nil { if err := i.writeLockFile(ctx, platform, lockfile); err != nil {
return nil, fmt.Errorf("failed to write lockfile: %w", err) return nil, fmt.Errorf("failed to write lockfile: %w", err)
} }
return lockfile, nil return lockfile, nil
} }
func (i *Installation) GetGameVersion(ctx *GlobalContext) (int, error) {
platform, err := i.GetPlatform(ctx)
if err != nil {
return 0, err
}
return i.getGameVersion(platform)
}
func (i *Installation) LockFile(ctx *GlobalContext) (*resolver.LockFile, error) {
platform, err := i.GetPlatform(ctx)
if err != nil {
return nil, err
}
return i.lockfile(ctx, platform)
}
func (i *Installation) WriteLockFile(ctx *GlobalContext, lockfile *resolver.LockFile) error {
platform, err := i.GetPlatform(ctx)
if err != nil {
return err
}
return i.writeLockFile(ctx, platform, lockfile)
}
type InstallUpdateType string type InstallUpdateType string
var ( var (
@ -387,10 +396,6 @@ type InstallUpdateItem struct {
} }
func (i *Installation) Install(ctx *GlobalContext, updates chan<- InstallUpdate) error { func (i *Installation) Install(ctx *GlobalContext, updates chan<- InstallUpdate) error {
if err := i.Validate(ctx); err != nil {
return fmt.Errorf("failed to validate installation: %w", err)
}
platform, err := i.GetPlatform(ctx) platform, err := i.GetPlatform(ctx)
if err != nil { if err != nil {
return fmt.Errorf("failed to detect platform: %w", err) return fmt.Errorf("failed to detect platform: %w", err)
@ -400,7 +405,7 @@ func (i *Installation) Install(ctx *GlobalContext, updates chan<- InstallUpdate)
if !i.Vanilla { if !i.Vanilla {
var err error var err error
lockfile, err = i.ResolveProfile(ctx) lockfile, err = i.resolveProfile(ctx, platform)
if err != nil { if err != nil {
return fmt.Errorf("failed to resolve lockfile: %w", err) return fmt.Errorf("failed to resolve lockfile: %w", err)
} }
@ -421,25 +426,41 @@ func (i *Installation) Install(ctx *GlobalContext, updates chan<- InstallUpdate)
return fmt.Errorf("failed to read mods directory: %w", err) return fmt.Errorf("failed to read mods directory: %w", err)
} }
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()
modDir := filepath.Join(modsDirectory, entry.Name()) mod, hasMod := lockfile.Mods[modName]
exists, err := d.Exists(filepath.Join(modDir, ".smm")) if hasMod {
if err != nil { _, hasTarget := mod.Targets[platform.TargetName]
return err hasMod = hasTarget
} }
if !hasMod {
if exists { modName := entry.Name()
slog.Info("deleting mod", slog.String("mod_reference", entry.Name())) modDir := filepath.Join(modsDirectory, modName)
if err := d.Remove(modDir); err != nil { deleteWait.Go(func() error {
return fmt.Errorf("failed to delete mod directory: %w", err) exists, err := d.Exists(filepath.Join(modDir, ".smm"))
if err != nil {
return err
} }
}
if exists {
slog.Info("deleting mod", slog.String("mod_reference", modName))
if err := d.Remove(modDir); err != nil {
return fmt.Errorf("failed to delete mod directory: %w", err)
}
}
return nil
})
} }
} }
} }
if err := deleteWait.Wait(); err != nil {
return fmt.Errorf("failed to remove old mods: %w", err)
}
slog.Info("starting installation", slog.Int("concurrency", viper.GetInt("concurrent-downloads")), slog.String("path", i.Path)) slog.Info("starting installation", slog.Int("concurrency", viper.GetInt("concurrent-downloads")), slog.String("path", i.Path))
errg := errgroup.Group{} errg := errgroup.Group{}
@ -478,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
@ -523,18 +547,19 @@ func (i *Installation) Install(ctx *GlobalContext, updates chan<- InstallUpdate)
} }
func (i *Installation) UpdateMods(ctx *GlobalContext, mods []string) error { func (i *Installation) UpdateMods(ctx *GlobalContext, mods []string) error {
if err := i.Validate(ctx); err != nil { platform, err := i.GetPlatform(ctx)
return fmt.Errorf("failed to validate installation: %w", err) if err != nil {
return err
} }
lockFile, err := i.LockFile(ctx) lockFile, err := i.lockfile(ctx, platform)
if err != nil { if err != nil {
return fmt.Errorf("failed to read lock file: %w", err) return fmt.Errorf("failed to read lock file: %w", err)
} }
resolver := resolver.NewDependencyResolver(ctx.Provider, viper.GetString("api-base")) resolver := resolver.NewDependencyResolver(ctx.Provider)
gameVersion, err := i.GetGameVersion(ctx) gameVersion, err := i.getGameVersion(platform)
if err != nil { if err != nil {
return fmt.Errorf("failed to detect game version: %w", err) return fmt.Errorf("failed to detect game version: %w", err)
} }
@ -553,7 +578,7 @@ func (i *Installation) UpdateMods(ctx *GlobalContext, mods []string) error {
return fmt.Errorf("failed to resolve dependencies: %w", err) return fmt.Errorf("failed to resolve dependencies: %w", err)
} }
if err := i.WriteLockFile(ctx, newLockFile); err != nil { if err := i.writeLockFile(ctx, platform, newLockFile); err != nil {
return fmt.Errorf("failed to write lock file: %w", err) return fmt.Errorf("failed to write lock file: %w", err)
} }
@ -667,30 +692,13 @@ type gameVersionFile struct {
IsPromotedBuild int `json:"IsPromotedBuild"` IsPromotedBuild int `json:"IsPromotedBuild"`
} }
func (i *Installation) GetGameVersion(ctx *GlobalContext) (int, error) { func (i *Installation) getGameVersion(platform *Platform) (int, error) {
if err := i.Validate(ctx); err != nil {
return 0, fmt.Errorf("failed to validate installation: %w", err)
}
platform, err := i.GetPlatform(ctx)
if err != nil {
return 0, err
}
d, err := i.GetDisk() d, err := i.GetDisk()
if err != nil { if err != nil {
return 0, err return 0, err
} }
fullPath := filepath.Join(i.BasePath(), platform.VersionPath) fullPath := filepath.Join(i.BasePath(), platform.VersionPath)
exists, err := d.Exists(fullPath)
if err != nil {
return 0, err
}
if !exists {
return 0, errors.New("game version file does not exist")
}
file, err := d.Read(fullPath) file, err := d.Read(fullPath)
if err != nil { if err != nil {

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

@ -0,0 +1,175 @@
package localregistry
import (
"database/sql"
"fmt"
"log/slog"
"os"
"path/filepath"
"sync"
"github.com/spf13/viper"
"github.com/satisfactorymodding/ficsit-cli/ficsit"
// sqlite driver
_ "modernc.org/sqlite"
)
var db *sql.DB
var dbWriteMutex = sync.Mutex{}
func Init() error {
dbPath := filepath.Join(viper.GetString("cache-dir"), "registry.db")
err := os.MkdirAll(filepath.Dir(dbPath), 0o777)
if err != nil {
return fmt.Errorf("failed to create local registry directory: %w", err)
}
db, err = sql.Open("sqlite", dbPath)
if err != nil {
return fmt.Errorf("failed to open database: %w", err)
}
// Set pragmas here because modernc.org/sqlite does not support them in the connection string
_, err = db.Exec(`
PRAGMA journal_mode = WAL;
PRAGMA foreign_keys = ON;
PRAGMA busy_timeout = 5000;
`)
if err != nil {
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 []ficsit.ModVersion) {
dbWriteMutex.Lock()
defer dbWriteMutex.Unlock()
tx, err := db.Begin()
if err != nil {
slog.Error("failed to start local registry transaction", slog.Any("err", err))
return
}
// In case the transaction is not committed, revert and release
defer tx.Rollback() //nolint:errcheck
_, err = tx.Exec("DELETE FROM versions WHERE mod_reference = ?", modReference)
if err != nil {
slog.Error("failed to delete existing mod versions from local registry", slog.Any("err", err))
return
}
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, 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
}
for _, dependency := range modVersion.Dependencies {
_, err = tx.Exec("INSERT INTO dependencies (version_id, dependency, condition, optional) VALUES (?, ?, ?, ?)", modVersion.ID, dependency.ModID, dependency.Condition, dependency.Optional)
if err != nil {
l.Error("failed to insert dependency into local registry", slog.String("dependency", dependency.ModID), slog.Any("err", err))
return
}
}
for _, target := range modVersion.Targets {
_, err = tx.Exec("INSERT INTO targets (version_id, target_name, link, hash, size) VALUES (?, ?, ?, ?, ?)", modVersion.ID, target.TargetName, target.Link, target.Hash, target.Size)
if err != nil {
l.Error("failed to insert target into local registry", slog.Any("target", target.TargetName), slog.Any("err", err))
return
}
}
}
err = tx.Commit()
if err != nil {
slog.Error("failed to commit local registry transaction", slog.Any("err", err))
return
}
}
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 []ficsit.ModVersion
for versionRows.Next() {
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)
}
dependencies, err := getVersionDependencies(version.ID)
if err != nil {
return nil, err
}
version.Dependencies = dependencies
targets, err := getVersionTargets(version.ID)
if err != nil {
return nil, err
}
version.Targets = targets
versions = append(versions, version)
}
return versions, nil
}
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)
}
defer dependencyRows.Close()
for dependencyRows.Next() {
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)
}
dependencies = append(dependencies, dependency)
}
return dependencies, nil
}
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)
}
defer targetRows.Close()
for targetRows.Next() {
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)
}
targets = append(targets, target)
}
return targets, nil
}

View file

@ -35,4 +35,15 @@ var platforms = []Platform{
LockfilePath: filepath.Join("FactoryGame", "Mods"), LockfilePath: filepath.Join("FactoryGame", "Mods"),
TargetName: "WindowsServer", TargetName: "WindowsServer",
}, },
// 1.0 stuff
{
VersionPath: filepath.Join("Engine", "Binaries", "Win64", "FactoryGameSteam-Win64-Shipping.version"),
LockfilePath: filepath.Join("FactoryGame", "Mods"),
TargetName: "Windows",
},
{
VersionPath: filepath.Join("Engine", "Binaries", "Win64", "FactoryGameEGS-Win64-Shipping.version"),
LockfilePath: filepath.Join("FactoryGame", "Mods"),
TargetName: "Windows",
},
} }

View file

@ -1,7 +1,6 @@
package cli package cli
import ( import (
"context"
"encoding/json" "encoding/json"
"errors" "errors"
"fmt" "fmt"
@ -299,7 +298,7 @@ func (p *Profile) Resolve(resolver resolver.DependencyResolver, lockFile *resolv
} }
} }
resultLockfile, err := resolver.ResolveModDependencies(context.TODO(), toResolve, lockFile, gameVersion, p.RequiredTargets) resultLockfile, err := resolver.ResolveModDependencies(toResolve, lockFile, gameVersion, p.RequiredTargets)
if err != nil { if err != nil {
return nil, fmt.Errorf("failed resolving profile dependencies: %w", err) return nil, fmt.Errorf("failed resolving profile dependencies: %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

@ -7,6 +7,7 @@ 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/satisfactorymodding/ficsit-cli/cli/localregistry"
"github.com/satisfactorymodding/ficsit-cli/ficsit" "github.com/satisfactorymodding/ficsit-cli/ficsit"
) )
@ -28,38 +29,6 @@ func (p FicsitProvider) GetMod(context context.Context, modReference string) (*f
return ficsit.GetMod(context, p.client, modReference) return ficsit.GetMod(context, p.client, modReference)
} }
func (p FicsitProvider) ModVersions(context context.Context, modReference string, filter ficsit.VersionFilter) (*ficsit.ModVersionsResponse, error) {
return ficsit.ModVersions(context, p.client, modReference, filter)
}
func (p FicsitProvider) SMLVersions(context context.Context) ([]resolver.SMLVersion, error) {
response, err := ficsit.SMLVersions(context, p.client)
if err != nil {
return nil, err
}
smlVersions := make([]resolver.SMLVersion, len(response.SmlVersions.Sml_versions))
for i, version := range response.GetSmlVersions().Sml_versions {
targets := make([]resolver.SMLVersionTarget, len(version.Targets))
for j, target := range version.Targets {
targets[j] = resolver.SMLVersionTarget{
TargetName: resolver.TargetName(target.TargetName),
Link: target.Link,
}
}
smlVersions[i] = resolver.SMLVersion{
ID: version.Id,
Version: version.Version,
SatisfactoryVersion: version.Satisfactory_version,
Targets: targets,
}
}
return smlVersions, nil
}
func (p FicsitProvider) ModVersionsWithDependencies(_ context.Context, modID string) ([]resolver.ModVersion, error) { func (p FicsitProvider) ModVersionsWithDependencies(_ context.Context, modID string) ([]resolver.ModVersion, error) {
response, err := ficsit.GetAllModVersions(modID) response, err := ficsit.GetAllModVersions(modID)
if err != nil { if err != nil {
@ -70,36 +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{
VersionID: target.VersionID,
TargetName: resolver.TargetName(target.TargetName),
Hash: target.Hash,
Size: target.Size,
}
}
modVersions[i] = resolver.ModVersion{
ID: modVersion.ID,
Version: modVersion.Version,
Dependencies: dependencies,
Targets: targets,
}
}
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

@ -2,7 +2,6 @@ package provider
import ( import (
"context" "context"
"errors"
"fmt" "fmt"
"strings" "strings"
"time" "time"
@ -10,6 +9,7 @@ import (
resolver "github.com/satisfactorymodding/ficsit-resolver" resolver "github.com/satisfactorymodding/ficsit-resolver"
"github.com/satisfactorymodding/ficsit-cli/cli/cache" "github.com/satisfactorymodding/ficsit-cli/cli/cache"
"github.com/satisfactorymodding/ficsit-cli/cli/localregistry"
"github.com/satisfactorymodding/ficsit-cli/ficsit" "github.com/satisfactorymodding/ficsit-cli/ficsit"
) )
@ -20,18 +20,14 @@ func NewLocalProvider() LocalProvider {
} }
func (p LocalProvider) Mods(_ context.Context, filter ficsit.ModFilter) (*ficsit.ModsResponse, error) { func (p LocalProvider) Mods(_ context.Context, filter ficsit.ModFilter) (*ficsit.ModsResponse, error) {
cachedMods, err := cache.GetCache() cachedMods, err := cache.GetCacheMods()
if err != nil { if err != nil {
return nil, fmt.Errorf("failed to get cache: %w", err) return nil, fmt.Errorf("failed to get cache: %w", err)
} }
mods := make([]ficsit.ModsModsGetModsModsMod, 0) mods := make([]ficsit.ModsModsGetModsModsMod, 0)
cachedMods.Range(func(modReference string, files []cache.File) bool { cachedMods.Range(func(modReference string, cachedMod cache.Mod) bool {
if modReference == "SML" {
return true
}
if len(filter.References) > 0 { if len(filter.References) > 0 {
skip := true skip := true
@ -49,7 +45,7 @@ func (p LocalProvider) Mods(_ context.Context, filter ficsit.ModFilter) (*ficsit
mods = append(mods, ficsit.ModsModsGetModsModsMod{ mods = append(mods, ficsit.ModsModsGetModsModsMod{
Id: modReference, Id: modReference,
Name: files[0].Plugin.FriendlyName, Name: cachedMod.Name,
Mod_reference: modReference, Mod_reference: modReference,
Last_version_date: time.Now(), Last_version_date: time.Now(),
Created_at: time.Now(), Created_at: time.Now(),
@ -92,18 +88,14 @@ func (p LocalProvider) Mods(_ context.Context, filter ficsit.ModFilter) (*ficsit
} }
func (p LocalProvider) GetMod(_ context.Context, modReference string) (*ficsit.GetModResponse, error) { func (p LocalProvider) GetMod(_ context.Context, modReference string) (*ficsit.GetModResponse, error) {
cachedModFiles, err := cache.GetCacheMod(modReference) cachedMod, err := cache.GetCacheMod(modReference)
if err != nil { if err != nil {
return nil, fmt.Errorf("failed to get cache: %w", err) return nil, fmt.Errorf("failed to get cache: %w", err)
} }
if len(cachedModFiles) == 0 {
return nil, errors.New("mod not found")
}
authors := make([]ficsit.GetModModAuthorsUserMod, 0) authors := make([]ficsit.GetModModAuthorsUserMod, 0)
for _, author := range strings.Split(cachedModFiles[0].Plugin.CreatedBy, ",") { for _, author := range strings.Split(cachedMod.Author, ",") {
authors = append(authors, ficsit.GetModModAuthorsUserMod{ authors = append(authors, ficsit.GetModModAuthorsUserMod{
Role: "Unknown", Role: "Unknown",
User: ficsit.GetModModAuthorsUserModUser{ User: ficsit.GetModModAuthorsUserModUser{
@ -115,7 +107,7 @@ func (p LocalProvider) GetMod(_ context.Context, modReference string) (*ficsit.G
return &ficsit.GetModResponse{ return &ficsit.GetModResponse{
Mod: ficsit.GetModMod{ Mod: ficsit.GetModMod{
Id: modReference, Id: modReference,
Name: cachedModFiles[0].Plugin.FriendlyName, Name: cachedMod.Name,
Mod_reference: modReference, Mod_reference: modReference,
Created_at: time.Now(), Created_at: time.Now(),
Views: 0, Views: 0,
@ -127,56 +119,26 @@ func (p LocalProvider) GetMod(_ context.Context, modReference string) (*ficsit.G
}, nil }, nil
} }
func (p LocalProvider) SMLVersions(_ context.Context) ([]resolver.SMLVersion, error) {
cachedSMLFiles, err := cache.GetCacheMod("SML")
if err != nil {
return nil, fmt.Errorf("failed to get cache: %w", err)
}
smlVersions := make([]resolver.SMLVersion, 0)
for _, smlFile := range cachedSMLFiles {
smlVersions = append(smlVersions, resolver.SMLVersion{
ID: "SML:" + smlFile.Plugin.SemVersion,
Version: smlFile.Plugin.SemVersion,
SatisfactoryVersion: 0, // TODO: where can this be obtained from?
})
}
return smlVersions, nil
}
func (p LocalProvider) ModVersionsWithDependencies(_ context.Context, modID string) ([]resolver.ModVersion, error) { func (p LocalProvider) ModVersionsWithDependencies(_ context.Context, modID string) ([]resolver.ModVersion, error) {
cachedModFiles, err := cache.GetCacheMod(modID) modVersions, err := localregistry.GetModVersions(modID)
if err != nil { if err != nil {
return nil, fmt.Errorf("failed to get cache: %w", err) return nil, fmt.Errorf("failed to get local mod versions: %w", err)
} }
versions := make([]resolver.ModVersion, 0) // TODO: only list as available the versions that have at least one target cached
for _, modFile := range cachedModFiles { return convertFicsitVersionsToResolver(modVersions), nil
versions = append(versions, resolver.ModVersion{
ID: modID + ":" + modFile.Plugin.SemVersion,
Version: modFile.Plugin.SemVersion,
})
}
return versions, nil
} }
func (p LocalProvider) GetModName(_ context.Context, modReference string) (*resolver.ModName, error) { func (p LocalProvider) GetModName(_ context.Context, modReference string) (*resolver.ModName, error) {
cachedModFiles, err := cache.GetCacheMod(modReference) cachedMod, err := cache.GetCacheMod(modReference)
if err != nil { if err != nil {
return nil, fmt.Errorf("failed to get cache: %w", err) return nil, fmt.Errorf("failed to get cache: %w", err)
} }
if len(cachedModFiles) == 0 {
return nil, errors.New("mod not found")
}
return &resolver.ModName{ return &resolver.ModName{
ID: modReference, ID: modReference,
Name: cachedModFiles[0].Plugin.FriendlyName, Name: cachedMod.Name,
ModReference: modReference, ModReference: modReference,
}, nil }, nil
} }

View file

@ -36,13 +36,6 @@ func (p MixedProvider) GetMod(context context.Context, modReference string) (*fi
return p.onlineProvider.GetMod(context, modReference) return p.onlineProvider.GetMod(context, modReference)
} }
func (p MixedProvider) SMLVersions(context context.Context) ([]resolver.SMLVersion, error) {
if p.Offline {
return p.offlineProvider.SMLVersions(context) // nolint
}
return p.onlineProvider.SMLVersions(context) // nolint
}
func (p MixedProvider) ModVersionsWithDependencies(context context.Context, modID string) ([]resolver.ModVersion, error) { func (p MixedProvider) ModVersionsWithDependencies(context context.Context, modID string) ([]resolver.ModVersion, error) {
if p.Offline { if p.Offline {
return p.offlineProvider.ModVersionsWithDependencies(context, modID) // nolint return p.offlineProvider.ModVersionsWithDependencies(context, modID) // nolint

View file

@ -1,15 +1,15 @@
package cli package cli
import ( import (
"context"
"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"
"github.com/spf13/viper"
"github.com/satisfactorymodding/ficsit-cli/cfg" "github.com/satisfactorymodding/ficsit-cli/cfg"
) )
@ -34,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)
@ -43,9 +145,9 @@ func TestUpdateMods(t *testing.T) {
ctx.Provider = MockProvider{} ctx.Provider = MockProvider{}
depResolver := resolver.NewDependencyResolver(ctx.Provider, viper.GetString("api-base")) depResolver := resolver.NewDependencyResolver(ctx.Provider)
oldLockfile, err := depResolver.ResolveModDependencies(context.Background(), map[string]string{ oldLockfile, err := depResolver.ResolveModDependencies(map[string]string{
"FicsitRemoteMonitoring": "0.9.8", "FicsitRemoteMonitoring": "0.9.8",
}, nil, math.MaxInt, nil) }, nil, math.MaxInt, nil)

View file

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

View file

@ -131,6 +131,8 @@ func init() {
baseLocalDir = os.Getenv("APPDATA") baseLocalDir = os.Getenv("APPDATA")
case "linux": case "linux":
baseLocalDir = filepath.Join(os.Getenv("HOME"), ".local", "share") baseLocalDir = filepath.Join(os.Getenv("HOME"), ".local", "share")
case "darwin":
baseLocalDir = filepath.Join(os.Getenv("HOME"), "Library", "Application Support")
default: default:
panic("unsupported platform: " + runtime.GOOS) panic("unsupported platform: " + runtime.GOOS)
} }

View file

@ -1,26 +1,25 @@
// https://cspell.org/configuration/ // https://cspell.org/configuration/
{ {
// Version of the setting file. Always 0.2 // Version of the setting file. Always 0.2
"version": "0.2", "version": "0.2",
// language - current active spelling language // language - current active spelling language
"language": "en", "language": "en",
// words - list of words to be always considered correct // words - list of words to be always considered correct
"words": [ "words": [
"ficsit", "armv",
"gofumpt", "ficsit",
"Goland", "gofumpt",
"golangci", "Goland",
"goquery", "golangci",
"graphqurl", "goquery",
"mvdan", "graphqurl",
"pgdn", "mvdan",
"pgup", "pgdn",
"wordwrap" "pgup",
], "wordwrap"
// flagWords - list of words to be always considered incorrect ],
// This is useful for offensive words and common spelling errors. // flagWords - list of words to be always considered incorrect
// cSpell:disable (don't complain about the words we listed here) // This is useful for offensive words and common spelling errors.
"flagWords": [ // cSpell:disable (don't complain about the words we listed here)
"hte" "flagWords": ["hte"]
]
} }

View file

@ -24,12 +24,12 @@ cli mod manager for satisfactory
### SEE ALSO ### SEE ALSO
* [ficsit apply](ficsit_apply.md) - Apply profiles to all installations - [ficsit apply](ficsit_apply.md) - Apply profiles to all installations
* [ficsit cli](ficsit_cli.md) - Start interactive CLI (default) - [ficsit cli](ficsit_cli.md) - Start interactive CLI (default)
* [ficsit installation](ficsit_installation.md) - Manage installations - [ficsit installation](ficsit_installation.md) - Manage installations
* [ficsit profile](ficsit_profile.md) - Manage profiles - [ficsit profile](ficsit_profile.md) - Manage profiles
* [ficsit search](ficsit_search.md) - Search mods - [ficsit search](ficsit_search.md) - Search mods
* [ficsit smr](ficsit_smr.md) - Manage mods on SMR - [ficsit smr](ficsit_smr.md) - Manage mods on SMR
* [ficsit version](ficsit_version.md) - Print current version information - [ficsit version](ficsit_version.md) - Print current version information
###### Auto generated by spf13/cobra on 7-Dec-2023 ###### Auto generated by spf13/cobra on 7-Dec-2023

View file

@ -33,6 +33,6 @@ ficsit apply [installation] ... [flags]
### SEE ALSO ### SEE ALSO
* [ficsit](ficsit.md) - cli mod manager for satisfactory - [ficsit](ficsit.md) - cli mod manager for satisfactory
###### Auto generated by spf13/cobra on 7-Dec-2023 ###### Auto generated by spf13/cobra on 7-Dec-2023

View file

@ -33,6 +33,6 @@ ficsit cli [flags]
### SEE ALSO ### SEE ALSO
* [ficsit](ficsit.md) - cli mod manager for satisfactory - [ficsit](ficsit.md) - cli mod manager for satisfactory
###### Auto generated by spf13/cobra on 7-Dec-2023 ###### Auto generated by spf13/cobra on 7-Dec-2023

View file

@ -29,11 +29,11 @@ Manage installations
### SEE ALSO ### SEE ALSO
* [ficsit](ficsit.md) - cli mod manager for satisfactory - [ficsit](ficsit.md) - cli mod manager for satisfactory
* [ficsit installation add](ficsit_installation_add.md) - Add an installation - [ficsit installation add](ficsit_installation_add.md) - Add an installation
* [ficsit installation ls](ficsit_installation_ls.md) - List all installations - [ficsit installation ls](ficsit_installation_ls.md) - List all installations
* [ficsit installation remove](ficsit_installation_remove.md) - Remove an installation - [ficsit installation remove](ficsit_installation_remove.md) - Remove an installation
* [ficsit installation set-profile](ficsit_installation_set-profile.md) - Change the profile of an installation - [ficsit installation set-profile](ficsit_installation_set-profile.md) - Change the profile of an installation
* [ficsit installation set-vanilla](ficsit_installation_set-vanilla.md) - Set the installation to vanilla mode or not - [ficsit installation set-vanilla](ficsit_installation_set-vanilla.md) - Set the installation to vanilla mode or not
###### Auto generated by spf13/cobra on 7-Dec-2023 ###### Auto generated by spf13/cobra on 7-Dec-2023

View file

@ -33,6 +33,6 @@ ficsit installation add <path> [profile] [flags]
### SEE ALSO ### SEE ALSO
* [ficsit installation](ficsit_installation.md) - Manage installations - [ficsit installation](ficsit_installation.md) - Manage installations
###### Auto generated by spf13/cobra on 7-Dec-2023 ###### Auto generated by spf13/cobra on 7-Dec-2023

View file

@ -33,6 +33,6 @@ ficsit installation ls [flags]
### SEE ALSO ### SEE ALSO
* [ficsit installation](ficsit_installation.md) - Manage installations - [ficsit installation](ficsit_installation.md) - Manage installations
###### Auto generated by spf13/cobra on 7-Dec-2023 ###### Auto generated by spf13/cobra on 7-Dec-2023

View file

@ -33,6 +33,6 @@ ficsit installation remove <path> [flags]
### SEE ALSO ### SEE ALSO
* [ficsit installation](ficsit_installation.md) - Manage installations - [ficsit installation](ficsit_installation.md) - Manage installations
###### Auto generated by spf13/cobra on 7-Dec-2023 ###### Auto generated by spf13/cobra on 7-Dec-2023

View file

@ -33,6 +33,6 @@ ficsit installation set-profile <path> <profile> [flags]
### SEE ALSO ### SEE ALSO
* [ficsit installation](ficsit_installation.md) - Manage installations - [ficsit installation](ficsit_installation.md) - Manage installations
###### Auto generated by spf13/cobra on 7-Dec-2023 ###### Auto generated by spf13/cobra on 7-Dec-2023

View file

@ -34,6 +34,6 @@ ficsit installation set-vanilla <path> [flags]
### SEE ALSO ### SEE ALSO
* [ficsit installation](ficsit_installation.md) - Manage installations - [ficsit installation](ficsit_installation.md) - Manage installations
###### Auto generated by spf13/cobra on 7-Dec-2023 ###### Auto generated by spf13/cobra on 7-Dec-2023

View file

@ -29,11 +29,11 @@ Manage profiles
### SEE ALSO ### SEE ALSO
* [ficsit](ficsit.md) - cli mod manager for satisfactory - [ficsit](ficsit.md) - cli mod manager for satisfactory
* [ficsit profile delete](ficsit_profile_delete.md) - Delete a profile - [ficsit profile delete](ficsit_profile_delete.md) - Delete a profile
* [ficsit profile ls](ficsit_profile_ls.md) - List all profiles - [ficsit profile ls](ficsit_profile_ls.md) - List all profiles
* [ficsit profile mods](ficsit_profile_mods.md) - List all mods in a profile - [ficsit profile mods](ficsit_profile_mods.md) - List all mods in a profile
* [ficsit profile new](ficsit_profile_new.md) - Create a new profile - [ficsit profile new](ficsit_profile_new.md) - Create a new profile
* [ficsit profile rename](ficsit_profile_rename.md) - Rename a profile - [ficsit profile rename](ficsit_profile_rename.md) - Rename a profile
###### Auto generated by spf13/cobra on 7-Dec-2023 ###### Auto generated by spf13/cobra on 7-Dec-2023

View file

@ -33,6 +33,6 @@ ficsit profile delete <name> [flags]
### SEE ALSO ### SEE ALSO
* [ficsit profile](ficsit_profile.md) - Manage profiles - [ficsit profile](ficsit_profile.md) - Manage profiles
###### Auto generated by spf13/cobra on 7-Dec-2023 ###### Auto generated by spf13/cobra on 7-Dec-2023

View file

@ -33,6 +33,6 @@ ficsit profile ls [flags]
### SEE ALSO ### SEE ALSO
* [ficsit profile](ficsit_profile.md) - Manage profiles - [ficsit profile](ficsit_profile.md) - Manage profiles
###### Auto generated by spf13/cobra on 7-Dec-2023 ###### Auto generated by spf13/cobra on 7-Dec-2023

View file

@ -33,6 +33,6 @@ ficsit profile mods <profile> [flags]
### SEE ALSO ### SEE ALSO
* [ficsit profile](ficsit_profile.md) - Manage profiles - [ficsit profile](ficsit_profile.md) - Manage profiles
###### Auto generated by spf13/cobra on 7-Dec-2023 ###### Auto generated by spf13/cobra on 7-Dec-2023

View file

@ -33,6 +33,6 @@ ficsit profile new <name> [flags]
### SEE ALSO ### SEE ALSO
* [ficsit profile](ficsit_profile.md) - Manage profiles - [ficsit profile](ficsit_profile.md) - Manage profiles
###### Auto generated by spf13/cobra on 7-Dec-2023 ###### Auto generated by spf13/cobra on 7-Dec-2023

View file

@ -33,6 +33,6 @@ ficsit profile rename <old> <name> [flags]
### SEE ALSO ### SEE ALSO
* [ficsit profile](ficsit_profile.md) - Manage profiles - [ficsit profile](ficsit_profile.md) - Manage profiles
###### Auto generated by spf13/cobra on 7-Dec-2023 ###### Auto generated by spf13/cobra on 7-Dec-2023

View file

@ -38,6 +38,6 @@ ficsit search [query] [flags]
### SEE ALSO ### SEE ALSO
* [ficsit](ficsit.md) - cli mod manager for satisfactory - [ficsit](ficsit.md) - cli mod manager for satisfactory
###### Auto generated by spf13/cobra on 7-Dec-2023 ###### Auto generated by spf13/cobra on 7-Dec-2023

View file

@ -29,7 +29,7 @@ Manage mods on SMR
### SEE ALSO ### SEE ALSO
* [ficsit](ficsit.md) - cli mod manager for satisfactory - [ficsit](ficsit.md) - cli mod manager for satisfactory
* [ficsit smr upload](ficsit_smr_upload.md) - Upload a new mod version - [ficsit smr upload](ficsit_smr_upload.md) - Upload a new mod version
###### Auto generated by spf13/cobra on 7-Dec-2023 ###### Auto generated by spf13/cobra on 7-Dec-2023

View file

@ -35,6 +35,6 @@ ficsit smr upload [flags] <mod-id> <file> <changelog...>
### SEE ALSO ### SEE ALSO
* [ficsit smr](ficsit_smr.md) - Manage mods on SMR - [ficsit smr](ficsit_smr.md) - Manage mods on SMR
###### Auto generated by spf13/cobra on 7-Dec-2023 ###### Auto generated by spf13/cobra on 7-Dec-2023

View file

@ -33,6 +33,6 @@ ficsit version [flags]
### SEE ALSO ### SEE ALSO
* [ficsit](ficsit.md) - cli mod manager for satisfactory - [ficsit](ficsit.md) - cli mod manager for satisfactory
###### Auto generated by spf13/cobra on 7-Dec-2023 ###### Auto generated by spf13/cobra on 7-Dec-2023

BIN
ficsit-cli Executable file

Binary file not shown.

View file

@ -17,15 +17,6 @@ func init() {
client = InitAPI() client = InitAPI()
} }
func TestModVersions(t *testing.T) {
response, err := ModVersions(context.Background(), client, "SmartFoundations", VersionFilter{})
testza.AssertNoError(t, err)
testza.AssertNotNil(t, response)
testza.AssertNotNil(t, response.Mod)
testza.AssertNotNil(t, response.Mod.Versions)
testza.AssertNotZero(t, len(response.Mod.Versions))
}
func TestMods(t *testing.T) { func TestMods(t *testing.T) {
response, err := Mods(context.Background(), client, ModFilter{}) response, err := Mods(context.Background(), client, ModFilter{})
testza.AssertNoError(t, err) testza.AssertNoError(t, err)

View file

@ -1,13 +0,0 @@
# @genqlient(omitempty: true)
query ModVersions (
$modId: String!,
$filter: VersionFilter
) {
mod: getModByIdOrReference(modIdOrReference: $modId) {
id
versions (filter: $filter) {
id
version
}
}
}

View file

@ -1,24 +0,0 @@
# @genqlient(omitempty: true)
query ModVersionsWithDependencies (
$modId: String!,
) {
mod: getModByIdOrReference(modIdOrReference: $modId) {
id
versions (filter: { limit: 100 }) {
id
version
link
hash
dependencies {
mod_id
condition
optional
}
targets {
targetName
link
hash
}
}
}
}

View file

@ -1,15 +0,0 @@
# @genqlient(omitempty: true)
query SMLVersions {
smlVersions: getSMLVersions(filter: {limit: 100}) {
count
sml_versions {
id
version
satisfactory_version
targets {
targetName
link
}
}
}
}

View file

@ -355,136 +355,6 @@ func (v *ModFilter) GetHidden() bool { return v.Hidden }
// GetTagIDs returns ModFilter.TagIDs, and is useful for accessing the field via an interface. // GetTagIDs returns ModFilter.TagIDs, and is useful for accessing the field via an interface.
func (v *ModFilter) GetTagIDs() []string { return v.TagIDs } func (v *ModFilter) GetTagIDs() []string { return v.TagIDs }
// ModVersionsMod includes the requested fields of the GraphQL type Mod.
type ModVersionsMod struct {
Id string `json:"id"`
Versions []ModVersionsModVersionsVersion `json:"versions"`
}
// GetId returns ModVersionsMod.Id, and is useful for accessing the field via an interface.
func (v *ModVersionsMod) GetId() string { return v.Id }
// GetVersions returns ModVersionsMod.Versions, and is useful for accessing the field via an interface.
func (v *ModVersionsMod) GetVersions() []ModVersionsModVersionsVersion { return v.Versions }
// ModVersionsModVersionsVersion includes the requested fields of the GraphQL type Version.
type ModVersionsModVersionsVersion struct {
Id string `json:"id"`
Version string `json:"version"`
}
// GetId returns ModVersionsModVersionsVersion.Id, and is useful for accessing the field via an interface.
func (v *ModVersionsModVersionsVersion) GetId() string { return v.Id }
// GetVersion returns ModVersionsModVersionsVersion.Version, and is useful for accessing the field via an interface.
func (v *ModVersionsModVersionsVersion) GetVersion() string { return v.Version }
// ModVersionsResponse is returned by ModVersions on success.
type ModVersionsResponse struct {
Mod ModVersionsMod `json:"mod"`
}
// GetMod returns ModVersionsResponse.Mod, and is useful for accessing the field via an interface.
func (v *ModVersionsResponse) GetMod() ModVersionsMod { return v.Mod }
// ModVersionsWithDependenciesMod includes the requested fields of the GraphQL type Mod.
type ModVersionsWithDependenciesMod struct {
Id string `json:"id"`
Versions []ModVersionsWithDependenciesModVersionsVersion `json:"versions"`
}
// GetId returns ModVersionsWithDependenciesMod.Id, and is useful for accessing the field via an interface.
func (v *ModVersionsWithDependenciesMod) GetId() string { return v.Id }
// GetVersions returns ModVersionsWithDependenciesMod.Versions, and is useful for accessing the field via an interface.
func (v *ModVersionsWithDependenciesMod) GetVersions() []ModVersionsWithDependenciesModVersionsVersion {
return v.Versions
}
// ModVersionsWithDependenciesModVersionsVersion includes the requested fields of the GraphQL type Version.
type ModVersionsWithDependenciesModVersionsVersion struct {
Id string `json:"id"`
Version string `json:"version"`
Link string `json:"link"`
Hash string `json:"hash"`
Dependencies []ModVersionsWithDependenciesModVersionsVersionDependenciesVersionDependency `json:"dependencies"`
Targets []ModVersionsWithDependenciesModVersionsVersionTargetsVersionTarget `json:"targets"`
}
// GetId returns ModVersionsWithDependenciesModVersionsVersion.Id, and is useful for accessing the field via an interface.
func (v *ModVersionsWithDependenciesModVersionsVersion) GetId() string { return v.Id }
// GetVersion returns ModVersionsWithDependenciesModVersionsVersion.Version, and is useful for accessing the field via an interface.
func (v *ModVersionsWithDependenciesModVersionsVersion) GetVersion() string { return v.Version }
// GetLink returns ModVersionsWithDependenciesModVersionsVersion.Link, and is useful for accessing the field via an interface.
func (v *ModVersionsWithDependenciesModVersionsVersion) GetLink() string { return v.Link }
// GetHash returns ModVersionsWithDependenciesModVersionsVersion.Hash, and is useful for accessing the field via an interface.
func (v *ModVersionsWithDependenciesModVersionsVersion) GetHash() string { return v.Hash }
// GetDependencies returns ModVersionsWithDependenciesModVersionsVersion.Dependencies, and is useful for accessing the field via an interface.
func (v *ModVersionsWithDependenciesModVersionsVersion) GetDependencies() []ModVersionsWithDependenciesModVersionsVersionDependenciesVersionDependency {
return v.Dependencies
}
// GetTargets returns ModVersionsWithDependenciesModVersionsVersion.Targets, and is useful for accessing the field via an interface.
func (v *ModVersionsWithDependenciesModVersionsVersion) GetTargets() []ModVersionsWithDependenciesModVersionsVersionTargetsVersionTarget {
return v.Targets
}
// ModVersionsWithDependenciesModVersionsVersionDependenciesVersionDependency includes the requested fields of the GraphQL type VersionDependency.
type ModVersionsWithDependenciesModVersionsVersionDependenciesVersionDependency struct {
Mod_id string `json:"mod_id"`
Condition string `json:"condition"`
Optional bool `json:"optional"`
}
// GetMod_id returns ModVersionsWithDependenciesModVersionsVersionDependenciesVersionDependency.Mod_id, and is useful for accessing the field via an interface.
func (v *ModVersionsWithDependenciesModVersionsVersionDependenciesVersionDependency) GetMod_id() string {
return v.Mod_id
}
// GetCondition returns ModVersionsWithDependenciesModVersionsVersionDependenciesVersionDependency.Condition, and is useful for accessing the field via an interface.
func (v *ModVersionsWithDependenciesModVersionsVersionDependenciesVersionDependency) GetCondition() string {
return v.Condition
}
// GetOptional returns ModVersionsWithDependenciesModVersionsVersionDependenciesVersionDependency.Optional, and is useful for accessing the field via an interface.
func (v *ModVersionsWithDependenciesModVersionsVersionDependenciesVersionDependency) GetOptional() bool {
return v.Optional
}
// ModVersionsWithDependenciesModVersionsVersionTargetsVersionTarget includes the requested fields of the GraphQL type VersionTarget.
type ModVersionsWithDependenciesModVersionsVersionTargetsVersionTarget struct {
TargetName TargetName `json:"targetName"`
Link string `json:"link"`
Hash string `json:"hash"`
}
// GetTargetName returns ModVersionsWithDependenciesModVersionsVersionTargetsVersionTarget.TargetName, and is useful for accessing the field via an interface.
func (v *ModVersionsWithDependenciesModVersionsVersionTargetsVersionTarget) GetTargetName() TargetName {
return v.TargetName
}
// GetLink returns ModVersionsWithDependenciesModVersionsVersionTargetsVersionTarget.Link, and is useful for accessing the field via an interface.
func (v *ModVersionsWithDependenciesModVersionsVersionTargetsVersionTarget) GetLink() string {
return v.Link
}
// GetHash returns ModVersionsWithDependenciesModVersionsVersionTargetsVersionTarget.Hash, and is useful for accessing the field via an interface.
func (v *ModVersionsWithDependenciesModVersionsVersionTargetsVersionTarget) GetHash() string {
return v.Hash
}
// ModVersionsWithDependenciesResponse is returned by ModVersionsWithDependencies on success.
type ModVersionsWithDependenciesResponse struct {
Mod ModVersionsWithDependenciesMod `json:"mod"`
}
// GetMod returns ModVersionsWithDependenciesResponse.Mod, and is useful for accessing the field via an interface.
func (v *ModVersionsWithDependenciesResponse) GetMod() ModVersionsWithDependenciesMod { return v.Mod }
// ModsModsGetMods includes the requested fields of the GraphQL type GetMods. // ModsModsGetMods includes the requested fields of the GraphQL type GetMods.
type ModsModsGetMods struct { type ModsModsGetMods struct {
Count int `json:"count"` Count int `json:"count"`
@ -675,115 +545,6 @@ const (
OrderDesc Order = "desc" OrderDesc Order = "desc"
) )
// SMLVersionsResponse is returned by SMLVersions on success.
type SMLVersionsResponse struct {
SmlVersions SMLVersionsSmlVersionsGetSMLVersions `json:"smlVersions"`
}
// GetSmlVersions returns SMLVersionsResponse.SmlVersions, and is useful for accessing the field via an interface.
func (v *SMLVersionsResponse) GetSmlVersions() SMLVersionsSmlVersionsGetSMLVersions {
return v.SmlVersions
}
// SMLVersionsSmlVersionsGetSMLVersions includes the requested fields of the GraphQL type GetSMLVersions.
type SMLVersionsSmlVersionsGetSMLVersions struct {
Count int `json:"count"`
Sml_versions []SMLVersionsSmlVersionsGetSMLVersionsSml_versionsSMLVersion `json:"sml_versions"`
}
// GetCount returns SMLVersionsSmlVersionsGetSMLVersions.Count, and is useful for accessing the field via an interface.
func (v *SMLVersionsSmlVersionsGetSMLVersions) GetCount() int { return v.Count }
// GetSml_versions returns SMLVersionsSmlVersionsGetSMLVersions.Sml_versions, and is useful for accessing the field via an interface.
func (v *SMLVersionsSmlVersionsGetSMLVersions) GetSml_versions() []SMLVersionsSmlVersionsGetSMLVersionsSml_versionsSMLVersion {
return v.Sml_versions
}
// SMLVersionsSmlVersionsGetSMLVersionsSml_versionsSMLVersion includes the requested fields of the GraphQL type SMLVersion.
type SMLVersionsSmlVersionsGetSMLVersionsSml_versionsSMLVersion struct {
Id string `json:"id"`
Version string `json:"version"`
Satisfactory_version int `json:"satisfactory_version"`
Targets []SMLVersionsSmlVersionsGetSMLVersionsSml_versionsSMLVersionTargetsSMLVersionTarget `json:"targets"`
}
// GetId returns SMLVersionsSmlVersionsGetSMLVersionsSml_versionsSMLVersion.Id, and is useful for accessing the field via an interface.
func (v *SMLVersionsSmlVersionsGetSMLVersionsSml_versionsSMLVersion) GetId() string { return v.Id }
// GetVersion returns SMLVersionsSmlVersionsGetSMLVersionsSml_versionsSMLVersion.Version, and is useful for accessing the field via an interface.
func (v *SMLVersionsSmlVersionsGetSMLVersionsSml_versionsSMLVersion) GetVersion() string {
return v.Version
}
// GetSatisfactory_version returns SMLVersionsSmlVersionsGetSMLVersionsSml_versionsSMLVersion.Satisfactory_version, and is useful for accessing the field via an interface.
func (v *SMLVersionsSmlVersionsGetSMLVersionsSml_versionsSMLVersion) GetSatisfactory_version() int {
return v.Satisfactory_version
}
// GetTargets returns SMLVersionsSmlVersionsGetSMLVersionsSml_versionsSMLVersion.Targets, and is useful for accessing the field via an interface.
func (v *SMLVersionsSmlVersionsGetSMLVersionsSml_versionsSMLVersion) GetTargets() []SMLVersionsSmlVersionsGetSMLVersionsSml_versionsSMLVersionTargetsSMLVersionTarget {
return v.Targets
}
// SMLVersionsSmlVersionsGetSMLVersionsSml_versionsSMLVersionTargetsSMLVersionTarget includes the requested fields of the GraphQL type SMLVersionTarget.
type SMLVersionsSmlVersionsGetSMLVersionsSml_versionsSMLVersionTargetsSMLVersionTarget struct {
TargetName TargetName `json:"targetName"`
Link string `json:"link"`
}
// GetTargetName returns SMLVersionsSmlVersionsGetSMLVersionsSml_versionsSMLVersionTargetsSMLVersionTarget.TargetName, and is useful for accessing the field via an interface.
func (v *SMLVersionsSmlVersionsGetSMLVersionsSml_versionsSMLVersionTargetsSMLVersionTarget) GetTargetName() TargetName {
return v.TargetName
}
// GetLink returns SMLVersionsSmlVersionsGetSMLVersionsSml_versionsSMLVersionTargetsSMLVersionTarget.Link, and is useful for accessing the field via an interface.
func (v *SMLVersionsSmlVersionsGetSMLVersionsSml_versionsSMLVersionTargetsSMLVersionTarget) GetLink() string {
return v.Link
}
type TargetName string
const (
TargetNameWindows TargetName = "Windows"
TargetNameWindowsserver TargetName = "WindowsServer"
TargetNameLinuxserver TargetName = "LinuxServer"
)
type VersionFields string
const (
VersionFieldsCreatedAt VersionFields = "created_at"
VersionFieldsUpdatedAt VersionFields = "updated_at"
VersionFieldsDownloads VersionFields = "downloads"
)
type VersionFilter struct {
Limit int `json:"limit,omitempty"`
Offset int `json:"offset,omitempty"`
Order_by VersionFields `json:"order_by,omitempty"`
Order Order `json:"order,omitempty"`
Search string `json:"search,omitempty"`
Ids []string `json:"ids,omitempty"`
}
// GetLimit returns VersionFilter.Limit, and is useful for accessing the field via an interface.
func (v *VersionFilter) GetLimit() int { return v.Limit }
// GetOffset returns VersionFilter.Offset, and is useful for accessing the field via an interface.
func (v *VersionFilter) GetOffset() int { return v.Offset }
// GetOrder_by returns VersionFilter.Order_by, and is useful for accessing the field via an interface.
func (v *VersionFilter) GetOrder_by() VersionFields { return v.Order_by }
// GetOrder returns VersionFilter.Order, and is useful for accessing the field via an interface.
func (v *VersionFilter) GetOrder() Order { return v.Order }
// GetSearch returns VersionFilter.Search, and is useful for accessing the field via an interface.
func (v *VersionFilter) GetSearch() string { return v.Search }
// GetIds returns VersionFilter.Ids, and is useful for accessing the field via an interface.
func (v *VersionFilter) GetIds() []string { return v.Ids }
// VersionMod includes the requested fields of the GraphQL type Mod. // VersionMod includes the requested fields of the GraphQL type Mod.
type VersionMod struct { type VersionMod struct {
Id string `json:"id"` Id string `json:"id"`
@ -884,26 +645,6 @@ type __GetModNameInput struct {
// GetModId returns __GetModNameInput.ModId, and is useful for accessing the field via an interface. // GetModId returns __GetModNameInput.ModId, and is useful for accessing the field via an interface.
func (v *__GetModNameInput) GetModId() string { return v.ModId } func (v *__GetModNameInput) GetModId() string { return v.ModId }
// __ModVersionsInput is used internally by genqlient
type __ModVersionsInput struct {
ModId string `json:"modId,omitempty"`
Filter VersionFilter `json:"filter,omitempty"`
}
// GetModId returns __ModVersionsInput.ModId, and is useful for accessing the field via an interface.
func (v *__ModVersionsInput) GetModId() string { return v.ModId }
// GetFilter returns __ModVersionsInput.Filter, and is useful for accessing the field via an interface.
func (v *__ModVersionsInput) GetFilter() VersionFilter { return v.Filter }
// __ModVersionsWithDependenciesInput is used internally by genqlient
type __ModVersionsWithDependenciesInput struct {
ModId string `json:"modId,omitempty"`
}
// GetModId returns __ModVersionsWithDependenciesInput.ModId, and is useful for accessing the field via an interface.
func (v *__ModVersionsWithDependenciesInput) GetModId() string { return v.ModId }
// __ModsInput is used internally by genqlient // __ModsInput is used internally by genqlient
type __ModsInput struct { type __ModsInput struct {
Filter ModFilter `json:"filter,omitempty"` Filter ModFilter `json:"filter,omitempty"`
@ -1129,98 +870,6 @@ func GetModName(
return &data, err return &data, err
} }
// The query or mutation executed by ModVersions.
const ModVersions_Operation = `
query ModVersions ($modId: String!, $filter: VersionFilter) {
mod: getModByIdOrReference(modIdOrReference: $modId) {
id
versions(filter: $filter) {
id
version
}
}
}
`
func ModVersions(
ctx context.Context,
client graphql.Client,
modId string,
filter VersionFilter,
) (*ModVersionsResponse, error) {
req := &graphql.Request{
OpName: "ModVersions",
Query: ModVersions_Operation,
Variables: &__ModVersionsInput{
ModId: modId,
Filter: filter,
},
}
var err error
var data ModVersionsResponse
resp := &graphql.Response{Data: &data}
err = client.MakeRequest(
ctx,
req,
resp,
)
return &data, err
}
// The query or mutation executed by ModVersionsWithDependencies.
const ModVersionsWithDependencies_Operation = `
query ModVersionsWithDependencies ($modId: String!) {
mod: getModByIdOrReference(modIdOrReference: $modId) {
id
versions(filter: {limit:100}) {
id
version
link
hash
dependencies {
mod_id
condition
optional
}
targets {
targetName
link
hash
}
}
}
}
`
func ModVersionsWithDependencies(
ctx context.Context,
client graphql.Client,
modId string,
) (*ModVersionsWithDependenciesResponse, error) {
req := &graphql.Request{
OpName: "ModVersionsWithDependencies",
Query: ModVersionsWithDependencies_Operation,
Variables: &__ModVersionsWithDependenciesInput{
ModId: modId,
},
}
var err error
var data ModVersionsWithDependenciesResponse
resp := &graphql.Response{Data: &data}
err = client.MakeRequest(
ctx,
req,
resp,
)
return &data, err
}
// The query or mutation executed by Mods. // The query or mutation executed by Mods.
const Mods_Operation = ` const Mods_Operation = `
query Mods ($filter: ModFilter) { query Mods ($filter: ModFilter) {
@ -1267,46 +916,6 @@ func Mods(
return &data, err return &data, err
} }
// The query or mutation executed by SMLVersions.
const SMLVersions_Operation = `
query SMLVersions {
smlVersions: getSMLVersions(filter: {limit:100}) {
count
sml_versions {
id
version
satisfactory_version
targets {
targetName
link
}
}
}
}
`
func SMLVersions(
ctx context.Context,
client graphql.Client,
) (*SMLVersionsResponse, error) {
req := &graphql.Request{
OpName: "SMLVersions",
Query: SMLVersions_Operation,
}
var err error
var data SMLVersionsResponse
resp := &graphql.Response{Data: &data}
err = client.MakeRequest(
ctx,
req,
resp,
)
return &data, err
}
// The query or mutation executed by Version. // The query or mutation executed by Version.
const Version_Operation = ` const Version_Operation = `
query Version ($modId: String!, $version: String!) { query Version ($modId: String!, $version: String!) {

View file

@ -7,10 +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"`
Dependencies []Dependency `json:"dependencies"` GameVersion string `json:"game_version"`
Targets []Target `json:"targets"` Dependencies []Dependency `json:"dependencies"`
Targets []Target `json:"targets"`
RequiredOnRemote bool `json:"required_on_remote"`
} }
type Dependency struct { type Dependency struct {
@ -22,6 +24,7 @@ type Dependency struct {
type Target struct { type Target struct {
VersionID string `json:"version_id"` VersionID string `json:"version_id"`
TargetName string `json:"target_name"` TargetName string `json:"target_name"`
Link string `json:"link"`
Hash string `json:"hash"` Hash string `json:"hash"`
Size int64 `json:"size"` Size int64 `json:"size"`
} }

View file

@ -18,18 +18,396 @@
"type": "github" "type": "github"
} }
}, },
"nixpkgs": { "flake-utils_10": {
"locked": { "locked": {
"lastModified": 1700014976, "lastModified": 1653893745,
"narHash": "sha256-dSGpS2YeJrXW5aH9y7Abd235gGufY3RuZFth6vuyVtU=", "narHash": "sha256-0jntwV3Z8//YwuOjzhV2sgJJPt+HY6KhU7VZUL0fKZQ=",
"owner": "NixOS", "owner": "numtide",
"repo": "nixpkgs", "repo": "flake-utils",
"rev": "592047fc9e4f7b74a4dc85d1b9f5243dfe4899e3", "rev": "1ed9fb1935d260de5fe1c2f7ee0ebaae17ed2fa1",
"type": "github" "type": "github"
}, },
"original": { "original": {
"id": "nixpkgs", "owner": "numtide",
"type": "indirect" "repo": "flake-utils",
"type": "github"
}
},
"flake-utils_2": {
"locked": {
"lastModified": 1653893745,
"narHash": "sha256-0jntwV3Z8//YwuOjzhV2sgJJPt+HY6KhU7VZUL0fKZQ=",
"owner": "numtide",
"repo": "flake-utils",
"rev": "1ed9fb1935d260de5fe1c2f7ee0ebaae17ed2fa1",
"type": "github"
},
"original": {
"owner": "numtide",
"repo": "flake-utils",
"type": "github"
}
},
"flake-utils_3": {
"locked": {
"lastModified": 1653893745,
"narHash": "sha256-0jntwV3Z8//YwuOjzhV2sgJJPt+HY6KhU7VZUL0fKZQ=",
"owner": "numtide",
"repo": "flake-utils",
"rev": "1ed9fb1935d260de5fe1c2f7ee0ebaae17ed2fa1",
"type": "github"
},
"original": {
"owner": "numtide",
"repo": "flake-utils",
"type": "github"
}
},
"flake-utils_4": {
"locked": {
"lastModified": 1653893745,
"narHash": "sha256-0jntwV3Z8//YwuOjzhV2sgJJPt+HY6KhU7VZUL0fKZQ=",
"owner": "numtide",
"repo": "flake-utils",
"rev": "1ed9fb1935d260de5fe1c2f7ee0ebaae17ed2fa1",
"type": "github"
},
"original": {
"owner": "numtide",
"repo": "flake-utils",
"type": "github"
}
},
"flake-utils_5": {
"locked": {
"lastModified": 1653893745,
"narHash": "sha256-0jntwV3Z8//YwuOjzhV2sgJJPt+HY6KhU7VZUL0fKZQ=",
"owner": "numtide",
"repo": "flake-utils",
"rev": "1ed9fb1935d260de5fe1c2f7ee0ebaae17ed2fa1",
"type": "github"
},
"original": {
"owner": "numtide",
"repo": "flake-utils",
"type": "github"
}
},
"flake-utils_6": {
"locked": {
"lastModified": 1653893745,
"narHash": "sha256-0jntwV3Z8//YwuOjzhV2sgJJPt+HY6KhU7VZUL0fKZQ=",
"owner": "numtide",
"repo": "flake-utils",
"rev": "1ed9fb1935d260de5fe1c2f7ee0ebaae17ed2fa1",
"type": "github"
},
"original": {
"owner": "numtide",
"repo": "flake-utils",
"type": "github"
}
},
"flake-utils_7": {
"locked": {
"lastModified": 1653893745,
"narHash": "sha256-0jntwV3Z8//YwuOjzhV2sgJJPt+HY6KhU7VZUL0fKZQ=",
"owner": "numtide",
"repo": "flake-utils",
"rev": "1ed9fb1935d260de5fe1c2f7ee0ebaae17ed2fa1",
"type": "github"
},
"original": {
"owner": "numtide",
"repo": "flake-utils",
"type": "github"
}
},
"flake-utils_8": {
"locked": {
"lastModified": 1653893745,
"narHash": "sha256-0jntwV3Z8//YwuOjzhV2sgJJPt+HY6KhU7VZUL0fKZQ=",
"owner": "numtide",
"repo": "flake-utils",
"rev": "1ed9fb1935d260de5fe1c2f7ee0ebaae17ed2fa1",
"type": "github"
},
"original": {
"owner": "numtide",
"repo": "flake-utils",
"type": "github"
}
},
"flake-utils_9": {
"locked": {
"lastModified": 1653893745,
"narHash": "sha256-0jntwV3Z8//YwuOjzhV2sgJJPt+HY6KhU7VZUL0fKZQ=",
"owner": "numtide",
"repo": "flake-utils",
"rev": "1ed9fb1935d260de5fe1c2f7ee0ebaae17ed2fa1",
"type": "github"
},
"original": {
"owner": "numtide",
"repo": "flake-utils",
"type": "github"
}
},
"mfgames-project-setup": {
"inputs": {
"nixago": "nixago",
"nixago-exts": "nixago-exts_3",
"nixpkgs": "nixpkgs"
},
"locked": {
"lastModified": 1723051996,
"narHash": "sha256-pcWXdsdAbD0U9b93V/I+eJREbgZ5TWYkFc26gmSz46E=",
"ref": "refs/heads/main",
"rev": "f378dedc71e3b2b82f95041069bec56fce9e9524",
"revCount": 29,
"type": "git",
"url": "https://src.mfgames.com/nixos-contrib/mfgames-project-setup-flake.git"
},
"original": {
"type": "git",
"url": "https://src.mfgames.com/nixos-contrib/mfgames-project-setup-flake.git"
}
},
"nixago": {
"inputs": {
"flake-utils": "flake-utils_2",
"nixago-exts": "nixago-exts",
"nixpkgs": [
"mfgames-project-setup",
"nixpkgs"
]
},
"locked": {
"lastModified": 1687381756,
"narHash": "sha256-IUMIlYfrvj7Yli4H2vvyig8HEPpfCeMaE6+kBGPzFyk=",
"owner": "jmgilman",
"repo": "nixago",
"rev": "dacceb10cace103b3e66552ec9719fa0d33c0dc9",
"type": "github"
},
"original": {
"owner": "jmgilman",
"repo": "nixago",
"type": "github"
}
},
"nixago-exts": {
"inputs": {
"flake-utils": "flake-utils_3",
"nixago": "nixago_2",
"nixpkgs": [
"mfgames-project-setup",
"nixago",
"nixpkgs"
]
},
"locked": {
"lastModified": 1676070308,
"narHash": "sha256-QaJ65oc2l8iwQIGWUJ0EKjCeSuuCM/LqR8RauxZUUkc=",
"owner": "nix-community",
"repo": "nixago-extensions",
"rev": "e5380cb0456f4ea3c86cf94e3039eb856bf07d0b",
"type": "github"
},
"original": {
"owner": "nix-community",
"repo": "nixago-extensions",
"type": "github"
}
},
"nixago-exts_2": {
"inputs": {
"flake-utils": "flake-utils_5",
"nixago": "nixago_3",
"nixpkgs": [
"mfgames-project-setup",
"nixago",
"nixago-exts",
"nixago",
"nixpkgs"
]
},
"locked": {
"lastModified": 1655508669,
"narHash": "sha256-BDDdo5dZQMmwNH/GNacy33nPBnCpSIydWFPZs0kkj/g=",
"owner": "nix-community",
"repo": "nixago-extensions",
"rev": "3022a932ce109258482ecc6568c163e8d0b426aa",
"type": "github"
},
"original": {
"owner": "nix-community",
"repo": "nixago-extensions",
"type": "github"
}
},
"nixago-exts_3": {
"inputs": {
"flake-utils": "flake-utils_7",
"nixago": "nixago_4",
"nixpkgs": [
"mfgames-project-setup",
"nixpkgs"
]
},
"locked": {
"lastModified": 1676070308,
"narHash": "sha256-QaJ65oc2l8iwQIGWUJ0EKjCeSuuCM/LqR8RauxZUUkc=",
"owner": "nix-community",
"repo": "nixago-extensions",
"rev": "e5380cb0456f4ea3c86cf94e3039eb856bf07d0b",
"type": "github"
},
"original": {
"owner": "nix-community",
"repo": "nixago-extensions",
"type": "github"
}
},
"nixago-exts_4": {
"inputs": {
"flake-utils": "flake-utils_9",
"nixago": "nixago_5",
"nixpkgs": [
"mfgames-project-setup",
"nixago-exts",
"nixago",
"nixpkgs"
]
},
"locked": {
"lastModified": 1655508669,
"narHash": "sha256-BDDdo5dZQMmwNH/GNacy33nPBnCpSIydWFPZs0kkj/g=",
"owner": "nix-community",
"repo": "nixago-extensions",
"rev": "3022a932ce109258482ecc6568c163e8d0b426aa",
"type": "github"
},
"original": {
"owner": "nix-community",
"repo": "nixago-extensions",
"type": "github"
}
},
"nixago_2": {
"inputs": {
"flake-utils": "flake-utils_4",
"nixago-exts": "nixago-exts_2",
"nixpkgs": [
"mfgames-project-setup",
"nixago",
"nixago-exts",
"nixpkgs"
]
},
"locked": {
"lastModified": 1676070010,
"narHash": "sha256-iYzJIWptE1EUD8VINAg66AAMUajizg8JUYN3oBmb8no=",
"owner": "nix-community",
"repo": "nixago",
"rev": "d480ba6c0c16e2c5c0bd2122852d6a0c9ad1ed0e",
"type": "github"
},
"original": {
"owner": "nix-community",
"ref": "rename-config-data",
"repo": "nixago",
"type": "github"
}
},
"nixago_3": {
"inputs": {
"flake-utils": "flake-utils_6",
"nixpkgs": [
"mfgames-project-setup",
"nixago",
"nixago-exts",
"nixago",
"nixago-exts",
"nixpkgs"
]
},
"locked": {
"lastModified": 1655405483,
"narHash": "sha256-Crd49aZWNrpczlRTOwWGfwBMsTUoG9vlHDKQC7cx264=",
"owner": "nix-community",
"repo": "nixago",
"rev": "e6a9566c18063db5b120e69e048d3627414e327d",
"type": "github"
},
"original": {
"owner": "nix-community",
"repo": "nixago",
"type": "github"
}
},
"nixago_4": {
"inputs": {
"flake-utils": "flake-utils_8",
"nixago-exts": "nixago-exts_4",
"nixpkgs": [
"mfgames-project-setup",
"nixago-exts",
"nixpkgs"
]
},
"locked": {
"lastModified": 1676070010,
"narHash": "sha256-iYzJIWptE1EUD8VINAg66AAMUajizg8JUYN3oBmb8no=",
"owner": "nix-community",
"repo": "nixago",
"rev": "d480ba6c0c16e2c5c0bd2122852d6a0c9ad1ed0e",
"type": "github"
},
"original": {
"owner": "nix-community",
"ref": "rename-config-data",
"repo": "nixago",
"type": "github"
}
},
"nixago_5": {
"inputs": {
"flake-utils": "flake-utils_10",
"nixpkgs": [
"mfgames-project-setup",
"nixago-exts",
"nixago",
"nixago-exts",
"nixpkgs"
]
},
"locked": {
"lastModified": 1655405483,
"narHash": "sha256-Crd49aZWNrpczlRTOwWGfwBMsTUoG9vlHDKQC7cx264=",
"owner": "nix-community",
"repo": "nixago",
"rev": "e6a9566c18063db5b120e69e048d3627414e327d",
"type": "github"
},
"original": {
"owner": "nix-community",
"repo": "nixago",
"type": "github"
}
},
"nixpkgs": {
"locked": {
"lastModified": 1706098335,
"narHash": "sha256-r3dWjT8P9/Ah5m5ul4WqIWD8muj5F+/gbCdjiNVBKmU=",
"rev": "a77ab169a83a4175169d78684ddd2e54486ac651",
"revCount": 554858,
"type": "tarball",
"url": "https://api.flakehub.com/f/pinned/NixOS/nixpkgs/0.2311.554858%2Brev-a77ab169a83a4175169d78684ddd2e54486ac651/018d46f0-798f-71dc-a8c5-4689c46f7d12/source.tar.gz"
},
"original": {
"type": "tarball",
"url": "https://flakehub.com/f/NixOS/nixpkgs/%2A.tar.gz"
} }
}, },
"nixpkgs-unstable": { "nixpkgs-unstable": {
@ -47,10 +425,25 @@
"type": "indirect" "type": "indirect"
} }
}, },
"nixpkgs_2": {
"locked": {
"lastModified": 1700014976,
"narHash": "sha256-dSGpS2YeJrXW5aH9y7Abd235gGufY3RuZFth6vuyVtU=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "592047fc9e4f7b74a4dc85d1b9f5243dfe4899e3",
"type": "github"
},
"original": {
"id": "nixpkgs",
"type": "indirect"
}
},
"root": { "root": {
"inputs": { "inputs": {
"flake-utils": "flake-utils", "flake-utils": "flake-utils",
"nixpkgs": "nixpkgs", "mfgames-project-setup": "mfgames-project-setup",
"nixpkgs": "nixpkgs_2",
"nixpkgs-unstable": "nixpkgs-unstable" "nixpkgs-unstable": "nixpkgs-unstable"
} }
}, },

View file

@ -1,19 +1,35 @@
{ {
description = "smr-cli"; description = "ficsit-cli";
inputs = { inputs = {
flake-utils.url = "github:numtide/flake-utils"; flake-utils.url = "github:numtide/flake-utils";
nixpkgs-unstable.url = "flake:nixpkgs/nixpkgs-unstable"; nixpkgs-unstable.url = "flake:nixpkgs/nixpkgs-unstable";
mfgames-project-setup.url = "git+https://src.mfgames.com/nixos-contrib/mfgames-project-setup-flake.git";
}; };
outputs = { self, nixpkgs, flake-utils, nixpkgs-unstable }: outputs = { self, nixpkgs, flake-utils, nixpkgs-unstable, mfgames-project-setup }:
flake-utils.lib.eachDefaultSystem flake-utils.lib.eachDefaultSystem
(system: (system:
let let
pkgs = nixpkgs.legacyPackages.${system}; pkgs = nixpkgs.legacyPackages.${system};
unstable = nixpkgs-unstable.legacyPackages.${system}; in unstable = nixpkgs-unstable.legacyPackages.${system};
in
{ {
devShells.default = import ./shell.nix { inherit pkgs unstable; }; devShells.default = import ./shell.nix { inherit system pkgs unstable mfgames-project-setup; };
defaultPackage = pkgs.buildGoModule {
pname = "ficsit-cli";
version = "0.6.0";
doCheck = false; # Tests are failing in this flake.
vendorHash = "sha256-vmA3jvxOLRYj5BmvWMhSEnCTEoe8BLm8lpm2kruIEv4="; #pkgs.lib.fakeHash;
src = pkgs.fetchFromGitHub {
owner = "satisfactorymodding";
repo = "ficsit-cli";
rev = "v0.6.0";
hash = "sha256-Zwidx0war3hos9NEmk9dEzPBgDGdUtWvZb7FIF5OZMA="; #pkgs.lib.fakeHash;
};
};
} }
); );
} }

View file

@ -1,7 +1,7 @@
# https://github.com/Khan/genqlient/blob/main/docs/genqlient.yaml # https://github.com/Khan/genqlient/blob/main/docs/genqlient.yaml
schema: schema.graphql schema: schema.graphql
operations: operations:
- ficsit/queries/*.graphql - ficsit/queries/*.graphql
generated: ficsit/types.go generated: ficsit/types.go
package: ficsit package: ficsit
bindings: bindings:
@ -17,10 +17,8 @@ bindings:
type: string type: string
GuideID: GuideID:
type: string type: string
SMLVersionID:
type: string
Date: Date:
type: time.Time type: time.Time
unmarshaler: github.com/satisfactorymodding/ficsit-cli/ficsit/utils.UnmarshalDateTime unmarshaler: github.com/satisfactorymodding/ficsit-cli/ficsit/utils.UnmarshalDateTime
TagID: TagID:
type: string type: string

29
go.mod
View file

@ -19,18 +19,20 @@ require (
github.com/jackc/puddle/v2 v2.2.1 github.com/jackc/puddle/v2 v2.2.1
github.com/jlaffaye/ftp v0.2.0 github.com/jlaffaye/ftp v0.2.0
github.com/lmittmann/tint v1.0.3 github.com/lmittmann/tint v1.0.3
github.com/mircearoata/pubgrub-go v0.3.3
github.com/muesli/reflow v0.3.0 github.com/muesli/reflow v0.3.0
github.com/pkg/sftp v1.13.6 github.com/pkg/sftp v1.13.6
github.com/pterm/pterm v0.12.71 github.com/pterm/pterm v0.12.71
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.2 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
golang.org/x/crypto v0.16.0 golang.org/x/crypto v0.21.0
golang.org/x/sync v0.5.0 golang.org/x/sync v0.6.0
modernc.org/sqlite v1.32.0
) )
require ( require (
@ -54,10 +56,12 @@ require (
github.com/dlclark/regexp2 v1.10.0 // indirect github.com/dlclark/regexp2 v1.10.0 // indirect
github.com/fsnotify/fsnotify v1.7.0 // indirect github.com/fsnotify/fsnotify v1.7.0 // indirect
github.com/google/go-cmp v0.6.0 // indirect github.com/google/go-cmp v0.6.0 // indirect
github.com/google/uuid v1.6.0 // indirect
github.com/gookit/color v1.5.4 // indirect github.com/gookit/color v1.5.4 // indirect
github.com/gorilla/css v1.0.1 // indirect github.com/gorilla/css v1.0.1 // indirect
github.com/hashicorp/errwrap v1.1.0 // indirect github.com/hashicorp/errwrap v1.1.0 // indirect
github.com/hashicorp/go-multierror v1.1.1 // indirect github.com/hashicorp/go-multierror v1.1.1 // indirect
github.com/hashicorp/golang-lru/v2 v2.0.7 // indirect
github.com/hashicorp/hcl v1.0.0 // indirect github.com/hashicorp/hcl v1.0.0 // indirect
github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect
github.com/klauspost/cpuid/v2 v2.2.6 // indirect github.com/klauspost/cpuid/v2 v2.2.6 // indirect
@ -69,14 +73,15 @@ require (
github.com/mattn/go-localereader v0.0.1 // indirect github.com/mattn/go-localereader v0.0.1 // indirect
github.com/mattn/go-runewidth v0.0.15 // indirect github.com/mattn/go-runewidth v0.0.15 // indirect
github.com/microcosm-cc/bluemonday v1.0.26 // indirect github.com/microcosm-cc/bluemonday v1.0.26 // indirect
github.com/mircearoata/pubgrub-go v0.3.3 // indirect
github.com/mitchellh/mapstructure v1.5.0 // indirect github.com/mitchellh/mapstructure v1.5.0 // indirect
github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 // indirect github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 // indirect
github.com/muesli/cancelreader v0.2.2 // indirect github.com/muesli/cancelreader v0.2.2 // indirect
github.com/muesli/termenv v0.15.2 // indirect github.com/muesli/termenv v0.15.2 // indirect
github.com/ncruces/go-strftime v0.1.9 // indirect
github.com/olekukonko/tablewriter v0.0.5 // indirect github.com/olekukonko/tablewriter v0.0.5 // indirect
github.com/pelletier/go-toml/v2 v2.1.0 // indirect github.com/pelletier/go-toml/v2 v2.1.0 // indirect
github.com/pkg/errors v0.9.1 // indirect github.com/pkg/errors v0.9.1 // indirect
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
github.com/rivo/uniseg v0.4.4 // indirect github.com/rivo/uniseg v0.4.4 // indirect
github.com/russross/blackfriday/v2 v2.1.0 // indirect github.com/russross/blackfriday/v2 v2.1.0 // indirect
github.com/sagikazarmark/locafero v0.4.0 // indirect github.com/sagikazarmark/locafero v0.4.0 // indirect
@ -94,13 +99,19 @@ require (
github.com/yuin/goldmark-emoji v1.0.2 // indirect github.com/yuin/goldmark-emoji v1.0.2 // indirect
go.uber.org/multierr v1.11.0 // indirect go.uber.org/multierr v1.11.0 // indirect
golang.org/x/exp v0.0.0-20231206192017-f3f8817b8deb // indirect golang.org/x/exp v0.0.0-20231206192017-f3f8817b8deb // indirect
golang.org/x/mod v0.14.0 // indirect golang.org/x/mod v0.16.0 // indirect
golang.org/x/net v0.19.0 // indirect golang.org/x/net v0.22.0 // indirect
golang.org/x/sys v0.15.0 // indirect golang.org/x/sys v0.22.0 // indirect
golang.org/x/term v0.15.0 // indirect golang.org/x/term v0.18.0 // indirect
golang.org/x/text v0.14.0 // indirect golang.org/x/text v0.14.0 // indirect
golang.org/x/tools v0.16.0 // indirect golang.org/x/tools v0.19.0 // indirect
gopkg.in/ini.v1 v1.67.0 // indirect gopkg.in/ini.v1 v1.67.0 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect
modernc.org/gc/v3 v3.0.0-20240107210532-573471604cb6 // indirect
modernc.org/libc v1.55.3 // indirect
modernc.org/mathutil v1.6.0 // indirect
modernc.org/memory v1.8.0 // indirect
modernc.org/strutil v1.2.0 // indirect
modernc.org/token v1.1.0 // indirect
) )

68
go.sum
View file

@ -86,6 +86,10 @@ github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nos
github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM= github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM=
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/pprof v0.0.0-20240409012703-83162a5b38cd h1:gbpYu9NMq8jhDVbvlGkMFWCjLFlqqEZjEmObmhUy6Vo=
github.com/google/pprof v0.0.0-20240409012703-83162a5b38cd/go.mod h1:kf6iHlnVGwgKolg33glAes7Yg/8iWP8ukqeldJSO7jw=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/gookit/color v1.4.2/go.mod h1:fqRyamkC1W8uxl+lxCQxOT09l/vYfZ+QeiX3rKQHCoQ= github.com/gookit/color v1.4.2/go.mod h1:fqRyamkC1W8uxl+lxCQxOT09l/vYfZ+QeiX3rKQHCoQ=
github.com/gookit/color v1.5.0/go.mod h1:43aQb+Zerm/BWh2GnrgOQm7ffz7tvQXEKV6BFMl7wAo= github.com/gookit/color v1.5.0/go.mod h1:43aQb+Zerm/BWh2GnrgOQm7ffz7tvQXEKV6BFMl7wAo=
github.com/gookit/color v1.5.4 h1:FZmqs7XOyGgCAxmWyPslpiok1k05wmY3SJTytgvYFs0= github.com/gookit/color v1.5.4 h1:FZmqs7XOyGgCAxmWyPslpiok1k05wmY3SJTytgvYFs0=
@ -99,6 +103,8 @@ github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY
github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo= github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo=
github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM= github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM=
github.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k=
github.com/hashicorp/golang-lru/v2 v2.0.7/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM=
github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4=
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
@ -164,6 +170,8 @@ github.com/muesli/reflow v0.3.0/go.mod h1:pbwTDkVPibjO2kyvBQRBxTWEEGDGq0FlB1BIKt
github.com/muesli/termenv v0.13.0/go.mod h1:sP1+uffeLaEYpyOTb8pLCUctGcGLnoFjSn4YJK5e2bc= github.com/muesli/termenv v0.13.0/go.mod h1:sP1+uffeLaEYpyOTb8pLCUctGcGLnoFjSn4YJK5e2bc=
github.com/muesli/termenv v0.15.2 h1:GohcuySI0QmI3wN8Ok9PtKGkgkFIk7y6Vpb5PvrY+Wo= github.com/muesli/termenv v0.15.2 h1:GohcuySI0QmI3wN8Ok9PtKGkgkFIk7y6Vpb5PvrY+Wo=
github.com/muesli/termenv v0.15.2/go.mod h1:Epx+iuz8sNs7mNKhxzH4fWXGNpZwUaJKRS1noLXviQ8= github.com/muesli/termenv v0.15.2/go.mod h1:Epx+iuz8sNs7mNKhxzH4fWXGNpZwUaJKRS1noLXviQ8=
github.com/ncruces/go-strftime v0.1.9 h1:bY0MQC28UADQmHmaF5dgpLmImcShSi2kHU9XLdhx/f4=
github.com/ncruces/go-strftime v0.1.9/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJmn9CehxcKcls=
github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec= github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec=
github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY= github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY=
github.com/pelletier/go-toml/v2 v2.1.0 h1:FnwAJ4oYMvbT/34k9zzHuZNrhlz48GB3/s6at6/MHO4= github.com/pelletier/go-toml/v2 v2.1.0 h1:FnwAJ4oYMvbT/34k9zzHuZNrhlz48GB3/s6at6/MHO4=
@ -187,6 +195,8 @@ github.com/pterm/pterm v0.12.71 h1:KcEJ98EiVCbzDkFbktJ2gMlr4pn8IzyGb9bwK6ffkuA=
github.com/pterm/pterm v0.12.71/go.mod h1:SUAcoZjRt+yjPWlWba+/Fd8zJJ2lSXBQWf0Z0HbFiIQ= github.com/pterm/pterm v0.12.71/go.mod h1:SUAcoZjRt+yjPWlWba+/Fd8zJJ2lSXBQWf0Z0HbFiIQ=
github.com/puzpuzpuz/xsync/v3 v3.0.2 h1:3yESHrRFYr6xzkz61LLkvNiPFXxJEAABanTQpKbAaew= github.com/puzpuzpuz/xsync/v3 v3.0.2 h1:3yESHrRFYr6xzkz61LLkvNiPFXxJEAABanTQpKbAaew=
github.com/puzpuzpuz/xsync/v3 v3.0.2/go.mod h1:VjzYrABPabuM4KyBh1Ftq6u8nhwY5tBPKP9jpmh0nnA= github.com/puzpuzpuz/xsync/v3 v3.0.2/go.mod h1:VjzYrABPabuM4KyBh1Ftq6u8nhwY5tBPKP9jpmh0nnA=
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE=
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
github.com/rivo/uniseg v0.4.4 h1:8TfxU8dW6PdqD27gjM8MVNuicgxIjxpm4K7x4jp8sis= github.com/rivo/uniseg v0.4.4 h1:8TfxU8dW6PdqD27gjM8MVNuicgxIjxpm4K7x4jp8sis=
@ -205,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.2 h1:dj/OsDLpaMUqCHpfBVHvDMUv2nf5gT4HS2ydBMkmtcQ= github.com/satisfactorymodding/ficsit-resolver v0.0.6 h1:4iCIHOg3z+AvwSVeWtu+k9aysLOL9+FIszCbiKOG2oo=
github.com/satisfactorymodding/ficsit-resolver v0.0.2/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=
@ -267,14 +277,14 @@ golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8U
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.1.0/go.mod h1:RecgLatLF4+eUMCP1PoPZQb+cVrJcOPbHkTkbkB9sbw= golang.org/x/crypto v0.1.0/go.mod h1:RecgLatLF4+eUMCP1PoPZQb+cVrJcOPbHkTkbkB9sbw=
golang.org/x/crypto v0.15.0/go.mod h1:4ChreQoLWfG3xLDer1WdlH5NdlQ3+mwnQq1YTKY+72g= golang.org/x/crypto v0.15.0/go.mod h1:4ChreQoLWfG3xLDer1WdlH5NdlQ3+mwnQq1YTKY+72g=
golang.org/x/crypto v0.16.0 h1:mMMrFzRSCF0GvB7Ne27XVtVAaXLrPmgPC7/v0tkwHaY= golang.org/x/crypto v0.21.0 h1:X31++rzVUdKhX5sWmSOFZxx8UW/ldWx55cbf08iNAMA=
golang.org/x/crypto v0.16.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4= golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs=
golang.org/x/exp v0.0.0-20231206192017-f3f8817b8deb h1:c0vyKkb6yr3KR7jEfJaOSv4lG7xPkbN6r52aJz1d8a8= golang.org/x/exp v0.0.0-20231206192017-f3f8817b8deb h1:c0vyKkb6yr3KR7jEfJaOSv4lG7xPkbN6r52aJz1d8a8=
golang.org/x/exp v0.0.0-20231206192017-f3f8817b8deb/go.mod h1:iRJReGqOEeBhDZGkGbynYwcHlctCvnjTYIamk7uXpHI= golang.org/x/exp v0.0.0-20231206192017-f3f8817b8deb/go.mod h1:iRJReGqOEeBhDZGkGbynYwcHlctCvnjTYIamk7uXpHI=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/mod v0.14.0 h1:dGoOF9QVLYng8IHTm7BAyWqCqSheQ5pYWGhzW00YJr0= golang.org/x/mod v0.16.0 h1:QX4fJ0Rr5cPQCF7O9lh9Se4pmwfwskqZfq5moyldzic=
golang.org/x/mod v0.14.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= golang.org/x/mod v0.16.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190522155817-f3200d17e092/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= golang.org/x/net v0.0.0-20190522155817-f3200d17e092/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
@ -290,13 +300,13 @@ golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
golang.org/x/net v0.9.0/go.mod h1:d48xBJpPfHeWQsugry2m+kC02ZBRGRgulfHnEXEuWns= golang.org/x/net v0.9.0/go.mod h1:d48xBJpPfHeWQsugry2m+kC02ZBRGRgulfHnEXEuWns=
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
golang.org/x/net v0.18.0/go.mod h1:/czyP5RqHAH4odGYxBJ1qz0+CE5WZ+2j1YgoEo8F2jQ= golang.org/x/net v0.18.0/go.mod h1:/czyP5RqHAH4odGYxBJ1qz0+CE5WZ+2j1YgoEo8F2jQ=
golang.org/x/net v0.19.0 h1:zTwKpTd2XuCqf8huc7Fo2iSy+4RHPd10s4KzeTnVr1c= golang.org/x/net v0.22.0 h1:9sGLhx7iRIHEiX0oAJ3MRZMUCElJgy7Br1nO+AMN3Tc=
golang.org/x/net v0.19.0/go.mod h1:CfAk/cbD4CthTvqiEl8NpboMuiuOYsAr/7NOjZJtv1U= golang.org/x/net v0.22.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.5.0 h1:60k92dhOjHxJkrqnwsfl8KuaHbn/5dl0lUPUklKo3qE= golang.org/x/sync v0.6.0 h1:5BMeUDZ7vkXGfEr1x9B4bRcTH4lpkTkpdh0T/J+qjbQ=
golang.org/x/sync v0.5.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@ -318,8 +328,8 @@ golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.14.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.14.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.15.0 h1:h48lPFYpsTvQJZF4EKyI4aLHaev3CxivZmv7yZig9pc= golang.org/x/sys v0.22.0 h1:RI27ohtqKCnwULzJLqkv897zojh5/DwS/ENaMzUOaWI=
golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.22.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210220032956-6a3ed077a48d/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210220032956-6a3ed077a48d/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210615171337-6886f2dfbf5b/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.0.0-20210615171337-6886f2dfbf5b/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
@ -329,8 +339,8 @@ golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
golang.org/x/term v0.7.0/go.mod h1:P32HKFT3hSsZrRxla30E9HqToFYAQPCMs/zFMBUFqPY= golang.org/x/term v0.7.0/go.mod h1:P32HKFT3hSsZrRxla30E9HqToFYAQPCMs/zFMBUFqPY=
golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
golang.org/x/term v0.14.0/go.mod h1:TySc+nGkYR6qt8km8wUhuFRTVSMIX3XPR58y2lC8vww= golang.org/x/term v0.14.0/go.mod h1:TySc+nGkYR6qt8km8wUhuFRTVSMIX3XPR58y2lC8vww=
golang.org/x/term v0.15.0 h1:y/Oo/a/q3IXu26lQgl04j/gjuBDOBlx7X6Om1j2CPW4= golang.org/x/term v0.18.0 h1:FcHjZXDMxI8mM3nwhX9HlKop4C0YQvCVCdwYl2wOtE8=
golang.org/x/term v0.15.0/go.mod h1:BDl952bC7+uMoWR75FIrCDx79TPU9oHkTZ9yRbYOrX0= golang.org/x/term v0.18.0/go.mod h1:ILwASektA3OnRv7amZ1xhE/KTR+u50pbXfZ03+6Nx58=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
@ -346,8 +356,8 @@ golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
golang.org/x/tools v0.16.0 h1:GO788SKMRunPIBCXiQyo2AaexLstOrVhuAL5YwsckQM= golang.org/x/tools v0.19.0 h1:tfGCXNR1OsFG+sVdLAitlpjAvD/I6dHDKnYrpEZUHkw=
golang.org/x/tools v0.16.0/go.mod h1:kYVVN6I1mBNoB1OX+noeBjbRk4IUEPa7JJ+TJMEooJ0= golang.org/x/tools v0.19.0/go.mod h1:qoJWxmGSIBmAeriMx19ogtrEPrGtDbPK634QFIcLAhc=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo=
@ -363,3 +373,29 @@ gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
modernc.org/cc/v4 v4.21.4 h1:3Be/Rdo1fpr8GrQ7IVw9OHtplU4gWbb+wNgeoBMmGLQ=
modernc.org/cc/v4 v4.21.4/go.mod h1:HM7VJTZbUCR3rV8EYBi9wxnJ0ZBRiGE5OeGXNA0IsLQ=
modernc.org/ccgo/v4 v4.19.2 h1:lwQZgvboKD0jBwdaeVCTouxhxAyN6iawF3STraAal8Y=
modernc.org/ccgo/v4 v4.19.2/go.mod h1:ysS3mxiMV38XGRTTcgo0DQTeTmAO4oCmJl1nX9VFI3s=
modernc.org/fileutil v1.3.0 h1:gQ5SIzK3H9kdfai/5x41oQiKValumqNTDXMvKo62HvE=
modernc.org/fileutil v1.3.0/go.mod h1:XatxS8fZi3pS8/hKG2GH/ArUogfxjpEKs3Ku3aK4JyQ=
modernc.org/gc/v2 v2.4.1 h1:9cNzOqPyMJBvrUipmynX0ZohMhcxPtMccYgGOJdOiBw=
modernc.org/gc/v2 v2.4.1/go.mod h1:wzN5dK1AzVGoH6XOzc3YZ+ey/jPgYHLuVckd62P0GYU=
modernc.org/gc/v3 v3.0.0-20240107210532-573471604cb6 h1:5D53IMaUuA5InSeMu9eJtlQXS2NxAhyWQvkKEgXZhHI=
modernc.org/gc/v3 v3.0.0-20240107210532-573471604cb6/go.mod h1:Qz0X07sNOR1jWYCrJMEnbW/X55x206Q7Vt4mz6/wHp4=
modernc.org/libc v1.55.3 h1:AzcW1mhlPNrRtjS5sS+eW2ISCgSOLLNyFzRh/V3Qj/U=
modernc.org/libc v1.55.3/go.mod h1:qFXepLhz+JjFThQ4kzwzOjA/y/artDeg+pcYnY+Q83w=
modernc.org/mathutil v1.6.0 h1:fRe9+AmYlaej+64JsEEhoWuAYBkOtQiMEU7n/XgfYi4=
modernc.org/mathutil v1.6.0/go.mod h1:Ui5Q9q1TR2gFm0AQRqQUaBWFLAhQpCwNcuhBOSedWPo=
modernc.org/memory v1.8.0 h1:IqGTL6eFMaDZZhEWwcREgeMXYwmW83LYW8cROZYkg+E=
modernc.org/memory v1.8.0/go.mod h1:XPZ936zp5OMKGWPqbD3JShgd/ZoQ7899TUuQqxY+peU=
modernc.org/opt v0.1.3 h1:3XOZf2yznlhC+ibLltsDGzABUGVx8J6pnFMS3E4dcq4=
modernc.org/opt v0.1.3/go.mod h1:WdSiB5evDcignE70guQKxYUl14mgWtbClRi5wmkkTX0=
modernc.org/sortutil v1.2.0 h1:jQiD3PfS2REGJNzNCMMaLSp/wdMNieTbKX920Cqdgqc=
modernc.org/sortutil v1.2.0/go.mod h1:TKU2s7kJMf1AE84OoiGppNHJwvB753OYfNl2WRb++Ss=
modernc.org/sqlite v1.32.0 h1:6BM4uGza7bWypsw4fdLRsLxut6bHe4c58VeqjRgST8s=
modernc.org/sqlite v1.32.0/go.mod h1:UqoylwmTb9F+IqXERT8bW9zzOWN8qwAIcLdzeBZs4hA=
modernc.org/strutil v1.2.0 h1:agBi9dp1I+eOnxXeiZawM8F4LawKv4NzGWSaLfyeNZA=
modernc.org/strutil v1.2.0/go.mod h1:/mdcBmfOibveCTBxUl5B5l6W+TTH1FXPLHZE6bTosX0=
modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y=
modernc.org/token v1.1.0/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM=

View file

@ -1,6 +1,13 @@
{ pkgs, unstable }: { system, pkgs, unstable, mfgames-project-setup }:
let
project-config = mfgames-project-setup.lib.mkConfig {
inherit system pkgs;
};
in
pkgs.mkShell { pkgs.mkShell {
buildInputs = project-config.packages;
shellHook = project-config.shellHook;
nativeBuildInputs = with pkgs.buildPackages; [ nativeBuildInputs = with pkgs.buildPackages; [
unstable.go_1_21 unstable.go_1_21
unstable.golangci-lint unstable.golangci-lint

View file

@ -3,6 +3,7 @@ package components
import ( import (
"github.com/Khan/genqlient/graphql" "github.com/Khan/genqlient/graphql"
tea "github.com/charmbracelet/bubbletea" tea "github.com/charmbracelet/bubbletea"
resolver "github.com/satisfactorymodding/ficsit-resolver"
"github.com/satisfactorymodding/ficsit-cli/cli" "github.com/satisfactorymodding/ficsit-cli/cli"
"github.com/satisfactorymodding/ficsit-cli/cli/provider" "github.com/satisfactorymodding/ficsit-cli/cli/provider"
@ -19,6 +20,7 @@ type RootModel interface {
GetAPIClient() graphql.Client GetAPIClient() graphql.Client
GetProvider() provider.Provider GetProvider() provider.Provider
GetResolver() resolver.DependencyResolver
Size() tea.WindowSizeMsg Size() tea.WindowSizeMsg
SetSize(size tea.WindowSizeMsg) SetSize(size tea.WindowSizeMsg)

View file

@ -7,7 +7,6 @@ import (
tea "github.com/charmbracelet/bubbletea" tea "github.com/charmbracelet/bubbletea"
"github.com/charmbracelet/lipgloss" "github.com/charmbracelet/lipgloss"
resolver "github.com/satisfactorymodding/ficsit-resolver" resolver "github.com/satisfactorymodding/ficsit-resolver"
"github.com/spf13/viper"
"github.com/satisfactorymodding/ficsit-cli/cli" "github.com/satisfactorymodding/ficsit-cli/cli"
"github.com/satisfactorymodding/ficsit-cli/cli/provider" "github.com/satisfactorymodding/ficsit-cli/cli/provider"
@ -29,7 +28,7 @@ func newModel(global *cli.GlobalContext) *rootModel {
Width: 20, Width: 20,
Height: 14, Height: 14,
}, },
dependencyResolver: resolver.NewDependencyResolver(global.Provider, viper.GetString("api-base")), dependencyResolver: resolver.NewDependencyResolver(global.Provider),
} }
m.headerComponent = components.NewHeaderComponent(m) m.headerComponent = components.NewHeaderComponent(m)
@ -71,6 +70,10 @@ func (m *rootModel) GetProvider() provider.Provider {
return m.global.Provider return m.global.Provider
} }
func (m *rootModel) GetResolver() resolver.DependencyResolver {
return m.dependencyResolver
}
func (m *rootModel) Size() tea.WindowSizeMsg { func (m *rootModel) Size() tea.WindowSizeMsg {
return m.currentSize return m.currentSize
} }

View file

@ -13,8 +13,6 @@ import (
tea "github.com/charmbracelet/bubbletea" tea "github.com/charmbracelet/bubbletea"
"github.com/charmbracelet/lipgloss" "github.com/charmbracelet/lipgloss"
"github.com/muesli/reflow/truncate" "github.com/muesli/reflow/truncate"
resolver "github.com/satisfactorymodding/ficsit-resolver"
"github.com/spf13/viper"
"github.com/satisfactorymodding/ficsit-cli/ficsit" "github.com/satisfactorymodding/ficsit-cli/ficsit"
"github.com/satisfactorymodding/ficsit-cli/tea/components" "github.com/satisfactorymodding/ficsit-cli/tea/components"
@ -112,9 +110,7 @@ func (m updateModsList) LoadModData() {
return return
} }
resolver := resolver.NewDependencyResolver(m.root.GetProvider(), viper.GetString("api-base")) updatedLockfile, err := currentProfile.Resolve(m.root.GetResolver(), nil, gameVersion)
updatedLockfile, err := currentProfile.Resolve(resolver, nil, gameVersion)
if err != nil { if err != nil {
return return
} }

View file

@ -46,6 +46,8 @@ func main() {
baseLocalDir = os.Getenv("APPDATA") baseLocalDir = os.Getenv("APPDATA")
case "linux": case "linux":
baseLocalDir = filepath.Join(os.Getenv("HOME"), ".local", "share") baseLocalDir = filepath.Join(os.Getenv("HOME"), ".local", "share")
case "darwin":
baseLocalDir = filepath.Join(os.Getenv("HOME"), "Library", "Application Support")
default: default:
panic("unsupported platform: " + runtime.GOOS) panic("unsupported platform: " + runtime.GOOS)
} }