From 20b5d3dbbeccc5d95cf0e49c6664c2ac29c15047 Mon Sep 17 00:00:00 2001 From: Alan Tse Date: Fri, 1 May 2026 22:50:32 -0700 Subject: [PATCH 1/2] fix(nexus): per-mod success/fail status in upload summary MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Replace the aggregate pass/fail summary with a Nexus API check that queries each mod's actual file list after upload. The summary now shows ✅/❌ per feature, lists the artifact name only for failures, and keeps the re-run instruction when any failed. Co-Authored-By: Claude Sonnet 4.6 --- .github/workflows/upload-nexus.yaml | 83 +++++++++++++++++++---------- 1 file changed, 54 insertions(+), 29 deletions(-) diff --git a/.github/workflows/upload-nexus.yaml b/.github/workflows/upload-nexus.yaml index 4c34e5a163..b9d74a0184 100644 --- a/.github/workflows/upload-nexus.yaml +++ b/.github/workflows/upload-nexus.yaml @@ -414,52 +414,77 @@ jobs: if: needs.prepare-nexus-matrix.outputs.dry_run != 'true' && needs.prepare-nexus-matrix.outputs.has_uploads == 'true' run: | python3 << 'PYEOF' - import json, os + import json, os, urllib.request, urllib.error - result = os.environ.get('UPLOAD_RESULT', '') rows = json.loads(os.environ['UPLOAD_MATRIX']).get('include', []) rows = [r for r in rows if not r.get('skip')] version = os.environ.get('VERSION', '') game_id = os.environ.get('NEXUS_GAME_ID', 'skyrimspecialedition') + api_key = os.environ.get('UNEX_APIKEY', '') summary = open(os.environ['GITHUB_STEP_SUMMARY'], 'a') def w(line=''): summary.write(line + '\n') - if result == 'success': - w('✅ All Nexus uploads completed successfully') - w() - w('| Feature | Version | Nexus |') - w('|---------|---------|-------|') - for row in rows: - mod_id = str(row.get('nexus_mod_id', '')) - planned = row.get('mod_version') or version - label = row.get('mod_filename', row.get('name', '')) - url = f'https://www.nexusmods.com/{game_id}/mods/{mod_id}' - w(f'| [{label}]({url}) | `{planned}` | [View]({url}) |') + def nexus_versions(mod_id): + if not api_key or not mod_id: + return None + url = f'https://api.nexusmods.com/v1/games/{game_id}/mods/{mod_id}/files.json' + req = urllib.request.Request(url, headers={ + 'apikey': api_key, + 'User-Agent': 'community-shaders-ci/1.0', + 'Accept': 'application/json', + }) + try: + with urllib.request.urlopen(req, timeout=15) as resp: + files = json.load(resp).get('files', []) + seen = [] + for f in files: + v = f.get('version', '') + if v and v not in seen: + seen.append(v) + return seen + except Exception: + return None + + succeeded, failed = [], [] + rows_status = [] + for row in rows: + name = row.get('name', '') + mod_id = str(row.get('nexus_mod_id', '')) + planned = row.get('mod_version') or version + label = row.get('mod_filename', name) + url = f'https://www.nexusmods.com/{game_id}/mods/{mod_id}' + artifact = f'nexus-upload-{name}' + versions = nexus_versions(mod_id) + if versions is None: + status, icon = 'unknown', '❓' + elif planned in versions: + status, icon = 'uploaded', '✅' + succeeded.append(label) + else: + status, icon = 'failed', '❌' + failed.append(label) + rows_status.append((icon, label, planned, url, artifact, status)) + + if failed: + w(f'⚠️ {len(failed)} upload(s) failed, {len(succeeded)} succeeded') else: - w(f'⚠️ Some uploads may have failed (result: `{result}`)') - w() - w('The following uploads were planned. Re-run this workflow to retry,') - w('or download the artifact and upload manually to Nexus.') - w() - w('| Feature | Version | Nexus | Artifact |') - w('|---------|---------|-------|----------|') - for row in rows: - name = row.get('name', '') - mod_id = str(row.get('nexus_mod_id', '')) - planned = row.get('mod_version') or version - label = row.get('mod_filename', name) - url = f'https://www.nexusmods.com/{game_id}/mods/{mod_id}' - artifact = f'nexus-upload-{name}' - w(f'| [{label}]({url}) | `{planned}` | [View]({url}) | `{artifact}` |') + w(f'✅ All {len(succeeded)} Nexus upload(s) completed successfully') + w() + w('| Status | Feature | Version | Nexus | Artifact |') + w('|--------|---------|---------|-------|----------|') + for icon, label, planned, url, artifact, status in rows_status: + art = f'`{artifact}`' if status == 'failed' else '—' + w(f'| {icon} | [{label}]({url}) | `{planned}` | [View]({url}) | {art} |') + if failed: w() w('Re-run is safe: already-uploaded versions will be automatically skipped.') summary.close() PYEOF env: - UPLOAD_RESULT: ${{ needs.upload-to-nexus.result }} UPLOAD_MATRIX: ${{ needs.prepare-nexus-matrix.outputs.upload_matrix }} VERSION: ${{ needs.prepare-nexus-matrix.outputs.version }} NEXUS_GAME_ID: ${{ inputs.nexus_game_id || 'skyrimspecialedition' }} + UNEX_APIKEY: ${{ secrets.UNEX_APIKEY }} From 20dd126dc27e22b561a886bd8c31f265694d1054 Mon Sep 17 00:00:00 2001 From: Alan Tse Date: Fri, 1 May 2026 23:36:07 -0700 Subject: [PATCH 2/2] fix(nexus): align summary category filter and track unknowns - Filter MAIN-category files first in nexus_versions() (matches dry-run check behavior); fall back to all files when no MAIN entries exist - Track unknown (API-unreachable) items in their own list so the headline never reads "All 0 succeeded" when all API calls fail - Show artifact name for unknown rows so re-uploaders know which artifact to grab - Extend re-run hint to unknown results, not just failures Co-Authored-By: Claude Sonnet 4.6 --- .github/workflows/upload-nexus.yaml | 20 ++++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/.github/workflows/upload-nexus.yaml b/.github/workflows/upload-nexus.yaml index b9d74a0184..2a49a715bd 100644 --- a/.github/workflows/upload-nexus.yaml +++ b/.github/workflows/upload-nexus.yaml @@ -438,8 +438,10 @@ jobs: try: with urllib.request.urlopen(req, timeout=15) as resp: files = json.load(resp).get('files', []) + main = [f for f in files if f.get('category_name') == 'MAIN'] + check = main if main else files seen = [] - for f in files: + for f in check: v = f.get('version', '') if v and v not in seen: seen.append(v) @@ -447,7 +449,7 @@ jobs: except Exception: return None - succeeded, failed = [], [] + succeeded, failed, unknowns = [], [], [] rows_status = [] for row in rows: name = row.get('name', '') @@ -459,6 +461,7 @@ jobs: versions = nexus_versions(mod_id) if versions is None: status, icon = 'unknown', '❓' + unknowns.append(label) elif planned in versions: status, icon = 'uploaded', '✅' succeeded.append(label) @@ -467,17 +470,22 @@ jobs: failed.append(label) rows_status.append((icon, label, planned, url, artifact, status)) - if failed: - w(f'⚠️ {len(failed)} upload(s) failed, {len(succeeded)} succeeded') + if failed or unknowns: + parts = [] + if failed: + parts.append(f'{len(failed)} failed') + if unknowns: + parts.append(f'{len(unknowns)} unknown') + w(f'⚠️ {", ".join(parts)} — {len(succeeded)} succeeded') else: w(f'✅ All {len(succeeded)} Nexus upload(s) completed successfully') w() w('| Status | Feature | Version | Nexus | Artifact |') w('|--------|---------|---------|-------|----------|') for icon, label, planned, url, artifact, status in rows_status: - art = f'`{artifact}`' if status == 'failed' else '—' + art = f'`{artifact}`' if status in ('failed', 'unknown') else '—' w(f'| {icon} | [{label}]({url}) | `{planned}` | [View]({url}) | {art} |') - if failed: + if failed or unknowns: w() w('Re-run is safe: already-uploaded versions will be automatically skipped.')