name: Release on: push: tags: - 'v*' workflow_dispatch: inputs: tag: description: 'Release tag' required: true type: string is_prerelease: description: 'Pre-release' required: false type: boolean default: false env: DOTNET_VERSION: '10.0.x' Solution_Name: LanMountainDesktop.slnx DOTNET_gcServer: 1 jobs: prepare: runs-on: ubuntu-latest outputs: version: ${{ steps.version.outputs.version }} assembly_version: ${{ steps.version.outputs.assembly_version }} informational_version: ${{ steps.version.outputs.informational_version }} tag: ${{ steps.version.outputs.tag }} checkout_ref: ${{ steps.version.outputs.checkout_ref }} is_prerelease: ${{ steps.version.outputs.is_prerelease }} release_channel: ${{ steps.version.outputs.release_channel }} steps: - name: Checkout repository metadata uses: actions/checkout@v4 with: fetch-depth: 0 - name: Get release info id: version run: | if [[ "${{ github.event_name }}" == "push" ]]; then TAG="${GITHUB_REF#refs/tags/}" CHECKOUT_REF="${GITHUB_REF}" IS_PRERELEASE="false" else RAW_TAG="${{ github.event.inputs.tag }}" if [[ "${RAW_TAG}" == refs/tags/* ]]; then TAG="${RAW_TAG#refs/tags/}" elif [[ "${RAW_TAG}" == v* ]]; then TAG="${RAW_TAG}" else TAG="v${RAW_TAG}" fi if git rev-parse -q --verify "refs/tags/${TAG}" >/dev/null; then CHECKOUT_REF="refs/tags/${TAG}" else CHECKOUT_REF="${GITHUB_SHA}" fi if [[ "${{ github.event.inputs.is_prerelease }}" == "true" ]]; then IS_PRERELEASE="true" else IS_PRERELEASE="false" fi fi VERSION="${TAG#v}" IFS='.' read -r -a VERSION_PARTS <<< "${VERSION}" while [ "${#VERSION_PARTS[@]}" -lt 4 ]; do VERSION_PARTS+=("0") done ASSEMBLY_VERSION="${VERSION_PARTS[0]}.${VERSION_PARTS[1]}.${VERSION_PARTS[2]}.${VERSION_PARTS[3]}" if [[ "${IS_PRERELEASE}" == "true" ]]; then RELEASE_CHANNEL="preview" else RELEASE_CHANNEL="stable" fi echo "tag=${TAG}" >> "$GITHUB_OUTPUT" echo "version=${VERSION}" >> "$GITHUB_OUTPUT" echo "assembly_version=${ASSEMBLY_VERSION}" >> "$GITHUB_OUTPUT" echo "informational_version=${VERSION}" >> "$GITHUB_OUTPUT" echo "checkout_ref=${CHECKOUT_REF}" >> "$GITHUB_OUTPUT" echo "is_prerelease=${IS_PRERELEASE}" >> "$GITHUB_OUTPUT" echo "release_channel=${RELEASE_CHANNEL}" >> "$GITHUB_OUTPUT" build-windows: needs: prepare runs-on: windows-latest strategy: fail-fast: false matrix: include: - arch: x64 self_contained: true suffix: '' - arch: x86 self_contained: true suffix: '' name: Build_Windows_${{ matrix.arch }}${{ matrix.suffix }} steps: - name: Checkout uses: actions/checkout@v4 with: fetch-depth: 0 submodules: recursive ref: ${{ needs.prepare.outputs.checkout_ref }} - name: Setup .NET uses: actions/setup-dotnet@v4 with: dotnet-version: ${{ env.DOTNET_VERSION }} dotnet-quality: 'preview' - name: Restore run: dotnet restore ${{ env.Solution_Name }} - name: Build run: > dotnet build ${{ env.Solution_Name }} -c Release --no-restore -v minimal -p:Version=${{ needs.prepare.outputs.version }} -p:AssemblyVersion=${{ needs.prepare.outputs.assembly_version }} -p:FileVersion=${{ needs.prepare.outputs.assembly_version }} -p:InformationalVersion=${{ needs.prepare.outputs.informational_version }} - name: Publish Launcher (AOT) run: | $version = "${{ needs.prepare.outputs.version }}" $arch = "${{ matrix.arch }}" $launcherPublishDir = "publish/launcher-win-$arch" dotnet publish LanMountainDesktop.Launcher/LanMountainDesktop.Launcher.csproj ` -c Release ` -o ./$launcherPublishDir ` --self-contained ` -r win-$arch ` -p:PublishAot=true ` -p:PublishSingleFile=true ` -p:IncludeNativeLibrariesForSelfExtract=true ` -p:EnableCompressionInSingleFile=true ` -p:DebugType=none ` -p:DebugSymbols=false ` -p:Version=${{ needs.prepare.outputs.version }} ` -p:AssemblyVersion=${{ needs.prepare.outputs.assembly_version }} ` -p:FileVersion=${{ needs.prepare.outputs.assembly_version }} ` -p:InformationalVersion=${{ needs.prepare.outputs.informational_version }} if ($LASTEXITCODE -ne 0) { Write-Error "Launcher AOT publish failed" exit 1 } shell: pwsh - name: Publish Main App run: | $selfContained = "${{ matrix.self_contained }}" -eq "true" $publishDir = if ($selfContained) { "publish/windows-${{ matrix.arch }}" } else { "publish/windows-${{ matrix.arch }}-lite" } if ($selfContained) { dotnet publish LanMountainDesktop/LanMountainDesktop.csproj ` -c Release ` -o ./$publishDir ` --self-contained ` -r win-${{ matrix.arch }} ` -p:PublishSingleFile=false ` -p:DebugType=none ` -p:DebugSymbols=false ` -p:PublishTrimmed=false ` -p:PublishReadyToRun=false ` -p:Version=${{ needs.prepare.outputs.version }} ` -p:AssemblyVersion=${{ needs.prepare.outputs.assembly_version }} ` -p:FileVersion=${{ needs.prepare.outputs.assembly_version }} ` -p:InformationalVersion=${{ needs.prepare.outputs.informational_version }} } else { dotnet publish LanMountainDesktop/LanMountainDesktop.csproj ` -c Release ` -o ./$publishDir ` --self-contained:false ` -p:PublishSingleFile=false ` -p:DebugType=none ` -p:DebugSymbols=false ` -p:PublishTrimmed=false ` -p:PublishReadyToRun=false ` -p:Version=${{ needs.prepare.outputs.version }} ` -p:AssemblyVersion=${{ needs.prepare.outputs.assembly_version }} ` -p:FileVersion=${{ needs.prepare.outputs.assembly_version }} ` -p:InformationalVersion=${{ needs.prepare.outputs.informational_version }} } shell: pwsh - name: Restructure for Launcher run: | $version = "${{ needs.prepare.outputs.version }}" $arch = "${{ matrix.arch }}" $selfContained = "${{ matrix.self_contained }}" -eq "true" $publishDir = if ($selfContained) { "publish/windows-$arch" } else { "publish/windows-$arch-lite" } $launcherPublishDir = "publish/launcher-win-$arch" $appDir = "app-$version" $newStructure = "publish-launcher/windows-$arch" New-Item -ItemType Directory -Path $newStructure -Force | Out-Null $appPath = Join-Path $newStructure $appDir Move-Item -Path $publishDir -Destination $appPath -Force if (Test-Path $launcherPublishDir) { Copy-Item -Path "$launcherPublishDir\*" -Destination $newStructure -Recurse -Force } New-Item -ItemType File -Path (Join-Path $appPath ".current") -Force | Out-Null Remove-Item -Path $publishDir -Recurse -Force -ErrorAction SilentlyContinue Remove-Item -Path $launcherPublishDir -Recurse -Force -ErrorAction SilentlyContinue Move-Item -Path $newStructure -Destination $publishDir -Force shell: pwsh - name: Install Inno Setup and 7z run: | choco install innosetup -y --no-progress choco install 7zip -y --no-progress shell: pwsh - name: Build Installer run: | $version = "${{ needs.prepare.outputs.version }}" $arch = "${{ matrix.arch }}" $suffix = "${{ matrix.suffix }}" $selfContained = "${{ matrix.self_contained }}" -eq "true" $publishDir = if ($selfContained) { "publish/windows-$arch" } else { "publish/windows-$arch-lite" } $outputDir = "build-installer" $installerScript = "LanMountainDesktop/installer/LanMountainDesktop.iss" New-Item -ItemType Directory -Path $outputDir -Force | Out-Null $candidatePaths = @( (Get-Command iscc.exe -ErrorAction SilentlyContinue | Select-Object -ExpandProperty Source -ErrorAction SilentlyContinue), "$env:ProgramFiles(x86)\Inno Setup 6\ISCC.exe", "$env:ProgramFiles\Inno Setup 6\ISCC.exe", "$env:ChocolateyInstall\lib\innosetup\tools\ISCC.exe" ) | Where-Object { $_ -and (Test-Path $_) } $isccPath = $candidatePaths | Select-Object -First 1 if (-not $isccPath) { Write-Error "Inno Setup compiler not found." exit 1 } if (-not (Test-Path $installerScript)) { Write-Error "Installer script not found: $(Join-Path $PWD $installerScript)" exit 1 } $publishDir = (Resolve-Path $publishDir).Path $outputDir = (Resolve-Path $outputDir).Path $installerScript = (Resolve-Path $installerScript).Path $compileArgs = @( "/DMyAppVersion=$version", "/DPublishDir=$publishDir", "/DMyOutputDir=$outputDir", "/DMyAppArch=$arch", "/DMyAppSuffix=$suffix", "/DIsSelfContained=$selfContained", $installerScript ) & $isccPath @compileArgs if ($LASTEXITCODE -ne 0) { Write-Error "Inno Setup compiler exited with code $LASTEXITCODE" exit 1 } $installerFile = Get-ChildItem -Path $outputDir -Filter "*.exe" -ErrorAction SilentlyContinue | Select-Object -First 1 if (-not $installerFile) { Write-Error "Failed to create installer" exit 1 } shell: pwsh - name: Package Payload Zip run: | $version = "${{ needs.prepare.outputs.version }}" $arch = "${{ matrix.arch }}" $payloadRoot = Join-Path (Join-Path $PWD "publish/windows-$arch") "app-$version" if (-not (Test-Path $payloadRoot)) { Write-Error "Payload root not found: $payloadRoot" exit 1 } $stageDir = Join-Path $PWD "payload-stage/windows-$arch" $releaseDir = Join-Path $PWD "release-assets" Remove-Item -Path $stageDir -Recurse -Force -ErrorAction SilentlyContinue New-Item -ItemType Directory -Path $stageDir -Force | Out-Null New-Item -ItemType Directory -Path $releaseDir -Force | Out-Null Get-ChildItem -Path $payloadRoot -Recurse -File | ForEach-Object { $relative = [System.IO.Path]::GetRelativePath($payloadRoot, $_.FullName).Replace('\', '/') if ($relative -eq '.current' -or $relative -eq '.partial' -or $relative -eq '.destroy' -or $relative.StartsWith('.current/') -or $relative.StartsWith('.partial/') -or $relative.StartsWith('.destroy/')) { return } $destination = Join-Path $stageDir ($relative -replace '/', [System.IO.Path]::DirectorySeparatorChar) $destinationDir = Split-Path -Path $destination -Parent if (-not [string]::IsNullOrWhiteSpace($destinationDir)) { New-Item -ItemType Directory -Path $destinationDir -Force | Out-Null } Copy-Item -Path $_.FullName -Destination $destination -Force } $payloadZip = Join-Path $releaseDir "files-windows-$arch.zip" if (Test-Path $payloadZip) { Remove-Item $payloadZip -Force } Compress-Archive -Path (Join-Path $stageDir '*') -DestinationPath $payloadZip -Force shell: pwsh - name: Upload Release Assets uses: actions/upload-artifact@v4 with: name: release-windows-${{ matrix.arch }} path: | release-assets/files-windows-${{ matrix.arch }}.zip build-installer/*.exe if-no-files-found: error retention-days: 30 build-linux: needs: prepare runs-on: ubuntu-latest name: Build_Linux steps: - name: Checkout uses: actions/checkout@v4 with: fetch-depth: 0 submodules: recursive ref: ${{ needs.prepare.outputs.checkout_ref }} - name: Install dependencies run: | sudo apt-get update sudo apt-get install -y \ libfontconfig1 libfreetype6 \ libx11-6 libxrandr2 libxinerama1 \ libxi6 libxcursor1 libxext6 \ libxrender1 libxkbcommon-x11-0 \ clang zlib1g-dev zip rsync sudo apt-get install -y libasound2t64 || sudo apt-get install -y libasound2 sudo apt-get install -y libportaudio2t64 || sudo apt-get install -y libportaudio2 sudo apt-get install -y libwebkit2gtk-4.1-dev || sudo apt-get install -y libwebkit2gtk-4.0-dev - name: Setup .NET uses: actions/setup-dotnet@v4 with: dotnet-version: ${{ env.DOTNET_VERSION }} dotnet-quality: 'preview' - name: Restore run: dotnet restore ${{ env.Solution_Name }} - name: Build run: > dotnet build ${{ env.Solution_Name }} -c Release --no-restore -v minimal -p:Version=${{ needs.prepare.outputs.version }} -p:AssemblyVersion=${{ needs.prepare.outputs.assembly_version }} -p:FileVersion=${{ needs.prepare.outputs.assembly_version }} -p:InformationalVersion=${{ needs.prepare.outputs.informational_version }} - name: Publish Launcher (AOT) run: | dotnet publish LanMountainDesktop.Launcher/LanMountainDesktop.Launcher.csproj \ -c Release \ -o ./publish/launcher-linux-x64 \ --self-contained \ -r linux-x64 \ -p:PublishAot=true \ -p:PublishSingleFile=true \ -p:IncludeNativeLibrariesForSelfExtract=true \ -p:EnableCompressionInSingleFile=true \ -p:DebugType=none \ -p:DebugSymbols=false \ -p:Version=${{ needs.prepare.outputs.version }} \ -p:AssemblyVersion=${{ needs.prepare.outputs.assembly_version }} \ -p:FileVersion=${{ needs.prepare.outputs.assembly_version }} \ -p:InformationalVersion=${{ needs.prepare.outputs.informational_version }} - name: Publish Main App run: | dotnet publish LanMountainDesktop/LanMountainDesktop.csproj \ -c Release \ -o ./publish/linux-x64-app \ --self-contained \ -r linux-x64 \ -p:PublishSingleFile=false \ -p:SelfContained=true \ -p:DebugType=none \ -p:DebugSymbols=false \ -p:PublishTrimmed=false \ -p:PublishReadyToRun=false \ -p:Version=${{ needs.prepare.outputs.version }} \ -p:AssemblyVersion=${{ needs.prepare.outputs.assembly_version }} \ -p:FileVersion=${{ needs.prepare.outputs.assembly_version }} \ -p:InformationalVersion=${{ needs.prepare.outputs.informational_version }} - name: Restructure for Launcher run: | version="${{ needs.prepare.outputs.version }}" publishDir="publish/linux-x64" appDir="app-$version" launcherDir="publish/launcher-linux-x64" mkdir -p "$publishDir" mv "publish/linux-x64-app" "$publishDir/$appDir" if [ -d "$launcherDir" ]; then cp -r "$launcherDir"/* "$publishDir/" chmod +x "$publishDir/LanMountainDesktop.Launcher" 2>/dev/null || true fi touch "$publishDir/$appDir/.current" rm -rf "$launcherDir" - name: Package as DEB run: | version="${{ needs.prepare.outputs.version }}" source="publish/linux-x64" package_name="LanMountainDesktop" package_version="${version}" arch="amd64" desktop_template="LanMountainDesktop/packaging/linux/LanMountainDesktop.desktop" icon_source="LanMountainDesktop/packaging/linux/lanmountaindesktop.png" mkdir -p "build-deb/DEBIAN" mkdir -p "build-deb/usr/local/bin" mkdir -p "build-deb/usr/share/applications" mkdir -p "build-deb/usr/share/pixmaps" mkdir -p "build-deb/usr/share/icons/hicolor/256x256/apps" cp -r "$source"/* "build-deb/usr/local/bin/" sed \ -e "s|@@EXEC@@|/usr/local/bin/LanMountainDesktop.Launcher|g" \ -e "s|@@ICON@@|lanmountaindesktop|g" \ "$desktop_template" > "build-deb/usr/share/applications/LanMountainDesktop.desktop" cp "$icon_source" "build-deb/usr/share/pixmaps/lanmountaindesktop.png" cp "$icon_source" "build-deb/usr/share/icons/hicolor/256x256/apps/lanmountaindesktop.png" { printf '%s\n' '#!/bin/sh' printf '%s\n' 'set -e' printf '%s\n' 'if command -v update-desktop-database >/dev/null 2>&1; then' printf '%s\n' ' update-desktop-database /usr/share/applications >/dev/null 2>&1 || true' printf '%s\n' 'fi' printf '%s\n' 'if command -v gtk-update-icon-cache >/dev/null 2>&1; then' printf '%s\n' ' gtk-update-icon-cache /usr/share/icons/hicolor >/dev/null 2>&1 || true' printf '%s\n' 'fi' } > "build-deb/DEBIAN/postinst" { printf '%s\n' "Package: $package_name" printf '%s\n' "Version: $package_version" printf '%s\n' "Architecture: $arch" printf '%s\n' 'Maintainer: LanMountain Team ' printf '%s\n' 'Description: LanMountain Desktop Application' printf '%s\n' ' A desktop application for LanMountain.' } > "build-deb/DEBIAN/control" chmod 755 "build-deb/usr/local/bin/LanMountainDesktop.Launcher" 2>/dev/null || chmod 755 "build-deb/usr/local/bin"/* chmod 644 "build-deb/usr/share/applications/LanMountainDesktop.desktop" chmod 644 "build-deb/usr/share/pixmaps/lanmountaindesktop.png" chmod 644 "build-deb/usr/share/icons/hicolor/256x256/apps/lanmountaindesktop.png" chmod 755 "build-deb/DEBIAN/postinst" dpkg-deb --build "build-deb" "${package_name}_${package_version}_${arch}.deb" - name: Package Payload Zip run: | version="${{ needs.prepare.outputs.version }}" payload_root="publish/linux-x64/app-$version" release_dir="$PWD/release-assets" stage_dir="$PWD/payload-stage/linux-x64" if [ ! -d "$payload_root" ]; then echo "Payload root not found: $payload_root" exit 1 fi rm -rf "$stage_dir" mkdir -p "$stage_dir" "$release_dir" rsync -a \ --exclude '.current' \ --exclude '.partial' \ --exclude '.destroy' \ "$payload_root/" "$stage_dir/" ( cd "$stage_dir" zip -qr "$release_dir/files-linux-x64.zip" . ) - name: Upload Release Assets uses: actions/upload-artifact@v4 with: name: release-linux-x64 path: | release-assets/files-linux-x64.zip *.deb if-no-files-found: error retention-days: 30 build-macos: needs: prepare runs-on: macos-latest continue-on-error: true strategy: fail-fast: false matrix: arch: [x64, arm64] name: Build_macOS_${{ matrix.arch }} steps: - name: Checkout uses: actions/checkout@v4 with: fetch-depth: 0 submodules: recursive ref: ${{ needs.prepare.outputs.checkout_ref }} - name: Install dependencies run: brew install portaudio - name: Setup .NET uses: actions/setup-dotnet@v4 with: dotnet-version: ${{ env.DOTNET_VERSION }} dotnet-quality: 'preview' - name: Restore run: dotnet restore ${{ env.Solution_Name }} - name: Build run: > dotnet build ${{ env.Solution_Name }} -c Release --no-restore -v minimal -p:Version=${{ needs.prepare.outputs.version }} -p:AssemblyVersion=${{ needs.prepare.outputs.assembly_version }} -p:FileVersion=${{ needs.prepare.outputs.assembly_version }} -p:InformationalVersion=${{ needs.prepare.outputs.informational_version }} - name: Publish Launcher (AOT) run: | dotnet publish LanMountainDesktop.Launcher/LanMountainDesktop.Launcher.csproj \ -c Release \ -o ./publish/launcher-macos-${{ matrix.arch }} \ --self-contained \ -r osx-${{ matrix.arch }} \ -p:PublishAot=true \ -p:PublishSingleFile=true \ -p:IncludeNativeLibrariesForSelfExtract=true \ -p:EnableCompressionInSingleFile=true \ -p:DebugType=none \ -p:DebugSymbols=false \ -p:Version=${{ needs.prepare.outputs.version }} \ -p:AssemblyVersion=${{ needs.prepare.outputs.assembly_version }} \ -p:FileVersion=${{ needs.prepare.outputs.assembly_version }} \ -p:InformationalVersion=${{ needs.prepare.outputs.informational_version }} - name: Publish Main App run: | dotnet publish LanMountainDesktop/LanMountainDesktop.csproj \ -c Release \ -o ./publish/macos-${{ matrix.arch }}-app \ --self-contained \ -r osx-${{ matrix.arch }} \ -p:PublishSingleFile=false \ -p:SelfContained=true \ -p:DebugType=none \ -p:DebugSymbols=false \ -p:PublishTrimmed=false \ -p:PublishReadyToRun=false \ -p:Version=${{ needs.prepare.outputs.version }} \ -p:AssemblyVersion=${{ needs.prepare.outputs.assembly_version }} \ -p:FileVersion=${{ needs.prepare.outputs.assembly_version }} \ -p:InformationalVersion=${{ needs.prepare.outputs.informational_version }} - name: Package Payload Zip run: | release_dir="$PWD/release-assets" stage_dir="$PWD/payload-stage/macos-${{ matrix.arch }}" payload_root="publish/macos-${{ matrix.arch }}-app" rm -rf "$stage_dir" mkdir -p "$stage_dir" "$release_dir" rsync -a "$payload_root/" "$stage_dir/" ( cd "$stage_dir" zip -qr "$release_dir/files-macos-${{ matrix.arch }}.zip" . ) - name: Restructure and Package as DMG continue-on-error: true run: | version="${{ needs.prepare.outputs.version }}" arch="${{ matrix.arch }}" app_name="LanMountainDesktop" package_name="${app_name}-${version}-macos-${arch}" launcherDir="publish/launcher-macos-$arch" appSourceDir="publish/macos-$arch-app" mkdir -p "${app_name}.app/Contents/MacOS" appDir="app-$version" mkdir -p "${app_name}.app/Contents/MacOS/$appDir" cp -r "$appSourceDir"/* "${app_name}.app/Contents/MacOS/$appDir/" if [ -d "$launcherDir" ]; then cp -r "$launcherDir"/* "${app_name}.app/Contents/MacOS/" chmod +x "${app_name}.app/Contents/MacOS/LanMountainDesktop.Launcher" 2>/dev/null || true fi touch "${app_name}.app/Contents/MacOS/$appDir/.current" mkdir -p "${app_name}.app/Contents/Resources" { printf '%s\n' '' printf '%s\n' '' printf '%s\n' '' printf '%s\n' '' printf '%s\n' ' CFBundleExecutable' printf '%s\n' ' LanMountainDesktop.Launcher' printf '%s\n' ' CFBundleName' printf '%s\n' ' LanMountain Desktop' printf '%s\n' ' CFBundleVersion' printf '%s\n' " $version" printf '%s\n' ' CFBundleShortVersionString' printf '%s\n' " $version" printf '%s\n' ' CFBundleIdentifier' printf '%s\n' ' com.lanmountain.desktop' printf '%s\n' ' CFBundlePackageType' printf '%s\n' ' APPL' printf '%s\n' '' printf '%s\n' '' } > "${app_name}.app/Contents/Info.plist" mkdir -p dmg-temp cp -r "${app_name}.app" dmg-temp/ hdiutil create -volname "${app_name}" -srcfolder dmg-temp -ov -format UDZO "${package_name}.dmg" - name: Upload Release Assets if: always() uses: actions/upload-artifact@v4 with: name: release-macos-${{ matrix.arch }} path: | release-assets/files-macos-${{ matrix.arch }}.zip *.dmg if-no-files-found: ignore retention-days: 30 github-release: needs: [prepare, build-windows, build-linux] runs-on: ubuntu-latest permissions: contents: write steps: - name: Download release artifacts uses: actions/download-artifact@v4 with: path: release-files pattern: release-* merge-multiple: true - name: Normalize release files run: | mkdir -p release-bundle mapfile -t downloaded_files < <(find release-files -type f) if [ "${#downloaded_files[@]}" -eq 0 ]; then echo "No downloaded release artifacts were found." exit 1 fi for file in "${downloaded_files[@]}"; do base_name="$(basename "$file")" target_path="release-bundle/$base_name" if [ -e "$target_path" ]; then echo "Duplicate release asset name detected: $base_name" echo "Conflicting file: $file" exit 1 fi cp "$file" "$target_path" done - name: Validate release files run: | echo "Release files:" find release-bundle -maxdepth 1 -type f -exec ls -lh {} \; if [ ! -f release-bundle/files-windows-x64.zip ] || [ ! -f release-bundle/files-windows-x86.zip ] || [ ! -f release-bundle/files-linux-x64.zip ]; then echo "Required payload zips are missing." exit 1 fi file_count=$(find release-bundle -maxdepth 1 -type f | wc -l) if [ "$file_count" -eq 0 ]; then echo "No release files were produced." exit 1 fi - name: Create or Update Release uses: ncipollo/release-action@v1 with: tag: ${{ needs.prepare.outputs.tag }} name: ${{ needs.prepare.outputs.tag }} allowUpdates: true draft: false prerelease: ${{ needs.prepare.outputs.is_prerelease == 'true' }} artifacts: 'release-bundle/*' body: | ## Release ${{ needs.prepare.outputs.version }} ### Installers - `LanMountainDesktop-Setup-${{ needs.prepare.outputs.version }}-x64.exe` - `LanMountainDesktop-Setup-${{ needs.prepare.outputs.version }}-x86.exe` - `LanMountainDesktop_${{ needs.prepare.outputs.version }}_amd64.deb` ### Payload Archives - `files-windows-x64.zip` - `files-windows-x86.zip` - `files-linux-x64.zip` ### macOS - macOS assets are best-effort and will not block the release. Release keeps only the stable installer and payload outputs. PLONDS delta assets and external mirror metadata are generated by follow-up workflows. token: ${{ secrets.GITHUB_TOKEN }}