diff --git a/.gitea/workflows/build-pack.yaml b/.gitea/workflows/build-pack.yaml new file mode 100644 index 0000000..12fcb9f --- /dev/null +++ b/.gitea/workflows/build-pack.yaml @@ -0,0 +1,146 @@ +name: Build & Release Pack + +on: + push: + branches: [main] + tags: ['v*'] + +jobs: + build: + runs-on: docker + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Setup Go + uses: actions/setup-go@v5 + with: + go-version: '1.22' + + - name: Install packwiz + run: go install github.com/packwiz/packwiz@latest + + - name: Add packwiz to PATH + run: echo "$(go env GOPATH)/bin" >> "$GITHUB_PATH" + + - name: Refresh index + working-directory: client_pack + run: packwiz refresh + + - name: Integrity check + working-directory: client_pack + run: | + python3 - <<'EOF' + import hashlib, os, re, sys, tomllib + index = tomllib.load(open("index.toml", "rb")) + indexed = {e["file"] for e in index["files"]} + mods = [f for f in os.listdir("mods") if f.endswith(".pw.toml")] + missing = [f for f in mods if f"mods/{f}" not in indexed] + if missing: + print("ERROR: pw.toml files missing from index.toml:", missing) + sys.exit(1) + extra = [f for f in indexed if f.startswith("mods/") and f not in {f"mods/{m}" for m in mods}] + if extra: + print("ERROR: index.toml entries missing from mods/:", extra) + sys.exit(1) + errors = 0 + for f in mods: + data = tomllib.load(open(f"mods/{f}", "rb")) + d = data.get("download") + if not d or not d.get("hash"): + print(f"ERROR: {f} missing [download] hash") + errors += 1 + continue + if not d.get("url") and not d.get("mode", "").startswith("metadata:"): + print(f"ERROR: {f} missing [download] url or metadata mode") + errors += 1 + if d.get("url", "").startswith("http://"): + print(f"ERROR: {f} uses insecure http url") + errors += 1 + hf = d.get("hash-format", "sha512") + if not re.fullmatch(r"[0-9a-f]+", d["hash"]): + print(f"ERROR: {f} hash not hex ({hf})") + errors += 1 + if errors: + sys.exit(1) + print(f"OK: {len(mods)} mods indexed, all have [download] url or metadata source") + EOF + + - name: Sanity grep download URLs + working-directory: client_pack + run: | + python3 - <<'EOF' + import glob, sys, tomllib + bad = [] + url_count = 0 + meta_count = 0 + for f in sorted(glob.glob("mods/*.pw.toml")): + d = tomllib.load(open(f, "rb")).get("download", {}) + if d.get("url"): + url_count += 1 + elif d.get("mode", "").startswith("metadata:"): + meta_count += 1 + else: + bad.append(f) + if bad: + print("ERROR: mods without download.url or metadata mode:") + for f in bad: + print(" " + f) + sys.exit(1) + print(f"OK: {url_count} direct urls + {meta_count} metadata sources = all mods resolvable") + EOF + + - name: Export Modrinth pack + working-directory: client_pack + run: packwiz modrinth export -o ../SciCraft-Modrinth.mrpack --restrictDomains=false + + - name: Export CurseForge pack + working-directory: client_pack + run: packwiz curseforge export -o ../SciCraft-CurseForge.zip + + - name: Validate CurseForge pack completeness + working-directory: client_pack + run: | + python3 - <<'EOF' + import json, os, re, sys, zipfile, tomllib + z = zipfile.ZipFile("../SciCraft-CurseForge.zip") + manifest = json.loads(z.read("manifest.json")) + pids = {f["projectID"] for f in manifest["files"]} + ov = {n.split("overrides/mods/")[1] for n in z.namelist() if n.startswith("overrides/mods/")} + missing = [] + for f in os.listdir("mods"): + if not f.endswith(".pw.toml"): + continue + data = tomllib.load(open(f"mods/{f}", "rb")) + if data.get("side") == "server": + continue + fn = data["filename"] + cf = data.get("update", {}).get("curseforge", {}).get("project-id") + if cf: + if cf not in pids: + missing.append(fn) + elif fn not in ov: + missing.append(fn) + if missing: + print("ERROR: mods missing from CurseForge export:", missing) + sys.exit(1) + print(f"OK: CurseForge export complete ({len(pids)} manifest files + {len(ov)} overrides)") + EOF + + - name: Upload pack artifacts + uses: actions/upload-artifact@v4 + with: + name: scicraft-pack + path: | + SciCraft-Modrinth.mrpack + SciCraft-CurseForge.zip + + - name: Release + if: startsWith(github.ref, 'refs/tags/v') + uses: softprops/action-gh-release@v1 + with: + files: | + SciCraft-Modrinth.mrpack + SciCraft-CurseForge.zip + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} \ No newline at end of file