Add explicit client/server side field to manifest
Bake each file's "side" (client / server / both) into manifest.json at build time so both the launcher and the server filter deterministically and offline. This replaces the launcher's previous "download everything" default and lets the server short-circuit its runtime Modrinth lookup when a file already has an authoritative tag. Schema: - ManifestFile.Side: "client" | "server" | "both" (null = "both" for backward compat with manifests pre-dating this field) - Files marked "both" omit the field entirely to keep the JSON tight Code changes: - launcher Manifest.cs + ManifestSyncService: filter out "server" files in both the prune+download path and FindMissingFiles - server Manifest.cs + ManifestSync: drop "client" files outright; only fall back to the existing runtime Modrinth lookup for files with no explicit side (legacy/un-tagged mods stay protected) - server LockedMod: side field propagates into manifest at build time - scripts/Build-Pack.ps1: propagate side from lockfile to manifest - scripts/Bootstrap-Sides.ps1: one-off populator; queries Modrinth's client_side/server_side per project, conservatively marks restricted only when one side is "unsupported", leaves ambiguous cases as "both" - pack/pack.lock.json: bootstrap-populated sides (3 client, 5 server, rest both); CurseForge mods default to "both" pending manual review; version bumped 0.9.3 -> 0.10.0 since clients must re-sync Compatibility: - Old launcher + new manifest: ignores unknown "side", downloads all - New launcher + old manifest: side null -> "both", installs all - Old server + new manifest: same -- runtime Modrinth lookup still works - New server + old manifest: side null -> runtime lookup, same behaviour
This commit is contained in:
@@ -0,0 +1,96 @@
|
||||
#requires -Version 7
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Populate the `side` field on each mod entry in pack.lock.json by querying
|
||||
Modrinth's project metadata. Run once after schema migration; thereafter,
|
||||
Update-Pack.ps1 should set side automatically when adding new mods.
|
||||
|
||||
.DESCRIPTION
|
||||
Modrinth returns `client_side` and `server_side` per project, with values
|
||||
`required` / `optional` / `unsupported` / `unknown`. We map:
|
||||
server_side = unsupported -> side = "client" (skip on server)
|
||||
client_side = unsupported -> side = "server" (skip on launcher)
|
||||
both required/optional/unknown -> side = "both" (install everywhere)
|
||||
|
||||
Conservative: only marks a mod side-restricted when the OTHER side is
|
||||
explicitly `unsupported`. Anything ambiguous stays "both".
|
||||
|
||||
CurseForge mods can't be queried (no API key here) -- left at "both" by
|
||||
default, manual edit if needed.
|
||||
|
||||
.PARAMETER LockPath
|
||||
pack.lock.json to update in place. Defaults to ../pack/pack.lock.json.
|
||||
|
||||
.PARAMETER DryRun
|
||||
Print proposed changes without writing the file.
|
||||
#>
|
||||
|
||||
[CmdletBinding()]
|
||||
param(
|
||||
[string]$LockPath = $(Join-Path (Split-Path -Parent $MyInvocation.MyCommand.Path) '..\pack\pack.lock.json'),
|
||||
[switch]$DryRun
|
||||
)
|
||||
|
||||
$ErrorActionPreference = 'Stop'
|
||||
$ProgressPreference = 'SilentlyContinue'
|
||||
|
||||
$LockPath = (Resolve-Path $LockPath).Path
|
||||
$json = Get-Content $LockPath -Raw | ConvertFrom-Json -Depth 20
|
||||
|
||||
if (-not $json.mods) { throw "pack.lock.json has no 'mods' array." }
|
||||
|
||||
function Get-ModrinthSide {
|
||||
param([string]$Slug)
|
||||
$proj = Invoke-RestMethod -Uri "https://api.modrinth.com/v2/project/$Slug" `
|
||||
-Headers @{ 'User-Agent' = 'BrassAndSigil-Launcher/0.1 (matt@sijbers.uk)' }
|
||||
$cs = $proj.client_side; $ss = $proj.server_side
|
||||
if ($ss -eq 'unsupported' -and $cs -ne 'unsupported') { return 'client' }
|
||||
elseif ($cs -eq 'unsupported' -and $ss -ne 'unsupported') { return 'server' }
|
||||
else { return 'both' }
|
||||
}
|
||||
|
||||
Write-Host ""
|
||||
Write-Host "Bootstrapping side field on $($json.mods.Count) mods..."
|
||||
Write-Host ""
|
||||
|
||||
$changes = 0
|
||||
foreach ($mod in $json.mods) {
|
||||
$existing = if ($mod.PSObject.Properties.Name -contains 'side') { $mod.side } else { $null }
|
||||
if ($mod.source -ne 'modrinth') {
|
||||
# CurseForge or other: default to 'both' if missing, leave alone otherwise.
|
||||
if (-not $existing) {
|
||||
$mod | Add-Member -NotePropertyName 'side' -NotePropertyValue 'both' -Force
|
||||
Write-Host (" [{0}] {1,-26} {2}" -f $mod.source.PadRight(7).Substring(0,7), $mod.slug, 'both (default; non-modrinth)')
|
||||
$changes++
|
||||
}
|
||||
continue
|
||||
}
|
||||
try {
|
||||
$side = Get-ModrinthSide -Slug $mod.slug
|
||||
} catch {
|
||||
Write-Warning (" $($mod.slug) lookup failed: $($_.Exception.Message) -- leaving as 'both'")
|
||||
$side = 'both'
|
||||
}
|
||||
if ($existing -ne $side) {
|
||||
$mod | Add-Member -NotePropertyName 'side' -NotePropertyValue $side -Force
|
||||
Write-Host (" [{0}] {1,-26} {2}" -f $mod.source.PadRight(7).Substring(0,7), $mod.slug, $side)
|
||||
$changes++
|
||||
} else {
|
||||
Write-Host (" [{0}] {1,-26} {2} (unchanged)" -f $mod.source.PadRight(7).Substring(0,7), $mod.slug, $side) -ForegroundColor DarkGray
|
||||
}
|
||||
Start-Sleep -Milliseconds 100 # be polite to Modrinth's API
|
||||
}
|
||||
|
||||
Write-Host ""
|
||||
Write-Host "$changes mod(s) updated."
|
||||
|
||||
if ($DryRun) {
|
||||
Write-Host "DryRun: not writing $LockPath." -ForegroundColor DarkGray
|
||||
exit 0
|
||||
}
|
||||
|
||||
# Round-trip via JSON: Modrinth-returned ConvertFrom-Json objects don't always
|
||||
# round-trip with consistent property order, so we serialise with sorted keys
|
||||
# at the mod level to keep the lockfile diff minimal.
|
||||
$json | ConvertTo-Json -Depth 20 | Set-Content -Path $LockPath -Encoding utf8
|
||||
Write-Host "Wrote $LockPath" -ForegroundColor Green
|
||||
@@ -77,14 +77,21 @@ $files = @()
|
||||
$totalBytes = 0L
|
||||
|
||||
foreach ($mod in $lock.mods) {
|
||||
$files += [pscustomobject]@{
|
||||
# `side` is emitted only when explicitly set in the lockfile -- a missing or
|
||||
# null lockfile side means "both", and we keep the manifest clean by omitting
|
||||
# the field rather than serialising "both" everywhere.
|
||||
$entry = [ordered]@{
|
||||
path = $mod.path
|
||||
url = $mod.url
|
||||
sha1 = $mod.sha1
|
||||
size = $mod.size
|
||||
}
|
||||
$hasSide = ($mod.PSObject.Properties.Name -contains 'side') -and $mod.side -and $mod.side -ne 'both'
|
||||
if ($hasSide) { $entry.side = $mod.side }
|
||||
$files += [pscustomobject]$entry
|
||||
$totalBytes += $mod.size
|
||||
Write-Host (" [{0}] {1,-26} {2,-22} {3,8:N0} KB" -f $mod.source.PadRight(7).Substring(0,7), $mod.slug, $mod.version, ($mod.size/1KB))
|
||||
$sideTag = if ($hasSide) { " [$($mod.side)]" } else { '' }
|
||||
Write-Host (" [{0}] {1,-26} {2,-22} {3,8:N0} KB{4}" -f $mod.source.PadRight(7).Substring(0,7), $mod.slug, $mod.version, ($mod.size/1KB), $sideTag)
|
||||
}
|
||||
|
||||
# Local overrides (configs, custom files not on Modrinth/CurseForge)
|
||||
|
||||
Reference in New Issue
Block a user