Repository navigation
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: Build executables | |
| on: | |
| push: | |
| branches: | |
| - "**" | |
| tags: | |
| - "v*" | |
| pull_request: | |
| branches: | |
| - master | |
| workflow_dispatch: | |
| permissions: | |
| contents: read | |
| jobs: | |
| version: | |
| name: Resolve build version | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 5 | |
| outputs: | |
| version: ${{ steps.metadata.outputs.version }} | |
| sha: ${{ steps.metadata.outputs.sha }} | |
| steps: | |
| - name: Check out repository | |
| uses: actions/checkout@v6 | |
| - name: Set up Dart | |
| uses: dart-lang/setup-dart@v1 | |
| with: | |
| sdk: "3.10.0" | |
| - name: Resolve build metadata | |
| id: metadata | |
| shell: bash | |
| run: | | |
| if [[ "$GITHUB_REF_TYPE" == "tag" ]]; then | |
| version="$(dart run MultiplexorApp/tool/resolve_build_version.dart --tag "$GITHUB_REF_NAME")" | |
| else | |
| version="$(dart run MultiplexorApp/tool/resolve_build_version.dart --run-number "$GITHUB_RUN_NUMBER")" | |
| fi | |
| echo "version=$version" >> "$GITHUB_OUTPUT" | |
| echo "sha=$(git rev-parse HEAD)" >> "$GITHUB_OUTPUT" | |
| gameplay: | |
| name: Test Mineflayer (${{ matrix.runner }}) | |
| needs: version | |
| runs-on: ${{ matrix.runner }} | |
| timeout-minutes: 15 | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| runner: | |
| - ubuntu-latest | |
| - windows-latest | |
| - macos-15 | |
| - macos-15-intel | |
| defaults: | |
| run: | |
| working-directory: MultiplexorApp/tool/mineflayer | |
| steps: | |
| - name: Check out repository | |
| uses: actions/checkout@v6 | |
| with: | |
| ref: ${{ needs.version.outputs.sha }} | |
| - name: Set up Node.js | |
| uses: actions/setup-node@v6 | |
| with: | |
| node-version: "22" | |
| cache: npm | |
| cache-dependency-path: MultiplexorApp/tool/mineflayer/package-lock.json | |
| - name: Install Mineflayer dependencies | |
| run: npm ci --no-audit --no-fund | |
| - name: Test macOS launcher | |
| if: runner.os == 'macOS' | |
| working-directory: . | |
| shell: bash | |
| run: /bin/bash MultiplexorApp/tool/test_launcher.sh | |
| - name: Test Mineflayer harness | |
| run: npm test | |
| - name: Check Mineflayer dependencies | |
| run: npm run doctor -- --json | |
| build: | |
| name: Build ${{ matrix.name }} | |
| needs: version | |
| runs-on: ${{ matrix.runner }} | |
| timeout-minutes: 15 | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| include: | |
| - name: macOS (Apple Silicon) | |
| runner: macos-15 | |
| executable: multiplexor | |
| archive_suffix: macos-arm64.tar.gz | |
| - name: macOS (Intel) | |
| runner: macos-15-intel | |
| executable: multiplexor | |
| archive_suffix: macos-x64.tar.gz | |
| - name: Windows (x64) | |
| runner: windows-latest | |
| executable: multiplexor.exe | |
| archive_suffix: windows-x64.zip | |
| env: | |
| EXECUTABLE_NAME: ${{ matrix.executable }} | |
| ARCHIVE_NAME: multiplexor-v${{ needs.version.outputs.version }}-${{ matrix.archive_suffix }} | |
| BUILD_VERSION: ${{ needs.version.outputs.version }} | |
| steps: | |
| - name: Check out repository | |
| uses: actions/checkout@v6 | |
| with: | |
| ref: ${{ needs.version.outputs.sha }} | |
| - name: Set up Dart | |
| uses: dart-lang/setup-dart@v1 | |
| with: | |
| sdk: "3.10.0" | |
| - name: Install dependencies | |
| working-directory: MultiplexorApp | |
| run: dart pub get | |
| - name: Analyze | |
| working-directory: MultiplexorApp | |
| run: dart analyze | |
| - name: Test | |
| working-directory: MultiplexorApp | |
| run: dart test | |
| - name: Compile macOS executable | |
| if: runner.os == 'macOS' | |
| shell: bash | |
| run: dart run MultiplexorApp/tool/build_exe.dart --version "$BUILD_VERSION" --output "$RUNNER_TEMP/$EXECUTABLE_NAME" | |
| - name: Compile Windows executable | |
| if: runner.os == 'Windows' | |
| shell: pwsh | |
| run: | | |
| $outputPath = Join-Path $env:RUNNER_TEMP $env:EXECUTABLE_NAME | |
| dart run MultiplexorApp/tool/build_exe.dart --version $env:BUILD_VERSION --output $outputPath | |
| - name: Smoke-test macOS executable | |
| if: runner.os == 'macOS' | |
| shell: bash | |
| run: | | |
| actual="$("$RUNNER_TEMP/$EXECUTABLE_NAME" version | head -n 1)" | |
| test "$actual" = "Multiplexor CLI v$BUILD_VERSION" | |
| - name: Smoke-test Windows executable | |
| if: runner.os == 'Windows' | |
| shell: pwsh | |
| run: | | |
| $outputPath = Join-Path $env:RUNNER_TEMP $env:EXECUTABLE_NAME | |
| $actual = (& $outputPath version | Select-Object -First 1) | |
| if ($actual -ne "Multiplexor CLI v$env:BUILD_VERSION") { | |
| throw "Expected Multiplexor CLI v$env:BUILD_VERSION, got $actual" | |
| } | |
| - name: Smoke-test Windows create and runtime lifecycle | |
| if: runner.os == 'Windows' | |
| shell: pwsh | |
| run: | | |
| $outputPath = Join-Path $env:RUNNER_TEMP $env:EXECUTABLE_NAME | |
| $smokeRoot = Join-Path $env:RUNNER_TEMP "multiplexor-windows-smoke" | |
| $fakeTools = Join-Path $env:RUNNER_TEMP "multiplexor-fake-tools" | |
| New-Item -ItemType Directory -Force -Path $fakeTools | Out-Null | |
| $fakeJavaSource = Join-Path $fakeTools "fake_java.dart" | |
| @' | |
| import 'dart:async'; | |
| import 'dart:convert'; | |
| import 'dart:io'; | |
| Future<void> main(List<String> args) async { | |
| if (args.contains('-version')) { | |
| stderr.writeln('openjdk version "21.0.1"'); | |
| return; | |
| } | |
| if (args.contains('--installServer')) { | |
| final Directory argsDir = Directory('libraries\\fake')..createSync(recursive: true); | |
| File('${argsDir.path}\\win_args.txt').writeAsStringSync('--launchTarget forge_server'); | |
| File('run.bat').writeAsStringSync('java @libraries\\fake\\win_args.txt %*'); | |
| return; | |
| } | |
| stdout.writeln('fake java started'); | |
| if (Directory.current.path.toLowerCase().endsWith('ci-network-proxy')) { | |
| File('.ci-java-args.tmp') | |
| ..writeAsStringSync(jsonEncode(args)) | |
| ..renameSync('.ci-java-args.json'); | |
| await for (final String command in stdin | |
| .transform(utf8.decoder) | |
| .transform(const LineSplitter()) | |
| .timeout(const Duration(seconds: 60))) { | |
| if (command == 'end') { | |
| File('.ci-proxy-ended').writeAsStringSync('end'); | |
| return; | |
| } | |
| } | |
| throw StateError('Proxy input closed before end'); | |
| } | |
| final File restartMarker = File('.ci-restart-triggered'); | |
| if (Directory.current.path.toLowerCase().endsWith('ci-purpur') && | |
| !restartMarker.existsSync()) { | |
| final File restartRequest = File('.ci-request-restart'); | |
| for (var attempt = 0; attempt < 300; attempt++) { | |
| if (restartRequest.existsSync()) { | |
| restartMarker.writeAsStringSync('triggered'); | |
| return; | |
| } | |
| await Future<void>.delayed(const Duration(milliseconds: 100)); | |
| } | |
| } | |
| await Future<void>.delayed(const Duration(seconds: 30)); | |
| } | |
| '@ | Set-Content -LiteralPath $fakeJavaSource | |
| dart compile exe $fakeJavaSource -o (Join-Path $fakeTools "java.exe") | |
| $env:PATH = "$fakeTools;$env:PATH" | |
| $env:MULTIPLEXOR_FORCE_WINDOWS_LINK_FALLBACK = "1" | |
| $jarPath = Join-Path $env:RUNNER_TEMP "fake-server.jar" | |
| Set-Content -LiteralPath $jarPath -Value "fake server" | |
| $installerPath = Join-Path $env:RUNNER_TEMP "forge-installer.jar" | |
| Set-Content -LiteralPath $installerPath -Value "fake installer" | |
| function Get-ConsumerRoot([string] $Consumer) { | |
| switch ($Consumer) { | |
| "plugin" { return "plugin-consumers" } | |
| "forge" { return "forge-mod-consumers" } | |
| "fabric" { return "fabric-mod-consumers" } | |
| "neoforge" { return "neoforge-mod-consumers" } | |
| default { throw "Unknown smoke consumer: $Consumer" } | |
| } | |
| } | |
| function Invoke-ServerLifecycle { | |
| param( | |
| [string] $Consumer, | |
| [string] $Type, | |
| [string] $Name, | |
| [string] $Jar, | |
| [switch] $ExpectRestart | |
| ) | |
| & $outputPath --root $smokeRoot --consumer $Consumer server create $Name --type $Type --jar $Jar | |
| if ($LASTEXITCODE -ne 0) { throw "Windows $Type server create failed" } | |
| $consumerRoot = Get-ConsumerRoot $Consumer | |
| $instanceRoot = Join-Path $smokeRoot "consumers\$consumerRoot\instances\$Name" | |
| if ($Type -eq "forge" -or $Type -eq "neoforge") { | |
| $metadata = Get-Content -Raw -LiteralPath (Join-Path $instanceRoot ".server-source") | |
| if ($metadata -notmatch "args_file_rel=.*win_args.txt") { | |
| throw "Windows $Type metadata did not select win_args.txt: $metadata" | |
| } | |
| } | |
| & $outputPath --root $smokeRoot --consumer $Consumer instance activate $Name | |
| if ($LASTEXITCODE -ne 0) { throw "Windows $Type activation failed" } | |
| & $outputPath --root $smokeRoot --consumer $Consumer instance activate $Name | |
| if ($LASTEXITCODE -ne 0) { throw "Windows $Type re-activation failed" } | |
| & $outputPath --root $smokeRoot --consumer $Consumer runtime start $Name --no-console | |
| if ($LASTEXITCODE -ne 0) { throw "Windows $Type runtime start failed" } | |
| $status = (& $outputPath --root $smokeRoot --consumer $Consumer runtime status $Name) -join "`n" | |
| if ($LASTEXITCODE -ne 0 -or $status -notmatch "Runtime running") { | |
| throw "Windows $Type runtime status failed: $status" | |
| } | |
| if ($ExpectRestart) { | |
| $pidFile = Join-Path $smokeRoot "consumers\$consumerRoot\state\runtime\$Name.server.pid" | |
| $initialPid = (Get-Content -Raw -LiteralPath $pidFile).Trim() | |
| $restartScript = Join-Path $instanceRoot "multiplexor-restart.cmd" | |
| Start-Process -FilePath "cmd.exe" -ArgumentList @("/d", "/c", $restartScript) -WindowStyle Hidden | |
| Set-Content -LiteralPath (Join-Path $instanceRoot ".ci-request-restart") -Value "requested" | |
| $restarted = $false | |
| for ($attempt = 0; $attempt -lt 45; $attempt++) { | |
| Start-Sleep -Seconds 1 | |
| if (-not (Test-Path -LiteralPath $pidFile)) { continue } | |
| $currentPid = (Get-Content -Raw -LiteralPath $pidFile).Trim() | |
| $restartStatus = (& $outputPath --root $smokeRoot --consumer $Consumer runtime status $Name) -join "`n" | |
| if ($LASTEXITCODE -eq 0 -and $currentPid -ne $initialPid -and $restartStatus -match "Runtime running") { | |
| $restarted = $true | |
| break | |
| } | |
| } | |
| if (-not $restarted) { | |
| $restartLog = Join-Path $smokeRoot "consumers\$consumerRoot\state\runtime\$Name.restart.log" | |
| $runtimeLog = Join-Path $smokeRoot "consumers\$consumerRoot\state\runtime\$Name.log" | |
| $restartDetails = if (Test-Path -LiteralPath $restartLog) { | |
| Get-Content -Raw -LiteralPath $restartLog | |
| } else { | |
| "<missing restart log>" | |
| } | |
| $runtimeDetails = if (Test-Path -LiteralPath $runtimeLog) { | |
| Get-Content -Raw -LiteralPath $runtimeLog | |
| } else { | |
| "<missing runtime log>" | |
| } | |
| throw "Windows $Type /restart lifecycle failed.`nRestart log:`n$restartDetails`nRuntime log:`n$runtimeDetails" | |
| } | |
| } | |
| & $outputPath --root $smokeRoot --consumer $Consumer runtime stop $Name | |
| if ($LASTEXITCODE -ne 0) { throw "Windows $Type runtime stop failed" } | |
| $stoppedStatus = (& $outputPath --root $smokeRoot --consumer $Consumer runtime status $Name) -join "`n" | |
| if ($LASTEXITCODE -ne 0 -or $stoppedStatus -notmatch "Runtime stopped") { | |
| throw "Windows $Type stopped status failed: $stoppedStatus" | |
| } | |
| & $outputPath --root $smokeRoot --consumer $Consumer instance delete $Name | |
| if ($LASTEXITCODE -ne 0) { throw "Windows $Type instance delete failed" } | |
| } | |
| Invoke-ServerLifecycle -Consumer plugin -Type paper -Name ci-paper -Jar $jarPath | |
| $sharedIris = Join-Path $smokeRoot "consumers\plugin-consumers\shared-plugin-data\iris\packs" | |
| $sharedSentinel = Join-Path $sharedIris "keep.txt" | |
| Set-Content -LiteralPath $sharedSentinel -Value "preserve shared target" | |
| Invoke-ServerLifecycle -Consumer plugin -Type purpur -Name ci-purpur -Jar $jarPath -ExpectRestart | |
| foreach ($type in @("folia", "canvas", "leaf", "spigot", "custom")) { | |
| Invoke-ServerLifecycle -Consumer plugin -Type $type -Name "ci-$type" -Jar $jarPath | |
| } | |
| Invoke-ServerLifecycle -Consumer forge -Type forge -Name ci-forge -Jar $installerPath | |
| Invoke-ServerLifecycle -Consumer fabric -Type fabric -Name ci-fabric -Jar $jarPath | |
| Invoke-ServerLifecycle -Consumer neoforge -Type neoforge -Name ci-neoforge -Jar $installerPath | |
| & $outputPath --root $smokeRoot --consumer plugin server create ci-velocity-backend --type paper --jar $jarPath --mc 1.21.11 --isolated | |
| if ($LASTEXITCODE -ne 0) { throw "Windows Velocity backend create failed" } | |
| $portProbe = [System.Net.Sockets.TcpListener]::new([System.Net.IPAddress]::Loopback, 0) | |
| $portProbe.Start() | |
| $proxyPort = $portProbe.LocalEndpoint.Port | |
| $portProbe.Stop() | |
| & $outputPath --root $smokeRoot --consumer plugin network create ci-network --members ci-velocity-backend --default ci-velocity-backend --jar $jarPath --proxy-version 3.4.0 --offline --port $proxyPort | |
| if ($LASTEXITCODE -ne 0) { throw "Windows Velocity network create failed" } | |
| $proxyRoot = Join-Path $smokeRoot "consumers\plugin-consumers\instances\ci-network-proxy" | |
| try { | |
| & $outputPath --root $smokeRoot --consumer plugin runtime start ci-network-proxy --no-console | |
| if ($LASTEXITCODE -ne 0) { throw "Windows Velocity runtime start failed" } | |
| $proxyStatus = (& $outputPath --root $smokeRoot --consumer plugin runtime status ci-network-proxy) -join "`n" | |
| if ($LASTEXITCODE -ne 0 -or $proxyStatus -notmatch "Runtime running") { | |
| throw "Windows Velocity runtime status failed: $proxyStatus" | |
| } | |
| $argumentsPath = Join-Path $proxyRoot ".ci-java-args.json" | |
| $argumentsDeadline = [DateTime]::UtcNow.AddSeconds(10) | |
| while (-not (Test-Path -LiteralPath $argumentsPath)) { | |
| if ([DateTime]::UtcNow -ge $argumentsDeadline) { | |
| throw "Windows Velocity did not record its Java arguments" | |
| } | |
| Start-Sleep -Milliseconds 100 | |
| } | |
| $proxyArguments = @(Get-Content -Raw -LiteralPath $argumentsPath | ConvertFrom-Json) | |
| if ($proxyArguments.Count -ne 5 -or $proxyArguments[0] -ne "-Xms1G" -or | |
| $proxyArguments[1] -ne "-Xmx1G" -or $proxyArguments[2] -ne "-Dfile.encoding=UTF-8" -or | |
| $proxyArguments[3] -ne "-jar") { | |
| throw "Windows Velocity Java arguments were incorrect: $($proxyArguments -join ' ')" | |
| } | |
| $proxyJarPath = if ([System.IO.Path]::IsPathRooted($proxyArguments[4])) { | |
| $proxyArguments[4] | |
| } else { | |
| Join-Path $proxyRoot $proxyArguments[4] | |
| } | |
| if (-not (Test-Path -LiteralPath $proxyJarPath -PathType Leaf)) { | |
| throw "Windows Velocity Java target does not exist: $proxyJarPath" | |
| } | |
| foreach ($gameFile in @("server.properties", "eula.txt", "ops.json", "multiplexor-restart.sh", "multiplexor-restart.cmd", ".multiplexor-log4j2.xml", "plugins\iris")) { | |
| if (Test-Path -LiteralPath (Join-Path $proxyRoot $gameFile)) { | |
| throw "Windows Velocity unexpectedly created $gameFile" | |
| } | |
| } | |
| } finally { | |
| & $outputPath --root $smokeRoot --consumer plugin runtime stop ci-network-proxy | |
| if ($LASTEXITCODE -ne 0) { throw "Windows Velocity runtime stop failed" } | |
| } | |
| if (-not (Test-Path -LiteralPath (Join-Path $proxyRoot ".ci-proxy-ended"))) { | |
| throw "Windows Velocity did not receive the graceful end command" | |
| } | |
| $proxyStopped = (& $outputPath --root $smokeRoot --consumer plugin runtime status ci-network-proxy) -join "`n" | |
| if ($LASTEXITCODE -ne 0 -or $proxyStopped -notmatch "Runtime stopped") { | |
| throw "Windows Velocity stopped status failed: $proxyStopped" | |
| } | |
| & $outputPath --root $smokeRoot --consumer plugin network delete ci-network --confirm ci-network | |
| if ($LASTEXITCODE -ne 0) { throw "Windows Velocity network cleanup failed" } | |
| foreach ($instanceName in @("ci-network-proxy", "ci-velocity-backend")) { | |
| & $outputPath --root $smokeRoot --consumer plugin instance delete $instanceName | |
| if ($LASTEXITCODE -ne 0) { throw "Windows Velocity instance cleanup failed: $instanceName" } | |
| } | |
| if (-not (Test-Path -LiteralPath $sharedSentinel)) { | |
| throw "Windows junction deletion traversed the shared Iris target" | |
| } | |
| & $outputPath --root $smokeRoot --consumer plugin plugins watch-stop | |
| if ($LASTEXITCODE -ne 0) { throw "Windows plugin watcher stop failed" } | |
| foreach ($consumer in @("forge", "fabric", "neoforge")) { | |
| & $outputPath --root $smokeRoot --consumer $consumer mods watch-stop | |
| if ($LASTEXITCODE -ne 0) { throw "Windows $consumer watcher stop failed" } | |
| } | |
| - name: Package macOS executable | |
| if: runner.os == 'macOS' | |
| shell: bash | |
| run: COPYFILE_DISABLE=1 tar --format=ustar --no-xattrs -czf "$RUNNER_TEMP/$ARCHIVE_NAME" -C "$RUNNER_TEMP" "$EXECUTABLE_NAME" | |
| - name: Package Windows executable | |
| if: runner.os == 'Windows' | |
| shell: pwsh | |
| run: | | |
| $outputPath = Join-Path $env:RUNNER_TEMP $env:EXECUTABLE_NAME | |
| $archivePath = Join-Path $env:RUNNER_TEMP $env:ARCHIVE_NAME | |
| Compress-Archive -LiteralPath $outputPath -DestinationPath $archivePath -Force | |
| - name: Upload executable | |
| uses: actions/upload-artifact@v7 | |
| with: | |
| name: ${{ env.ARCHIVE_NAME }} | |
| path: ${{ runner.temp }}/${{ env.ARCHIVE_NAME }} | |
| if-no-files-found: error | |
| retention-days: 30 | |
| release: | |
| name: Publish GitHub Release | |
| if: github.event_name == 'push' | |
| needs: | |
| - version | |
| - build | |
| - gameplay | |
| runs-on: ubuntu-latest | |
| concurrency: | |
| group: executable-releases-${{ github.repository }} | |
| cancel-in-progress: false | |
| permissions: | |
| actions: read | |
| contents: write | |
| steps: | |
| - name: Download executables | |
| uses: actions/download-artifact@v8 | |
| with: | |
| pattern: multiplexor-* | |
| path: release-assets | |
| merge-multiple: true | |
| - name: Check release archives and write checksums | |
| shell: bash | |
| working-directory: release-assets | |
| env: | |
| BUILD_VERSION: ${{ needs.version.outputs.version }} | |
| run: | | |
| set -euo pipefail | |
| shopt -s nullglob dotglob | |
| archives=( | |
| "multiplexor-v$BUILD_VERSION-macos-arm64.tar.gz" | |
| "multiplexor-v$BUILD_VERSION-macos-x64.tar.gz" | |
| "multiplexor-v$BUILD_VERSION-windows-x64.zip" | |
| ) | |
| downloaded=(*) | |
| if [[ ${#downloaded[@]} -ne ${#archives[@]} ]]; then | |
| echo "Expected exactly three executable archives, got ${#downloaded[@]}." >&2 | |
| exit 1 | |
| fi | |
| for archive in "${archives[@]}"; do | |
| if [[ ! -f "$archive" || -L "$archive" || ! -s "$archive" ]]; then | |
| echo "Missing or invalid release archive: $archive" >&2 | |
| exit 1 | |
| fi | |
| done | |
| sha256sum -- "${archives[@]}" > SHA256SUMS | |
| sha256sum --check --strict SHA256SUMS | |
| - name: Publish release | |
| shell: bash | |
| env: | |
| BUILD_SHA: ${{ needs.version.outputs.sha }} | |
| BUILD_VERSION: ${{ needs.version.outputs.version }} | |
| DEFAULT_BRANCH: ${{ github.event.repository.default_branch }} | |
| GH_TOKEN: ${{ github.token }} | |
| GH_REPO: ${{ github.repository }} | |
| run: | | |
| set -euo pipefail | |
| if [[ "$GITHUB_REF_TYPE" == "tag" ]]; then | |
| release_tag="$GITHUB_REF_NAME" | |
| target_args=(--target "$BUILD_SHA" --verify-tag) | |
| else | |
| release_tag="v$BUILD_VERSION" | |
| target_args=(--target "$BUILD_SHA") | |
| fi | |
| verify_tag_target() { | |
| local tag_count tag_sha | |
| tag_count="$(gh api "repos/$GH_REPO/git/matching-refs/tags/$release_tag" \ | |
| --jq "map(select(.ref == \"refs/tags/$release_tag\")) | length")" | |
| if [[ "$tag_count" -ne 0 ]]; then | |
| tag_sha="$(gh api "repos/$GH_REPO/commits/$release_tag" --jq .sha)" | |
| if [[ "$tag_sha" != "$BUILD_SHA" ]]; then | |
| echo "Release tag $release_tag points to $tag_sha, expected $BUILD_SHA." >&2 | |
| exit 1 | |
| fi | |
| elif [[ "$GITHUB_REF_TYPE" == "tag" ]]; then | |
| echo "Pushed release tag $release_tag no longer exists." >&2 | |
| exit 1 | |
| fi | |
| } | |
| verify_tag_target | |
| already_published=false | |
| if is_draft="$(gh release view "$release_tag" --json isDraft --jq .isDraft 2>/dev/null)"; then | |
| if [[ "$is_draft" == "true" ]]; then | |
| gh release edit "$release_tag" --target "$BUILD_SHA" | |
| gh release upload "$release_tag" release-assets/* --clobber | |
| else | |
| already_published=true | |
| fi | |
| else | |
| gh release create "$release_tag" release-assets/* \ | |
| --title "Multiplexor v$BUILD_VERSION" \ | |
| --generate-notes --draft \ | |
| "${target_args[@]}" | |
| fi | |
| verification_dir="$(mktemp -d)" | |
| trap 'rm -rf "$verification_dir"' EXIT | |
| gh release download "$release_tag" --dir "$verification_dir" | |
| diff --recursive --brief release-assets "$verification_dir" | |
| (cd "$verification_dir" && sha256sum --check --strict SHA256SUMS) | |
| if [[ "$already_published" == "true" ]]; then | |
| echo "Release $release_tag is already published with these exact assets." | |
| exit 0 | |
| fi | |
| verify_tag_target | |
| if [[ "$GITHUB_REF_TYPE" == "tag" ]]; then | |
| channel_args=(--latest --prerelease=false) | |
| elif [[ "$GITHUB_REF_NAME" == "$DEFAULT_BRANCH" ]]; then | |
| current_default_sha="$(gh api "repos/$GH_REPO/commits/$DEFAULT_BRANCH" --jq .sha)" | |
| if [[ "$current_default_sha" == "$BUILD_SHA" ]]; then | |
| channel_args=(--latest --prerelease=false) | |
| else | |
| channel_args=(--latest=false --prerelease) | |
| fi | |
| else | |
| channel_args=(--latest=false --prerelease) | |
| fi | |
| gh release edit "$release_tag" --draft=false \ | |
| "${target_args[@]}" "${channel_args[@]}" |