Skip to content

fix(npc): tolerate removed region unlock member #468

fix(npc): tolerate removed region unlock member

fix(npc): tolerate removed region unlock member #468

Workflow file for this run

name: API Coverage Analysis
on:
push:
branches: [ master, main, stable ]
paths-ignore:
- README.md
- tools/S1APICoverageAnalyzer/coverage-history.json
pull_request:
branches: [ master, main, stable ]
workflow_dispatch:
inputs:
assembly_branch:
description: 'Assembly branch to use (main or beta)'
required: false
default: 'main'
type: choice
options:
- main
- beta
jobs:
coverage:
runs-on: ubuntu-latest
permissions:
contents: write
pull-requests: write
steps:
- name: Checkout S1API
uses: actions/checkout@v4
with:
token: ${{ secrets.GITHUB_TOKEN }}
fetch-depth: 0
- name: Setup .NET
uses: actions/setup-dotnet@v4
with:
dotnet-version: '8.0.x'
# Determine which assembly branch to use (beta or main) - MUST run before cache
- name: Determine Assembly Branch
id: assembly-branch
run: |
# Manual workflow dispatch takes precedence
if [[ "${{ github.event_name }}" == "workflow_dispatch" ]] && [[ -n "${{ inputs.assembly_branch }}" ]]; then
echo "branch=${{ inputs.assembly_branch }}" >> $GITHUB_OUTPUT
echo "Using ${{ inputs.assembly_branch }} assemblies (manual trigger)"
elif [[ "${{ github.event_name }}" == "pull_request" ]] && [[ "${{ contains(github.event.pull_request.labels.*.name, 'beta') }}" == "true" ]]; then
echo "branch=beta" >> $GITHUB_OUTPUT
echo "Using beta assemblies for PR with beta label"
else
echo "branch=main" >> $GITHUB_OUTPUT
echo "Using main/stable assemblies"
fi
- name: Create Assembly Directory Structure
run: |
mkdir -p S1API/ScheduleOneAssemblies/Managed
mkdir -p S1API/ScheduleOneAssemblies/MelonLoader
# Try to restore assemblies from cache first (for external PRs)
# Cache key includes assembly branch to separate beta from main
# v3 suffix allows cache invalidation by bumping version
# Only use cache for PR events to avoid stale assemblies after merges
- name: Restore Game Assemblies from Cache
id: cache-assemblies
if: github.event_name == 'pull_request'
uses: actions/cache/restore@v4
with:
path: S1API/ScheduleOneAssemblies
key: game-assemblies-v3-${{ steps.assembly-branch.outputs.branch }}-${{ hashFiles('S1API/S1API.csproj') }}
restore-keys: |
game-assemblies-v3-${{ steps.assembly-branch.outputs.branch }}-
# Only checkout game assemblies if cache miss AND we have access to secrets
- name: Checkout Game Assemblies
uses: actions/checkout@v4
with:
repository: ${{ secrets.GAME_ASSEMBLIES_REPO }}
token: ${{ secrets.GAME_ASSEMBLIES_TOKEN }}
ref: ${{ steps.assembly-branch.outputs.branch }}
path: game-assemblies
fetch-depth: 1
if: steps.cache-assemblies.outputs.cache-hit != 'true' && (github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository)
continue-on-error: true
- name: Debug Game Assemblies Structure
run: |
echo "=== Game Assemblies Repository Structure ==="
find game-assemblies -type f -name "*.dll" | head -20
echo ""
echo "=== Looking for coverage assemblies ==="
find game-assemblies \( -name "Assembly-CSharp.dll" -o -name "ScheduleOne.Core.dll" \) -type f
if: steps.cache-assemblies.outputs.cache-hit != 'true' && (github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository)
- name: Copy Game Assemblies
run: |
# Copy Mono assemblies
if [ -d "game-assemblies/Managed" ]; then
cp -r game-assemblies/Managed/* S1API/ScheduleOneAssemblies/Managed/ || echo "Failed to copy Mono assemblies"
echo "Copied Mono assemblies from game-assemblies/Managed/"
else
echo "No game-assemblies/Managed directory found"
fi
# Copy mod loader assemblies
if [ -d "game-assemblies/MelonLoader" ]; then
cp -r game-assemblies/MelonLoader/* S1API/ScheduleOneAssemblies/MelonLoader/ || echo "Failed to copy MelonLoader assemblies"
echo "Copied MelonLoader assemblies"
else
echo "No game-assemblies/MelonLoader directory found"
fi
# Ensure Unity.TextMeshPro.dll is available
if [ ! -f "S1API/ScheduleOneAssemblies/Managed/Unity.TextMeshPro.dll" ]; then
echo "Unity.TextMeshPro.dll not found in Managed folder, searching for it..."
TMPRO_PATH=$(find game-assemblies -name "Unity.TextMeshPro.dll" -type f | head -1)
if [ -n "$TMPRO_PATH" ]; then
echo "Found Unity.TextMeshPro.dll at: $TMPRO_PATH"
cp "$TMPRO_PATH" S1API/ScheduleOneAssemblies/Managed/ || echo "Failed to copy Unity.TextMeshPro.dll"
echo "Copied Unity.TextMeshPro.dll to Managed folder"
else
echo "Warning: Unity.TextMeshPro.dll not found in game-assemblies repository"
fi
fi
if: steps.cache-assemblies.outputs.cache-hit != 'true' && (github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository)
- name: Verify Assembly Copies
id: verify-assemblies
run: |
echo "=== Verifying Assembly Structure ==="
ls -la S1API/ScheduleOneAssemblies/ || echo "ScheduleOneAssemblies directory not found"
echo ""
echo "=== Critical Assembly Check ==="
if [ -f S1API/ScheduleOneAssemblies/Managed/Assembly-CSharp.dll ] && [ -f S1API/ScheduleOneAssemblies/Managed/ScheduleOne.Core.dll ]; then
echo "✓ Found Assembly-CSharp.dll"
ls -l S1API/ScheduleOneAssemblies/Managed/Assembly-CSharp.dll
echo "✓ Found ScheduleOne.Core.dll"
ls -l S1API/ScheduleOneAssemblies/Managed/ScheduleOne.Core.dll
echo "has_assemblies=true" >> $GITHUB_OUTPUT
else
echo "✗ Missing Assembly-CSharp.dll and/or ScheduleOne.Core.dll - Coverage analysis will be skipped"
echo "has_assemblies=false" >> $GITHUB_OUTPUT
fi
echo ""
if [ "${{ steps.cache-assemblies.outputs.cache-hit }}" == "true" ]; then
echo "✓ Using cached assemblies for build"
else
echo "✓ Using fresh assemblies from repository"
fi
- name: Create CI Build Properties
if: steps.verify-assemblies.outputs.has_assemblies == 'true'
run: |
cat > ci.build.props << 'EOF'
<Project>
<PropertyGroup>
<AutomateLocalDeployment>false</AutomateLocalDeployment>
<!-- CI Assembly Paths (relative to S1API/S1API.csproj) -->
<MelonLoaderAssembliesPath>../ScheduleOneAssemblies/MelonLoader</MelonLoaderAssembliesPath>
<!-- Mono Configuration -->
<LocalMonoDeploymentPath>null</LocalMonoDeploymentPath>
<MonoAssembliesPath>../ScheduleOneAssemblies/Managed</MonoAssembliesPath>
</PropertyGroup>
</Project>
EOF
- name: Restore S1API dependencies
if: steps.verify-assemblies.outputs.has_assemblies == 'true'
run: dotnet restore S1API/S1API.csproj -p:Configuration=MonoMelon
- name: Build S1API
if: steps.verify-assemblies.outputs.has_assemblies == 'true'
run: dotnet build S1API/S1API.csproj --no-restore -c MonoMelon -v minimal
- name: Build Coverage Analyzer
if: steps.verify-assemblies.outputs.has_assemblies == 'true'
run: dotnet build tools/S1APICoverageAnalyzer/S1APICoverageAnalyzer.csproj -c Release
- name: Run Coverage Analysis
if: steps.verify-assemblies.outputs.has_assemblies == 'true'
id: coverage
run: |
dotnet run --project tools/S1APICoverageAnalyzer/S1APICoverageAnalyzer.csproj --no-build -c Release -- \
--game-assembly S1API/ScheduleOneAssemblies/Managed/Assembly-CSharp.dll \
--game-assembly S1API/ScheduleOneAssemblies/Managed/ScheduleOne.Core.dll \
--api-assembly S1API/bin/MonoMelon/netstandard2.1/S1API.dll \
--output coverage-report.json \
--badge-output coverage-badge.md \
--history-file tools/S1APICoverageAnalyzer/coverage-history.json \
--chart-output coverage-chart.md \
--chart-format markdown \
--deduplicate-history \
--verbose
# Read coverage change status
COVERAGE_CHANGED=$(cat tools/S1APICoverageAnalyzer/coverage-changed.txt 2>/dev/null || echo "true")
echo "coverage_changed=$COVERAGE_CHANGED" >> $GITHUB_OUTPUT
echo "Coverage changed: $COVERAGE_CHANGED"
- name: Upload Coverage Report
if: steps.verify-assemblies.outputs.has_assemblies == 'true'
uses: actions/upload-artifact@v4
with:
name: coverage-report
path: |
coverage-report.json
coverage-badge.md
retention-days: 30
- name: Display Coverage Badge
if: steps.verify-assemblies.outputs.has_assemblies == 'true'
run: |
echo "=== Coverage Badge ==="
cat coverage-badge.md || echo "Badge file not found"
echo ""
echo "=== Coverage Summary ==="
if [ -f coverage-report.json ]; then
# Extract key metrics from JSON
echo "Class Coverage: $(jq -r '.classCoverage.percentage' coverage-report.json)%"
echo "Member Coverage: $(jq -r '.memberCoverage.percentage' coverage-report.json)%"
echo "Covered Classes: $(jq -r '.classCoverage.covered' coverage-report.json) / $(jq -r '.classCoverage.total' coverage-report.json)"
fi
- name: Generate README Badge and Chart Updates
id: coverage-files
if: steps.verify-assemblies.outputs.has_assemblies == 'true' && github.event_name == 'push' && (github.ref == 'refs/heads/master' || github.ref == 'refs/heads/main' || github.ref == 'refs/heads/stable')
run: |
COVERAGE_CHANGED="${{ steps.coverage.outputs.coverage_changed }}"
echo "Coverage changed status: $COVERAGE_CHANGED"
# The analyzer refreshes the latest history timestamp on every run.
# Discard that metadata churn unless the measured coverage changed.
if [ "$COVERAGE_CHANGED" != "true" ]; then
echo "Coverage percentage unchanged - discarding generated metadata churn"
git restore -- README.md tools/S1APICoverageAnalyzer/coverage-history.json
echo "changed=false" >> "$GITHUB_OUTPUT"
exit 0
fi
# Coverage changed - update README badge and chart
if [ -f coverage-badge.md ]; then
# Read the new badge markdown (trim whitespace)
NEW_BADGE=$(cat coverage-badge.md | tr -d '\n\r')
# Replace the link to point to the GitHub Actions workflow
# Extract the badge image URL and replace the link URL
WORKFLOW_URL="https://github.com/${{ github.repository }}/actions/workflows/coverage.yml"
NEW_BADGE=$(echo "$NEW_BADGE" | sed "s|](docs/coverage-report.json)|]($WORKFLOW_URL)|g")
echo "New badge: $NEW_BADGE"
# Find the line number with the API Coverage badge in README
BADGE_LINE=$(grep -n "\[!\[API Coverage\]" README.md | head -1 | cut -d: -f1)
if [ -n "$BADGE_LINE" ]; then
echo "Found API Coverage badge at line $BADGE_LINE, updating..."
# Update the badge line in README.md
# Use a temporary file to avoid sed escaping issues
awk -v line="$BADGE_LINE" -v new_badge="$NEW_BADGE" 'NR==line {print new_badge; next} {print}' README.md > README.md.tmp
mv README.md.tmp README.md
else
echo "Warning: API Coverage badge not found in README.md"
fi
else
echo "Coverage badge file unavailable - skipping badge update"
fi
if [ -f coverage-chart.md ]; then
echo "Updating coverage chart in README..."
CHART_URL=$(sed -n '3p' coverage-chart.md)
CHART_LINE=$(grep -n "!\[Coverage Chart\]" README.md | head -1 | cut -d: -f1)
if [ -n "$CHART_LINE" ]; then
echo "Found chart at line $CHART_LINE, updating..."
awk -v line="$CHART_LINE" -v new_chart="$CHART_URL" 'NR==line {print new_chart; next} {print}' README.md > README.md.tmp
mv README.md.tmp README.md
else
echo "Chart line not found in README"
fi
fi
if git diff --quiet -- README.md tools/S1APICoverageAnalyzer/coverage-history.json; then
echo "No coverage files changed"
echo "changed=false" >> "$GITHUB_OUTPUT"
else
echo "Coverage files changed"
git diff -- README.md tools/S1APICoverageAnalyzer/coverage-history.json
echo "changed=true" >> "$GITHUB_OUTPUT"
fi
- name: Open Coverage Update Pull Request
if: steps.coverage-files.outputs.changed == 'true'
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
BASE_BRANCH: ${{ github.ref_name }}
UPDATE_BRANCH: automation/coverage-${{ github.ref_name }}
run: |
git config user.name "github-actions[bot]"
git config user.email "github-actions[bot]@users.noreply.github.com"
git add README.md tools/S1APICoverageAnalyzer/coverage-history.json
git commit -m "chore: update API coverage badge and history"
REMOTE_SHA=$(git ls-remote --heads origin "refs/heads/$UPDATE_BRANCH" | cut -f1)
if [ -n "$REMOTE_SHA" ]; then
git push --force-with-lease="refs/heads/$UPDATE_BRANCH:$REMOTE_SHA" origin "HEAD:$UPDATE_BRANCH"
else
git push origin "HEAD:$UPDATE_BRANCH"
fi
EXISTING_PR=$(gh pr list \
--repo "${{ github.repository }}" \
--base "$BASE_BRANCH" \
--head "$UPDATE_BRANCH" \
--state open \
--json url \
--jq '.[0].url')
if [ -n "$EXISTING_PR" ]; then
echo "Updated existing coverage pull request: $EXISTING_PR"
else
gh pr create \
--repo "${{ github.repository }}" \
--base "$BASE_BRANCH" \
--head "$UPDATE_BRANCH" \
--title "chore: update API coverage badge and history" \
--body "Automated coverage metadata update generated by [workflow run ${{ github.run_id }}](${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }})."
fi
# Save cache even if build fails (to enable beta cache priming)
# Save on PR events AND on pushes to main branches (to update cache after assembly updates)
- name: Save Game Assemblies to Cache
uses: actions/cache/save@v4
if: always() && (github.event_name == 'pull_request' || (github.event_name == 'push' && (github.ref == 'refs/heads/master' || github.ref == 'refs/heads/main' || github.ref == 'refs/heads/stable'))) && steps.cache-assemblies.outputs.cache-hit != 'true'
with:
path: S1API/ScheduleOneAssemblies
key: game-assemblies-v3-${{ steps.assembly-branch.outputs.branch }}-${{ hashFiles('S1API/S1API.csproj') }}
- name: Coverage Skipped Notice
if: steps.verify-assemblies.outputs.has_assemblies != 'true'
run: |
echo "::warning::Coverage analysis was skipped because game assemblies are not available."
echo "This is expected for external PRs or when the assemblies repository is not accessible."