fix showing text alignment #90
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 and Release | |
| on: | |
| push: | |
| branches: | |
| - master | |
| tags: | |
| - 'v*' | |
| workflow_dispatch: | |
| inputs: | |
| version: | |
| description: 'Version to release (e.g., 8.0.1)' | |
| required: true | |
| type: string | |
| auto_release: | |
| description: 'Automatically create GitHub release' | |
| required: true | |
| type: boolean | |
| default: false | |
| permissions: | |
| contents: write | |
| env: | |
| JAVA_VERSION: '17' | |
| jobs: | |
| # Prepare and cache common build artifacts | |
| prepare: | |
| runs-on: ubuntu-latest | |
| outputs: | |
| version: ${{ steps.version.outputs.version }} | |
| steps: | |
| - name: Checkout code | |
| uses: actions/checkout@v4 | |
| with: | |
| fetch-depth: 0 | |
| - name: Set up JDK | |
| uses: actions/setup-java@v4 | |
| with: | |
| java-version: ${{ env.JAVA_VERSION }} | |
| distribution: 'temurin' | |
| - name: Cache Gradle dependencies | |
| uses: actions/cache@v4 | |
| with: | |
| path: | | |
| ~/.gradle/caches | |
| ~/.gradle/wrapper | |
| key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }} | |
| restore-keys: ${{ runner.os }}-gradle- | |
| - name: Determine version | |
| id: version | |
| run: | | |
| # Use version from latest git tag, fallback to build.gradle.kts if no tags exist | |
| git fetch --tags | |
| LATEST_TAG=$(git describe --tags --abbrev=0 2>/dev/null || echo "") | |
| if [ -n "$LATEST_TAG" ]; then | |
| # Remove 'v' or 'V' prefix if present | |
| VERSION=${LATEST_TAG#v} | |
| VERSION=${VERSION#V} | |
| echo "Using version from git tag: $VERSION" | |
| else | |
| # Fallback to build.gradle.kts | |
| VERSION=$(grep '^version = ' build.gradle.kts | head -1 | sed 's/.*"\(.*\)".*/\1/') | |
| echo "No git tags found, using version from build.gradle.kts: $VERSION" | |
| fi | |
| echo "version=$VERSION" >> $GITHUB_OUTPUT | |
| echo "Building version: $VERSION" | |
| - name: Build Jubler distribution | |
| run: | | |
| gradle clean assembleDistribution | |
| - name: Upload build artifacts for platform packaging | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: jubler-base-build-${{ steps.version.outputs.version }} | |
| path: build/jubler/ | |
| retention-days: 1 | |
| # Parallel build matrix for all platforms | |
| build-platform: | |
| needs: prepare | |
| runs-on: ubuntu-latest | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| target: | |
| - name: generic | |
| artifact: "Jubler-*.tar.gz" | |
| display: "Generic (tar.gz)" | |
| - name: linux | |
| artifact: "Jubler-*-x86_64.AppImage" | |
| display: "Linux AppImage" | |
| - name: macos | |
| artifact: "Jubler-*.dmg" | |
| display: "macOS DMG" | |
| - name: windows | |
| artifact: "Jubler-*-x64.exe" | |
| display: "Windows Installer" | |
| steps: | |
| - name: Checkout code | |
| uses: actions/checkout@v4 | |
| with: | |
| fetch-depth: 0 | |
| - name: Download base build | |
| uses: actions/download-artifact@v4 | |
| with: | |
| name: jubler-base-build-${{ needs.prepare.outputs.version }} | |
| path: build/jubler/ | |
| - name: Set up Docker Buildx | |
| if: matrix.target.name == 'windows' | |
| uses: docker/setup-buildx-action@v3 | |
| with: | |
| driver: docker-container | |
| - name: Set up tools and HFS+ support | |
| run: | | |
| echo "===== Initial system info =====" | |
| uname -r | |
| echo "" | |
| echo "===== Installing packages =====" | |
| sudo apt-get update | |
| sudo apt-get install -y curl wget hfsplus hfsprogs hfsutils | |
| echo "" | |
| echo "===== Installing linux-modules-extra (contains HFS+ module) =====" | |
| KERNEL_VERSION=$(uname -r) | |
| echo "Current kernel: $KERNEL_VERSION" | |
| # Install modules-extra FIRST before trying to load | |
| sudo apt-get install -y "linux-modules-extra-${KERNEL_VERSION}" || { | |
| echo "❌ Failed to install linux-modules-extra-${KERNEL_VERSION}" | |
| echo "Available linux-modules-extra packages:" | |
| apt-cache search linux-modules-extra | grep -E "^linux-modules-extra-.*generic" | head -5 | |
| exit 1 | |
| } | |
| echo "" | |
| echo "===== Checking if HFS+ module file exists =====" | |
| if find /lib/modules/${KERNEL_VERSION} -name "hfsplus.ko*" 2>/dev/null | grep -q .; then | |
| echo "✅ HFS+ module file found:" | |
| find /lib/modules/${KERNEL_VERSION} -name "hfsplus.ko*" | |
| else | |
| echo "❌ HFS+ module file NOT found" | |
| exit 1 | |
| fi | |
| echo "" | |
| echo "===== Loading HFS+ module =====" | |
| if sudo modprobe hfsplus; then | |
| echo "✅ HFS+ module loaded successfully" | |
| else | |
| echo "❌ Failed to load HFS+ module" | |
| dmesg | tail -20 | |
| exit 1 | |
| fi | |
| echo "" | |
| echo "===== Verification =====" | |
| echo "Loaded modules:" | |
| lsmod | grep hfsplus || { echo "❌ Module not in lsmod"; exit 1; } | |
| echo "" | |
| echo "Available filesystems:" | |
| cat /proc/filesystems | grep hfs || { echo "❌ HFS not in /proc/filesystems"; exit 1; } | |
| echo "" | |
| echo "✅ HFS+ support confirmed and ready" | |
| - name: Install kpartx (for GPT partition support) | |
| run: | | |
| echo "===== Installing kpartx for GPT partition handling =====" | |
| sudo apt-get install -y kpartx | |
| echo "✅ kpartx installed" | |
| - name: Download and setup KPacker | |
| run: | | |
| mkdir -p $HOME/Works/System/bin/arch/linux-x86_64 | |
| # Download KPacker binary | |
| wget -O $HOME/Works/System/bin/arch/linux-x86_64/kpacker https://github.com/teras/KPacker/releases/download/v0.1/kpacker | |
| chmod +x $HOME/Works/System/bin/arch/linux-x86_64/kpacker | |
| # Test KPacker | |
| echo "Testing KPacker..." | |
| $HOME/Works/System/bin/arch/linux-x86_64/kpacker --help | |
| - name: Build ${{ matrix.target.display }} | |
| id: build | |
| run: | | |
| # Set environment for Docker/KPacker | |
| export DOCKER_BUILDKIT=1 | |
| export DOCKER_CLI_EXPERIMENTAL=enabled | |
| export BUILDX_NO_DEFAULT_ATTESTATIONS=1 | |
| # Build the platform target | |
| echo "Building ${{ matrix.target.name }} artifact..." | |
| export JUBLER_VERSION=${{ needs.prepare.outputs.version }} | |
| ./make.sh build ${{ matrix.target.name }} | |
| # List created artifacts | |
| echo "📦 Created artifacts:" | |
| ls -lah dist/ | |
| # Find the created artifact | |
| ARTIFACT_PATH=$(ls dist/${{ matrix.target.artifact }} 2>/dev/null | head -1) | |
| if [ -z "$ARTIFACT_PATH" ]; then | |
| echo "❌ Error: Artifact not found matching pattern: ${{ matrix.target.artifact }}" | |
| exit 1 | |
| fi | |
| echo "artifact_path=$ARTIFACT_PATH" >> $GITHUB_OUTPUT | |
| echo "✅ Successfully created: $ARTIFACT_PATH" | |
| - name: Upload ${{ matrix.target.display }} artifact | |
| id: upload | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: jubler-${{ matrix.target.name }}-${{ needs.prepare.outputs.version }} | |
| path: ${{ steps.build.outputs.artifact_path }} | |
| retention-days: 30 | |
| sign-macos: | |
| needs: [prepare, build-platform] | |
| runs-on: macos-latest | |
| if: github.repository_owner == 'teras' # Replace with your username | |
| environment: test | |
| steps: | |
| - name: Checkout code | |
| uses: actions/checkout@v4 | |
| with: | |
| fetch-depth: 0 | |
| - name: Download macOS build | |
| uses: actions/download-artifact@v4 | |
| with: | |
| name: jubler-macos-${{ needs.prepare.outputs.version }} | |
| path: dist/ | |
| - name: Sign and compress macOS DMG | |
| env: | |
| MACOS_CERTIFICATE: ${{ secrets.MACOS_CERTIFICATE }} | |
| MACOS_CERTIFICATE_PWD: ${{ secrets.MACOS_CERTIFICATE_PWD }} | |
| APPLE_NOTARY_JSON: ${{ secrets.APPLE_NOTARY_JSON }} | |
| run: | | |
| # Find the uncompressed DMG | |
| DMG_FILE=$(ls dist/Jubler-*.dmg | head -1) | |
| if [ -z "$DMG_FILE" ]; then | |
| echo "❌ Error: DMG file not found" | |
| exit 1 | |
| fi | |
| echo "📦 Found DMG: $DMG_FILE" | |
| # Setup signing certificate (required) | |
| if [ -z "$MACOS_CERTIFICATE" ]; then | |
| echo "❌ Error: MACOS_CERTIFICATE secret is not configured" | |
| exit 1 | |
| fi | |
| echo "🔐 Setting up code signing certificate..." | |
| # Generate random keychain password (temporary, only for this job) | |
| KEYCHAIN_PWD=$(openssl rand -base64 32) | |
| # Create keychain | |
| security create-keychain -p "$KEYCHAIN_PWD" build.keychain | |
| security default-keychain -s build.keychain | |
| security unlock-keychain -p "$KEYCHAIN_PWD" build.keychain | |
| security set-keychain-settings -t 3600 -u build.keychain | |
| # Import certificate | |
| echo "$MACOS_CERTIFICATE" | base64 --decode > certificate.p12 | |
| security import certificate.p12 -k build.keychain -P "$MACOS_CERTIFICATE_PWD" -T /usr/bin/codesign | |
| security set-key-partition-list -S apple-tool:,apple:,codesign: -s -k "$KEYCHAIN_PWD" build.keychain | |
| # Find certificate identity | |
| IDENTITY=$(security find-identity -v -p codesigning build.keychain | grep "Developer ID Application" | head -1 | awk '{print $2}') | |
| echo "✅ Certificate identity: $IDENTITY" | |
| # Mount DMG without allowing Finder to interfere (nobrowse prevents Finder windows) | |
| echo "📀 Mounting DMG..." | |
| hdiutil attach "$DMG_FILE" -mountpoint /Volumes/Jubler -nobrowse | |
| # Check for resource forks and extended attributes | |
| echo "🔍 Checking for resource forks and extended attributes..." | |
| echo "Files with extended attributes:" | |
| find /Volumes/Jubler/Jubler.app -type f -exec ls -la@ {} \; | grep -v "^[^@]*$" | head -20 | |
| echo "" | |
| echo "Common extended attributes found:" | |
| xattr -r /Volumes/Jubler/Jubler.app | head -20 | |
| echo "" | |
| echo "Checking for quarantine attributes:" | |
| xattr -d com.apple.quarantine /Volumes/Jubler/Jubler.app 2>/dev/null || echo "No quarantine attributes found" | |
| # Clean extended attributes and resource forks | |
| echo "🧹 Cleaning extended attributes and resource forks..." | |
| xattr -cr /Volumes/Jubler/Jubler.app | |
| # Verify cleanup | |
| echo "✅ Verifying cleanup..." | |
| if xattr -r /Volumes/Jubler/Jubler.app | grep -q .; then | |
| echo "⚠️ Some extended attributes remain:" | |
| xattr -r /Volumes/Jubler/Jubler.app | |
| else | |
| echo "✅ All extended attributes removed" | |
| fi | |
| # Sign all binaries in the .app bundle with entitlements | |
| echo "✍️ Signing app bundle contents..." | |
| find /Volumes/Jubler/Jubler.app -type f \( -name "*.dylib" -o -name "*.jnilib" -o -perm +111 \) -exec codesign --force --sign "$IDENTITY" --timestamp --options runtime --entitlements resources/macos-entitlements.plist {} \; || true | |
| # Sign native libraries inside JAR files | |
| echo "✍️ Signing native libraries inside JAR files..." | |
| # Create temporary directory for JAR extraction | |
| TEMP_DIR=$(mktemp -d) | |
| trap "rm -rf $TEMP_DIR" EXIT | |
| # Find and sign libraries in JAR files | |
| find /Volumes/Jubler/Jubler.app -name "*.jar" -type f | while read -r jar_file; do | |
| echo "Processing JAR: $jar_file" | |
| # Check if JAR contains native libraries | |
| if unzip -l "$jar_file" | grep -q "\.dylib\|\.jnilib\|\.so$"; then | |
| echo "Found native libraries in: $jar_file" | |
| # Extract entire JAR to temp directory | |
| JAR_TEMP="$TEMP_DIR/$(basename "$jar_file" .jar)" | |
| mkdir -p "$JAR_TEMP" | |
| unzip -q "$jar_file" -d "$JAR_TEMP" | |
| # Sign any native libraries found with entitlements | |
| find "$JAR_TEMP" -type f \( -name "*.dylib" -o -name "*.jnilib" -o -name "*.so" \) | while read -r lib_file; do | |
| echo "Signing library: $lib_file" | |
| codesign --force --sign "$IDENTITY" --timestamp --options runtime --entitlements resources/macos-entitlements.plist "$lib_file" || echo "Failed to sign $lib_file" | |
| done | |
| # Recreate JAR with signed libraries | |
| echo "Recreating JAR with signed libraries: $jar_file" | |
| (cd "$JAR_TEMP" && jar -cf "$jar_file" .) || echo "Failed to recreate JAR: $jar_file" | |
| # Clean up | |
| rm -rf "$JAR_TEMP" | |
| fi | |
| done | |
| # Clean up temp directory | |
| rm -rf "$TEMP_DIR" | |
| # Sign the app bundle with entitlements | |
| echo "✍️ Signing app bundle..." | |
| codesign --force --deep --sign "$IDENTITY" --timestamp --options runtime --entitlements resources/macos-entitlements.plist /Volumes/Jubler/Jubler.app | |
| # Verify signature | |
| codesign --verify --deep --strict --verbose=2 /Volumes/Jubler/Jubler.app | |
| # Unmount | |
| echo "📤 Unmounting DMG..." | |
| hdiutil detach /Volumes/Jubler -force | |
| # Sign the DMG itself | |
| echo "✍️ Signing DMG..." | |
| codesign --force --sign "$IDENTITY" --timestamp "$DMG_FILE" | |
| # Always compress DMG (convert to read-only compressed format) | |
| echo "🗜️ Compressing DMG..." | |
| COMPRESSED_DMG="${DMG_FILE%.dmg}-compressed.dmg" | |
| hdiutil convert "$DMG_FILE" -format UDZO -o "$COMPRESSED_DMG" | |
| rm "$DMG_FILE" | |
| mv "$COMPRESSED_DMG" "$DMG_FILE" | |
| echo "✅ DMG compressed successfully" | |
| - name: Upload signed macOS DMG | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: jubler-signed-macos-${{ needs.prepare.outputs.version }} | |
| path: dist/*.dmg | |
| retention-days: 30 | |
| - name: Notarize macOS DMG | |
| env: | |
| APPLE_NOTARY_JSON: ${{ secrets.APPLE_NOTARY_JSON }} | |
| run: | | |
| # Find the DMG file | |
| DMG_FILE=$(ls dist/Jubler-*.dmg | head -1) | |
| if [ -z "$DMG_FILE" ]; then | |
| echo "❌ Error: DMG file not found" | |
| exit 1 | |
| fi | |
| # Notarize (required) | |
| if [ -z "$APPLE_NOTARY_JSON" ]; then | |
| echo "❌ Error: APPLE_NOTARY_JSON secret is not configured" | |
| exit 1 | |
| fi | |
| echo "📮 Notarizing with Apple (API Key)..." | |
| # Parse JSON and extract values | |
| APPLE_API_ISSUER=$(echo "$APPLE_NOTARY_JSON" | grep -o '"issuer_id"[[:space:]]*:[[:space:]]*"[^"]*"' | sed 's/.*"issuer_id"[[:space:]]*:[[:space:]]*"\([^"]*\)".*/\1/') | |
| APPLE_API_KEY_ID=$(echo "$APPLE_NOTARY_JSON" | grep -o '"key_id"[[:space:]]*:[[:space:]]*"[^"]*"' | sed 's/.*"key_id"[[:space:]]*:[[:space:]]*"\([^"]*\)".*/\1/') | |
| APPLE_API_KEY=$(echo "$APPLE_NOTARY_JSON" | grep -o '"private_key"[[:space:]]*:[[:space:]]*"[^"]*"' | sed 's/.*"private_key"[[:space:]]*:[[:space:]]*"\([^"]*\)".*/\1/') | |
| echo " Issuer ID: $APPLE_API_ISSUER" | |
| echo " Key ID: $APPLE_API_KEY_ID" | |
| # Create private key file | |
| mkdir -p ~/private_keys | |
| echo "-----BEGIN PRIVATE KEY-----" > ~/private_keys/AuthKey_${APPLE_API_KEY_ID}.p8 | |
| echo "$APPLE_API_KEY" >> ~/private_keys/AuthKey_${APPLE_API_KEY_ID}.p8 | |
| echo "-----END PRIVATE KEY-----" >> ~/private_keys/AuthKey_${APPLE_API_KEY_ID}.p8 | |
| chmod 600 ~/private_keys/AuthKey_${APPLE_API_KEY_ID}.p8 | |
| # Submit for notarization | |
| NOTARY_OUTPUT=$(xcrun notarytool submit "$DMG_FILE" \ | |
| --key ~/private_keys/AuthKey_${APPLE_API_KEY_ID}.p8 \ | |
| --key-id "$APPLE_API_KEY_ID" \ | |
| --issuer "$APPLE_API_ISSUER" \ | |
| --wait) | |
| echo "$NOTARY_OUTPUT" | |
| # Extract submission ID for log retrieval | |
| SUBMISSION_ID=$(echo "$NOTARY_OUTPUT" | grep "id:" | head -1 | awk '{print $2}') | |
| echo "Submission ID: $SUBMISSION_ID" | |
| # Get detailed notarization logs if submission failed | |
| if echo "$NOTARY_OUTPUT" | grep -q "Invalid"; then | |
| echo "❌ Notarization failed. Fetching detailed logs..." | |
| xcrun notarytool log "$SUBMISSION_ID" \ | |
| --key ~/private_keys/AuthKey_${APPLE_API_KEY_ID}.p8 \ | |
| --key-id "$APPLE_API_KEY_ID" \ | |
| --issuer "$APPLE_API_ISSUER" | |
| fi | |
| # Clean up API key | |
| rm -rf ~/private_keys | |
| # Staple notarization ticket | |
| echo "📎 Stapling notarization ticket..." | |
| xcrun stapler staple "$DMG_FILE" | |
| echo "✅ Notarization complete" | |
| - name: Re-upload notarized macOS DMG | |
| if: success() | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: jubler-signed-macos-${{ needs.prepare.outputs.version }} | |
| path: dist/*.dmg | |
| retention-days: 30 | |
| overwrite: 'true' | |
| sign-windows: | |
| needs: [prepare, build-platform] | |
| runs-on: windows-latest | |
| if: github.repository_owner == 'teras' # Replace with your username | |
| environment: test | |
| steps: | |
| - name: Checkout code | |
| uses: actions/checkout@v4 | |
| with: | |
| fetch-depth: 0 | |
| - name: Download Windows build | |
| uses: actions/download-artifact@v4 | |
| with: | |
| name: jubler-windows-${{ needs.prepare.outputs.version }} | |
| path: dist/ | |
| - name: Prepare signed artifact directory | |
| run: New-Item -ItemType Directory -Force -Path "dist-signed" | |
| - name: Get Windows artifact ID | |
| id: get-artifact | |
| uses: actions/github-script@v7 | |
| with: | |
| script: | | |
| const artifacts = await github.rest.actions.listWorkflowRunArtifacts({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| run_id: context.runId, | |
| }); | |
| const windowsArtifact = artifacts.data.artifacts.find(a => | |
| a.name === `jubler-windows-${{ needs.prepare.outputs.version }}` | |
| ); | |
| if (!windowsArtifact) { | |
| core.setFailed('Windows artifact not found'); | |
| return; | |
| } | |
| core.setOutput('artifact-id', windowsArtifact.id); | |
| - name: Submit SignPath signing request | |
| id: signpath | |
| uses: SignPath/github-action-submit-signing-request@v1 | |
| with: | |
| api-token: ${{ secrets.SIGNPATH_API_TOKEN }} | |
| organization-id: ${{ secrets.SIGNPATH_ORGANIZATION_ID }} | |
| project-slug: Jubler | |
| signing-policy-slug: test-signing | |
| artifact-configuration-slug: jubler-exe-in-zip | |
| github-artifact-id: ${{ steps.get-artifact.outputs.artifact-id }} | |
| github-token: ${{ secrets.GITHUB_TOKEN }} | |
| output-artifact-directory: dist-signed | |
| - name: List signed artifact | |
| run: | | |
| Get-ChildItem -Path "dist-signed" | |
| - name: Upload signed Windows installer | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: jubler-signed-windows-${{ needs.prepare.outputs.version }} | |
| path: dist-signed/* | |
| retention-days: 30 | |
| release: | |
| needs: [prepare, build-platform, sign-macos, sign-windows] | |
| runs-on: ubuntu-latest | |
| if: always() && needs.prepare.result == 'success' && needs.build-platform.result == 'success' && (github.event.inputs.auto_release == 'true' || github.event_name == 'push') | |
| steps: | |
| - name: Checkout code | |
| uses: actions/checkout@v4 | |
| with: | |
| fetch-depth: 0 | |
| - name: Download all platform artifacts | |
| uses: actions/download-artifact@v4 | |
| with: | |
| pattern: jubler-*-${{ needs.prepare.outputs.version }} | |
| path: artifacts/ | |
| merge-multiple: false | |
| - name: Prepare release artifacts | |
| run: | | |
| mkdir -p release-artifacts | |
| echo "📦 Downloaded artifacts:" | |
| ls -laR artifacts/ | |
| # Copy all platform builds | |
| echo "Copying platform artifacts..." | |
| cp artifacts/jubler-generic-*/* release-artifacts/ 2>/dev/null || echo "⚠️ No generic artifacts" | |
| cp artifacts/jubler-linux-*/* release-artifacts/ 2>/dev/null || echo "⚠️ No Linux artifacts" | |
| # Use signed macOS artifact if available, otherwise use unsigned | |
| if [ -d "artifacts/jubler-signed-macos-${{ needs.prepare.outputs.version }}" ] && [ "$(ls -A artifacts/jubler-signed-macos-${{ needs.prepare.outputs.version }})" ]; then | |
| echo "✅ Using signed macOS artifact" | |
| cp artifacts/jubler-signed-macos-*/* release-artifacts/ 2>/dev/null || echo "⚠️ No signed macOS artifacts" | |
| else | |
| echo "⚠️ Using unsigned macOS artifact (signing may have failed or been skipped)" | |
| cp artifacts/jubler-macos-*/* release-artifacts/ 2>/dev/null || echo "⚠️ No macOS artifacts" | |
| fi | |
| # Use signed Windows artifact if available, otherwise use unsigned | |
| if [ -d "artifacts/jubler-signed-windows-${{ needs.prepare.outputs.version }}" ] && [ "$(ls -A artifacts/jubler-signed-windows-${{ needs.prepare.outputs.version }})" ]; then | |
| echo "✅ Using signed Windows artifact" | |
| cp artifacts/jubler-signed-windows-*/* release-artifacts/ 2>/dev/null || echo "⚠️ No signed Windows artifacts" | |
| else | |
| echo "⚠️ Using unsigned Windows artifact (signing may have failed or been skipped)" | |
| cp artifacts/jubler-windows-*/* release-artifacts/ 2>/dev/null || echo "⚠️ No Windows artifacts" | |
| fi | |
| echo "📋 Final release artifacts:" | |
| ls -lah release-artifacts/ | |
| - name: Set prerelease flag | |
| id: prerelease | |
| run: | | |
| # Always mark as prerelease (alpha) | |
| echo "prerelease=true" >> $GITHUB_OUTPUT | |
| echo "Marking as prerelease (alpha): ${{ needs.prepare.outputs.version }}" | |
| - name: Publish release | |
| uses: ncipollo/release-action@v1 | |
| with: | |
| tag: "v${{ needs.prepare.outputs.version }}" | |
| name: "Jubler v${{ needs.prepare.outputs.version }}" | |
| draft: false | |
| prerelease: ${{ steps.prerelease.outputs.prerelease }} | |
| allowUpdates: true | |
| removeArtifacts: true | |
| makeLatest: false | |
| artifacts: release-artifacts/* | |
| artifactErrorsFailBuild: false | |
| bodyFile: .github/release-template.md | |
| token: ${{ secrets.GITHUB_TOKEN }} | |
| cleanup: | |
| needs: [prepare, build-platform, sign-macos, sign-windows, release] | |
| runs-on: ubuntu-latest | |
| if: always() && (needs.release.result == 'success' || needs.release.result == 'skipped') | |
| steps: | |
| - name: Delete temporary build artifacts | |
| uses: geekyeggo/delete-artifact@v5 | |
| with: | |
| name: | | |
| jubler-base-build-${{ needs.prepare.outputs.version }} | |
| jubler-windows-${{ needs.prepare.outputs.version }} | |
| jubler-macos-${{ needs.prepare.outputs.version }} | |
| failOnError: false |