#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