Publish GitHub Release #9
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: Publish GitHub Release | |
| on: | |
| push: | |
| tags: | |
| - 'v*' | |
| workflow_dispatch: | |
| inputs: | |
| tag: | |
| description: Existing release tag to publish or refresh | |
| required: true | |
| type: string | |
| permissions: | |
| contents: write | |
| jobs: | |
| publish-release: | |
| runs-on: ubuntu-latest | |
| steps: | |
| - name: Resolve release metadata | |
| id: metadata | |
| shell: bash | |
| run: | | |
| if [[ "${{ github.event_name }}" == "workflow_dispatch" ]]; then | |
| tag="${{ inputs.tag }}" | |
| else | |
| tag="${GITHUB_REF_NAME}" | |
| fi | |
| if [[ -z "$tag" ]]; then | |
| echo "::error::Unable to determine release tag" | |
| exit 1 | |
| fi | |
| if [[ ! "$tag" =~ ^v([0-9]+\.[0-9]+\.[0-9]+)(r[0-9]+)?$ ]]; then | |
| echo "::error::Tag '$tag' does not match the expected release format" | |
| exit 1 | |
| fi | |
| base_version="${BASH_REMATCH[1]}" | |
| release_name="S1API ${tag#v}" | |
| release_branch="releases/${base_version}" | |
| echo "tag=${tag}" >> "$GITHUB_OUTPUT" | |
| echo "base_version=${base_version}" >> "$GITHUB_OUTPUT" | |
| echo "release_name=${release_name}" >> "$GITHUB_OUTPUT" | |
| echo "release_branch=${release_branch}" >> "$GITHUB_OUTPUT" | |
| - name: Checkout S1API | |
| uses: actions/checkout@v4 | |
| with: | |
| ref: ${{ steps.metadata.outputs.tag }} | |
| fetch-depth: 0 | |
| - name: Setup .NET | |
| uses: actions/setup-dotnet@v4 | |
| with: | |
| dotnet-version: '8.0.x' | |
| - name: Checkout Mono game assemblies | |
| uses: actions/checkout@v4 | |
| with: | |
| repository: ${{ secrets.GAME_ASSEMBLIES_REPO }} | |
| token: ${{ secrets.GAME_ASSEMBLIES_TOKEN }} | |
| path: game-assemblies | |
| fetch-depth: 1 | |
| - name: Checkout IL2CPP game assemblies | |
| uses: actions/checkout@v4 | |
| with: | |
| repository: ${{ secrets.IL2CPP_ASSEMBLIES_REPO }} | |
| token: ${{ secrets.IL2CPP_ASSEMBLIES_TOKEN }} | |
| ref: main | |
| path: il2cpp-scheduleone-assemblies | |
| fetch-depth: 1 | |
| - name: Prepare build inputs | |
| shell: bash | |
| run: | | |
| set -euo pipefail | |
| mkdir -p S1API/ScheduleOneAssemblies/Managed | |
| mkdir -p S1API/ScheduleOneAssemblies/MelonLoader | |
| mkdir -p S1API/ScheduleOneAssemblies/MelonLoaderMono | |
| mkdir -p S1API/ScheduleOneAssemblies/Il2CppAssemblies | |
| cp -r game-assemblies/Managed/* S1API/ScheduleOneAssemblies/Managed/ | |
| if [[ -d "game-assemblies/MelonLoaderMono" ]]; then | |
| cp -r game-assemblies/MelonLoaderMono/* S1API/ScheduleOneAssemblies/MelonLoaderMono/ | |
| elif [[ -d "game-assemblies/MelonLoader/net35" ]]; then | |
| cp -r game-assemblies/MelonLoader/net35/* S1API/ScheduleOneAssemblies/MelonLoaderMono/ | |
| elif [[ -d "game-assemblies/MelonLoader" ]]; then | |
| cp -r game-assemblies/MelonLoader/* S1API/ScheduleOneAssemblies/MelonLoaderMono/ | |
| else | |
| echo "Unable to locate Mono MelonLoader assemblies in game-assemblies" | |
| exit 1 | |
| fi | |
| if [[ -d "il2cpp-scheduleone-assemblies/MelonLoader/Il2CppAssemblies" ]]; then | |
| cp -r il2cpp-scheduleone-assemblies/MelonLoader/Il2CppAssemblies/* S1API/ScheduleOneAssemblies/Il2CppAssemblies/ | |
| else | |
| echo "Unable to locate IL2CPP generated assemblies" | |
| exit 1 | |
| fi | |
| if [[ -d "il2cpp-scheduleone-assemblies/MelonLoader/net6" ]]; then | |
| cp -r il2cpp-scheduleone-assemblies/MelonLoader/net6/* S1API/ScheduleOneAssemblies/MelonLoader/ | |
| else | |
| echo "Unable to locate IL2CPP MelonLoader net6 assemblies" | |
| exit 1 | |
| fi | |
| for assembly in \ | |
| Il2CppInterop.Runtime.dll \ | |
| Il2CppInterop.Common.dll \ | |
| Il2CppInterop.HarmonySupport.dll \ | |
| Il2CppInterop.Generator.dll \ | |
| UnityEngine.Il2CppAssetBundleManager.dll; do | |
| if [[ ! -f "S1API/ScheduleOneAssemblies/MelonLoader/${assembly}" ]]; then | |
| found_path="$(find il2cpp-scheduleone-assemblies -name "${assembly}" -type f | head -1)" | |
| if [[ -n "${found_path}" ]]; then | |
| cp "${found_path}" S1API/ScheduleOneAssemblies/MelonLoader/ | |
| else | |
| echo "Missing required IL2CPP dependency: ${assembly}" | |
| exit 1 | |
| fi | |
| fi | |
| done | |
| for assembly in \ | |
| Unity.TextMeshPro.dll \ | |
| UnityEngine.AIModule.dll; do | |
| if [[ ! -f "S1API/ScheduleOneAssemblies/Managed/${assembly}" ]]; then | |
| found_path="$(find game-assemblies -name "${assembly}" -type f | head -1)" | |
| if [[ -n "${found_path}" ]]; then | |
| cp "${found_path}" S1API/ScheduleOneAssemblies/Managed/ | |
| fi | |
| fi | |
| if [[ ! -f "S1API/ScheduleOneAssemblies/Il2CppAssemblies/${assembly}" ]]; then | |
| found_path="$(find il2cpp-scheduleone-assemblies -name "${assembly}" -type f | head -1)" | |
| if [[ -n "${found_path}" ]]; then | |
| cp "${found_path}" S1API/ScheduleOneAssemblies/Il2CppAssemblies/ | |
| fi | |
| fi | |
| done | |
| cat > ci.build.props << 'EOF' | |
| <Project> | |
| <PropertyGroup> | |
| <AutomateLocalDeployment>false</AutomateLocalDeployment> | |
| <MelonLoaderAssembliesPath>$(MSBuildThisFileDirectory)S1API/ScheduleOneAssemblies/MelonLoader</MelonLoaderAssembliesPath> | |
| <MelonLoaderMonoAssembliesPath>$(MSBuildThisFileDirectory)S1API/ScheduleOneAssemblies/MelonLoaderMono</MelonLoaderMonoAssembliesPath> | |
| <LocalMonoDeploymentPath>null</LocalMonoDeploymentPath> | |
| <MonoAssembliesPath>$(MSBuildThisFileDirectory)S1API/ScheduleOneAssemblies/Managed</MonoAssembliesPath> | |
| <LocalIl2CppDeploymentPath>null</LocalIl2CppDeploymentPath> | |
| <Il2CppAssembliesPath Condition="'$(Configuration)' == 'Il2CppMelon'">$(MSBuildThisFileDirectory)S1API/ScheduleOneAssemblies/Il2CppAssemblies</Il2CppAssembliesPath> | |
| </PropertyGroup> | |
| </Project> | |
| EOF | |
| - name: Build release DLLs | |
| shell: bash | |
| run: | | |
| set -euo pipefail | |
| dotnet restore S1API/S1API.csproj -p:Configuration=Il2CppMelon | |
| dotnet build S1API/S1API.csproj --no-restore -c Il2CppMelon -v minimal /p:AutomateLocalDeployment=false | |
| rm -rf S1API/obj | |
| dotnet restore S1API/S1API.csproj -p:Configuration=MonoMelon | |
| dotnet build S1API/S1API.csproj --no-restore -c MonoMelon -v minimal /p:AutomateLocalDeployment=false | |
| dotnet restore S1APILoader/S1APILoader.csproj -p:Configuration=MonoMelon | |
| dotnet build S1APILoader/S1APILoader.csproj -c MonoMelon -f netstandard2.1 /p:AutomateLocalDeployment=false --no-restore | |
| - name: Package release asset | |
| id: package | |
| shell: bash | |
| run: | | |
| set -euo pipefail | |
| package_dir="artifacts/S1API-Forked-${{ steps.metadata.outputs.base_version }}" | |
| zip_path="artifacts/S1API-Forked-${{ steps.metadata.outputs.base_version }}.zip" | |
| mkdir -p "${package_dir}/mods" "${package_dir}/plugins" | |
| cp S1API/bin/MonoMelon/netstandard2.1/S1API.dll "${package_dir}/mods/S1API.Mono.MelonLoader.dll" | |
| cp S1API/bin/Il2CppMelon/net6.0/S1API.dll "${package_dir}/mods/S1API.Il2Cpp.MelonLoader.dll" | |
| cp S1APILoader/bin/MonoMelon/netstandard2.1/S1APILoader.dll "${package_dir}/plugins/S1APILoader.MelonLoader.dll" | |
| ( | |
| cd "${package_dir}" | |
| zip -r "../$(basename "${zip_path}")" mods plugins | |
| ) | |
| unzip -l "${zip_path}" | |
| for entry in \ | |
| "mods/S1API.Mono.MelonLoader.dll" \ | |
| "mods/S1API.Il2Cpp.MelonLoader.dll" \ | |
| "plugins/S1APILoader.MelonLoader.dll"; do | |
| unzip -Z1 "${zip_path}" | grep -Fx "${entry}" >/dev/null | |
| done | |
| echo "zip_path=${zip_path}" >> "$GITHUB_OUTPUT" | |
| - name: Publish or update GitHub release | |
| uses: actions/github-script@v7 | |
| env: | |
| RELEASE_TAG: ${{ steps.metadata.outputs.tag }} | |
| RELEASE_NAME: ${{ steps.metadata.outputs.release_name }} | |
| RELEASE_BRANCH: ${{ steps.metadata.outputs.release_branch }} | |
| with: | |
| script: | | |
| const owner = context.repo.owner; | |
| const repo = context.repo.repo; | |
| const tag = process.env.RELEASE_TAG; | |
| const releaseName = process.env.RELEASE_NAME; | |
| const releaseBranch = process.env.RELEASE_BRANCH; | |
| const resolveTargetCommitish = async () => { | |
| try { | |
| await github.rest.repos.getBranch({ | |
| owner, | |
| repo, | |
| branch: releaseBranch | |
| }); | |
| return releaseBranch; | |
| } catch (error) { | |
| if (error.status !== 404) { | |
| throw error; | |
| } | |
| } | |
| const ref = await github.rest.git.getRef({ | |
| owner, | |
| repo, | |
| ref: `tags/${tag}` | |
| }); | |
| let target = ref.data.object; | |
| if (target.type === "tag") { | |
| const annotated = await github.rest.git.getTag({ | |
| owner, | |
| repo, | |
| tag_sha: target.sha | |
| }); | |
| target = annotated.data.object; | |
| } | |
| return target.sha; | |
| }; | |
| const targetCommitish = await resolveTargetCommitish(); | |
| const notes = await github.rest.repos.generateReleaseNotes({ | |
| owner, | |
| repo, | |
| tag_name: tag, | |
| target_commitish: targetCommitish | |
| }); | |
| const payload = { | |
| owner, | |
| repo, | |
| tag_name: tag, | |
| target_commitish: targetCommitish, | |
| name: releaseName, | |
| body: notes.data.body, | |
| draft: false, | |
| prerelease: false, | |
| make_latest: "legacy" | |
| }; | |
| let existingRelease = null; | |
| try { | |
| existingRelease = await github.rest.repos.getReleaseByTag({ | |
| owner, | |
| repo, | |
| tag | |
| }); | |
| } catch (error) { | |
| if (error.status !== 404) { | |
| throw error; | |
| } | |
| } | |
| if (existingRelease) { | |
| const updated = await github.rest.repos.updateRelease({ | |
| ...payload, | |
| release_id: existingRelease.data.id | |
| }); | |
| core.notice(`Updated GitHub release ${updated.data.html_url}`); | |
| return; | |
| } | |
| const created = await github.rest.repos.createRelease(payload); | |
| core.notice(`Published GitHub release ${created.data.html_url}`); | |
| - name: Upload release asset | |
| env: | |
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| run: gh release upload "${{ steps.metadata.outputs.tag }}" "${{ steps.package.outputs.zip_path }}" --clobber |