chore(release): bump S1API to 3.1.0-beta.3 #17
Workflow file for this run
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 (for example v3.1.0 or v3.1.0-beta.1) | |
| required: true | |
| type: string | |
| publish_nexus: | |
| description: Upload the release package to Nexus Mods | |
| required: false | |
| default: true | |
| type: boolean | |
| publish_thunderstore: | |
| description: Upload the Thunderstore package to Thunderstore | |
| required: false | |
| default: true | |
| type: boolean | |
| permissions: | |
| contents: write | |
| env: | |
| NEXUSMODS_API_KEY: ${{ secrets.NEXUSMODS_API_KEY }} | |
| NEXUSMODS_FILE_GROUP_ID: ${{ secrets.NEXUSMODS_FILE_GROUP_ID || vars.NEXUSMODS_FILE_GROUP_ID }} | |
| RELEASE_DESCRIPTION: S1API is an open source collaboration project to help standardize Schedule One modding processes. The goal is to provide a standard place for common functionalities so you can focus on making content versus reverse engineering the game. | |
| THUNDERSTORE_COMMUNITY: schedule-i | |
| THUNDERSTORE_TOKEN: ${{ secrets.THUNDERSTORE_TOKEN }} | |
| THUNDERSTORE_NAMESPACE: ifBars | |
| THUNDERSTORE_PACKAGE_NAME: S1API_Forked | |
| THUNDERSTORE_CATEGORIES: libraries | |
| THUNDERSTORE_WEBSITE: https://github.com/ifBars/S1API | |
| 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]+)(-([0-9A-Za-z][0-9A-Za-z.-]*))?(r[0-9]+)?$ ]]; then | |
| echo "::error::Tag '$tag' does not match the expected release format" | |
| exit 1 | |
| fi | |
| base_version="${BASH_REMATCH[1]}" | |
| release_version="${BASH_REMATCH[1]}${BASH_REMATCH[2]}" | |
| release_name="S1API ${tag#v}" | |
| if [[ -n "${BASH_REMATCH[2]}" ]]; then | |
| prerelease="true" | |
| release_branch="beta" | |
| assembly_branch="beta" | |
| else | |
| prerelease="false" | |
| release_branch="releases/${base_version}" | |
| assembly_branch="main" | |
| fi | |
| echo "tag=${tag}" >> "$GITHUB_OUTPUT" | |
| echo "base_version=${base_version}" >> "$GITHUB_OUTPUT" | |
| echo "release_version=${release_version}" >> "$GITHUB_OUTPUT" | |
| echo "release_name=${release_name}" >> "$GITHUB_OUTPUT" | |
| echo "release_branch=${release_branch}" >> "$GITHUB_OUTPUT" | |
| echo "assembly_branch=${assembly_branch}" >> "$GITHUB_OUTPUT" | |
| echo "prerelease=${prerelease}" >> "$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 }} | |
| ref: ${{ steps.metadata.outputs.assembly_branch }} | |
| path: game-assemblies | |
| fetch-depth: 1 | |
| - name: Prepare Mono build inputs | |
| shell: bash | |
| run: | | |
| set -euo pipefail | |
| mkdir -p S1API/ScheduleOneAssemblies/Managed | |
| mkdir -p S1API/ScheduleOneAssemblies/MelonLoader | |
| mkdir -p S1API/ScheduleOneAssemblies/MelonLoaderMono | |
| cp -r game-assemblies/Managed/* S1API/ScheduleOneAssemblies/Managed/ | |
| if [[ -d "game-assemblies/MelonLoader" ]]; then | |
| cp -r game-assemblies/MelonLoader/* S1API/ScheduleOneAssemblies/MelonLoader/ | |
| fi | |
| 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 [[ ! -f "S1API/ScheduleOneAssemblies/Managed/Unity.TextMeshPro.dll" ]]; then | |
| found_path="$(find game-assemblies -name "Unity.TextMeshPro.dll" -type f | head -1)" | |
| if [[ -n "${found_path}" ]]; then | |
| cp "${found_path}" S1API/ScheduleOneAssemblies/Managed/ | |
| fi | |
| fi | |
| if [[ ! -f "S1API/ScheduleOneAssemblies/Managed/UnityEngine.AIModule.dll" ]]; then | |
| found_path="$(find game-assemblies -name "UnityEngine.AIModule.dll" -type f | head -1)" | |
| if [[ -n "${found_path}" ]]; then | |
| cp "${found_path}" S1API/ScheduleOneAssemblies/Managed/ | |
| fi | |
| fi | |
| 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> | |
| </PropertyGroup> | |
| </Project> | |
| EOF | |
| - name: Build Mono release DLLs | |
| shell: bash | |
| run: | | |
| set -euo pipefail | |
| mkdir -p artifacts/build | |
| dotnet restore S1API/S1API.csproj -p:Configuration=MonoMelon | |
| dotnet build S1API/S1API.csproj --no-restore -c MonoMelon -v minimal /p:AutomateLocalDeployment=false | |
| cp S1API/bin/MonoMelon/netstandard2.1/S1API.dll artifacts/build/S1API.Mono.MelonLoader.dll | |
| dotnet restore S1APILoader/S1APILoader.csproj -p:Configuration=MonoMelon | |
| dotnet build S1APILoader/S1APILoader.csproj -c MonoMelon -f netstandard2.1 /p:AutomateLocalDeployment=false --no-restore | |
| cp S1APILoader/bin/MonoMelon/netstandard2.1/S1APILoader.dll artifacts/build/S1APILoader.MelonLoader.dll | |
| - name: Checkout IL2CPP game assemblies | |
| uses: actions/checkout@v4 | |
| with: | |
| repository: ${{ secrets.IL2CPP_ASSEMBLIES_REPO }} | |
| token: ${{ secrets.IL2CPP_ASSEMBLIES_TOKEN }} | |
| ref: ${{ steps.metadata.outputs.assembly_branch }} | |
| path: il2cpp-scheduleone-assemblies | |
| fetch-depth: 1 | |
| - name: Prepare IL2CPP build inputs | |
| shell: bash | |
| run: | | |
| set -euo pipefail | |
| rm -rf S1API/obj | |
| mkdir -p S1API/ScheduleOneAssemblies/MelonLoader | |
| mkdir -p S1API/ScheduleOneAssemblies/Il2CppAssemblies | |
| 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/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> | |
| <LocalIl2CppDeploymentPath>null</LocalIl2CppDeploymentPath> | |
| <Il2CppAssembliesPath Condition="'$(Configuration)' == 'Il2CppMelon'">$(MSBuildThisFileDirectory)S1API/ScheduleOneAssemblies/Il2CppAssemblies</Il2CppAssembliesPath> | |
| </PropertyGroup> | |
| </Project> | |
| EOF | |
| - name: Build IL2CPP release DLL | |
| 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 | |
| cp S1API/bin/Il2CppMelon/net6.0/S1API.dll artifacts/build/S1API.Il2Cpp.MelonLoader.dll | |
| - name: Package release assets | |
| id: package | |
| shell: bash | |
| run: | | |
| set -euo pipefail | |
| package_dir="artifacts/S1API-Forked-${{ steps.metadata.outputs.release_version }}" | |
| zip_path="artifacts/S1API-Forked-${{ steps.metadata.outputs.release_version }}.zip" | |
| thunderstore_dir="artifacts/S1API-TS-${{ steps.metadata.outputs.release_version }}" | |
| thunderstore_zip_path="artifacts/S1API-TS-${{ steps.metadata.outputs.release_version }}.zip" | |
| mkdir -p "${package_dir}/Mods" "${package_dir}/Plugins" | |
| cp artifacts/build/S1API.Mono.MelonLoader.dll "${package_dir}/Mods/S1API.Mono.MelonLoader.dll" | |
| cp artifacts/build/S1API.Il2Cpp.MelonLoader.dll "${package_dir}/Mods/S1API.Il2Cpp.MelonLoader.dll" | |
| cp artifacts/build/S1APILoader.MelonLoader.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 | |
| mkdir -p "${thunderstore_dir}/Mods" "${thunderstore_dir}/Plugins" | |
| cp artifacts/build/S1API.Mono.MelonLoader.dll "${thunderstore_dir}/Mods/S1API.Mono.MelonLoader.dll" | |
| cp artifacts/build/S1API.Il2Cpp.MelonLoader.dll "${thunderstore_dir}/Mods/S1API.Il2Cpp.MelonLoader.dll" | |
| cp artifacts/build/S1APILoader.MelonLoader.dll "${thunderstore_dir}/Plugins/S1APILoader.MelonLoader.dll" | |
| cp Public/logo_compressed.png "${thunderstore_dir}/icon.png" | |
| cp Public/README.md "${thunderstore_dir}/README.md" | |
| jq -n \ | |
| --arg name "${THUNDERSTORE_PACKAGE_NAME}" \ | |
| --arg version "${{ steps.metadata.outputs.release_version }}" \ | |
| --arg website "${THUNDERSTORE_WEBSITE}" \ | |
| --arg description "${RELEASE_DESCRIPTION}" \ | |
| '{ | |
| name: $name, | |
| version_number: $version, | |
| website_url: $website, | |
| description: $description, | |
| dependencies: ["LavaGang-MelonLoader-0.7.2"] | |
| }' > "${thunderstore_dir}/manifest.json" | |
| ( | |
| cd "${thunderstore_dir}" | |
| zip -r "../$(basename "${thunderstore_zip_path}")" icon.png README.md manifest.json Mods Plugins | |
| ) | |
| unzip -l "${thunderstore_zip_path}" | |
| for entry in \ | |
| "icon.png" \ | |
| "README.md" \ | |
| "manifest.json" \ | |
| "Mods/S1API.Mono.MelonLoader.dll" \ | |
| "Mods/S1API.Il2Cpp.MelonLoader.dll" \ | |
| "Plugins/S1APILoader.MelonLoader.dll"; do | |
| unzip -Z1 "${thunderstore_zip_path}" | grep -Fx "${entry}" >/dev/null | |
| done | |
| jq -e --arg version "${{ steps.metadata.outputs.release_version }}" '.version_number == $version' "${thunderstore_dir}/manifest.json" >/dev/null | |
| echo "zip_path=${zip_path}" >> "$GITHUB_OUTPUT" | |
| echo "thunderstore_zip_path=${thunderstore_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 }} | |
| PRERELEASE: ${{ steps.metadata.outputs.prerelease }} | |
| 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 prerelease = process.env.PRERELEASE === "true"; | |
| 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, | |
| make_latest: prerelease ? "false" : "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 GitHub release assets | |
| env: | |
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| run: | | |
| gh release delete-asset "${{ steps.metadata.outputs.tag }}" "$(basename "${{ steps.package.outputs.thunderstore_zip_path }}")" --yes || true | |
| gh release upload "${{ steps.metadata.outputs.tag }}" "${{ steps.package.outputs.zip_path }}" --clobber | |
| - name: Upload Nexus Mods release | |
| if: ${{ steps.metadata.outputs.prerelease != 'true' && (github.event_name != 'workflow_dispatch' || inputs.publish_nexus) && env.NEXUSMODS_API_KEY != '' && env.NEXUSMODS_FILE_GROUP_ID != '' }} | |
| uses: Nexus-Mods/upload-action@v1.0.0-beta.7 | |
| with: | |
| api_key: ${{ env.NEXUSMODS_API_KEY }} | |
| file_group_id: ${{ env.NEXUSMODS_FILE_GROUP_ID }} | |
| filename: ${{ steps.package.outputs.zip_path }} | |
| version: ${{ steps.metadata.outputs.release_version }} | |
| display_name: S1API Forked ${{ steps.metadata.outputs.release_version }} | |
| description: ${{ env.RELEASE_DESCRIPTION }} | |
| file_category: main | |
| archive_existing_file: true | |
| primary_mod_manager_download: true | |
| allow_mod_manager_download: true | |
| show_requirements_pop_up: false | |
| - name: Skip Nexus Mods upload | |
| if: ${{ steps.metadata.outputs.prerelease != 'true' && (github.event_name != 'workflow_dispatch' || inputs.publish_nexus) && (env.NEXUSMODS_API_KEY == '' || env.NEXUSMODS_FILE_GROUP_ID == '') }} | |
| shell: bash | |
| run: echo "::notice::Skipping Nexus Mods upload because NEXUSMODS_API_KEY or NEXUSMODS_FILE_GROUP_ID is not configured." | |
| - name: Upload Thunderstore release | |
| if: ${{ steps.metadata.outputs.prerelease != 'true' && (github.event_name != 'workflow_dispatch' || inputs.publish_thunderstore) && env.THUNDERSTORE_TOKEN != '' }} | |
| uses: GreenTF/upload-thunderstore-package@v4.3 | |
| with: | |
| token: ${{ env.THUNDERSTORE_TOKEN }} | |
| community: ${{ env.THUNDERSTORE_COMMUNITY }} | |
| namespace: ${{ env.THUNDERSTORE_NAMESPACE }} | |
| name: ${{ env.THUNDERSTORE_PACKAGE_NAME }} | |
| description: ${{ env.RELEASE_DESCRIPTION }} | |
| version: ${{ steps.metadata.outputs.release_version }} | |
| file: ${{ steps.package.outputs.thunderstore_zip_path }} | |
| repo: https://thunderstore.io | |
| website: ${{ env.THUNDERSTORE_WEBSITE }} | |
| categories: ${{ env.THUNDERSTORE_CATEGORIES }} | |
| - name: Skip Thunderstore upload | |
| if: ${{ steps.metadata.outputs.prerelease != 'true' && (github.event_name != 'workflow_dispatch' || inputs.publish_thunderstore) && env.THUNDERSTORE_TOKEN == '' }} | |
| shell: bash | |
| run: echo "::notice::Skipping Thunderstore upload because THUNDERSTORE_TOKEN is not configured." | |
| - name: Skip external prerelease uploads | |
| if: ${{ steps.metadata.outputs.prerelease == 'true' }} | |
| run: echo "::notice::Experimental releases are published to GitHub only; Nexus Mods and Thunderstore uploads are disabled." |