Bump the nuget-minor-patch group with 5 updates #808
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: Build & Release | |
| on: | |
| push: | |
| branches: [ main, develop ] | |
| tags: [ 'v*' ] | |
| pull_request: | |
| branches: [ main ] | |
| types: [ opened, synchronize, reopened, labeled ] | |
| workflow_dispatch: | |
| inputs: | |
| publish_artifacts: | |
| description: 'Build, sign, notarize, and attach platform artifacts' | |
| required: false | |
| default: false | |
| type: boolean | |
| env: | |
| DOTNET_NOLOGO: true | |
| DOTNET_CLI_TELEMETRY_OPTOUT: true | |
| jobs: | |
| build-test: | |
| name: Build & Test | |
| runs-on: macos-26 | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@v4 | |
| - name: Select Xcode | |
| run: | | |
| if [ -d "/Applications/Xcode_26.2.app" ]; then | |
| XCODE_PATH="/Applications/Xcode_26.2.app" | |
| elif [ -d "/Applications/Xcode_26.2.0.app" ]; then | |
| XCODE_PATH="/Applications/Xcode_26.2.0.app" | |
| else | |
| XCODE_PATH=$(ls -d /Applications/Xcode_26*.app 2>/dev/null | sort -rV | head -1) | |
| fi | |
| echo "Selected Xcode: $XCODE_PATH" | |
| sudo xcode-select -s "$XCODE_PATH" | |
| xcodebuild -version | |
| - name: Setup .NET | |
| uses: actions/setup-dotnet@v4 | |
| with: | |
| global-json-file: global.json | |
| - name: Install MAUI workload | |
| run: dotnet workload install maui --version 10.0.102 | |
| - name: Build and test libraries | |
| run: | | |
| dotnet build tests/MauiSherpa.Core.Tests --configuration Release | |
| dotnet build tests/MauiSherpa.Workloads.Tests --configuration Release | |
| dotnet build tests/MauiSherpa.AppInspector.Cli.Tests --configuration Release | |
| - name: Run tests | |
| run: | | |
| dotnet test tests/MauiSherpa.Core.Tests --configuration Release --no-build --verbosity normal --logger "trx;LogFileName=test-results.trx" | |
| dotnet test tests/MauiSherpa.Workloads.Tests --configuration Release --no-build --verbosity normal --logger "trx;LogFileName=test-results.trx" | |
| dotnet test tests/MauiSherpa.AppInspector.Cli.Tests --configuration Release --no-build --verbosity normal --logger "trx;LogFileName=test-results.trx" | |
| - name: Upload test results | |
| uses: actions/upload-artifact@v4 | |
| if: always() | |
| with: | |
| name: test-results | |
| path: '**/TestResults/*.trx' | |
| retention-days: 7 | |
| publish-macos: | |
| name: Publish macOS App | |
| runs-on: macos-26 | |
| needs: build-test | |
| if: >- | |
| github.event_name == 'push' | |
| || (github.event_name == 'workflow_dispatch' && inputs.publish_artifacts) | |
| || (github.event_name == 'pull_request' && ( | |
| contains(github.event.pull_request.labels.*.name, 'build-artifacts') | |
| || contains(github.event.pull_request.labels.*.name, 'build-artifacts-notarized'))) | |
| permissions: | |
| statuses: write | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@v4 | |
| - name: Select Xcode | |
| run: | | |
| if [ -d "/Applications/Xcode_26.2.app" ]; then | |
| XCODE_PATH="/Applications/Xcode_26.2.app" | |
| elif [ -d "/Applications/Xcode_26.2.0.app" ]; then | |
| XCODE_PATH="/Applications/Xcode_26.2.0.app" | |
| else | |
| XCODE_PATH=$(ls -d /Applications/Xcode_26*.app 2>/dev/null | sort -rV | head -1) | |
| fi | |
| echo "Selected Xcode: $XCODE_PATH" | |
| sudo xcode-select -s "$XCODE_PATH" | |
| sudo xcodebuild -license accept 2>/dev/null || true | |
| xcodebuild -version | |
| echo "MD_APPLE_SDK_ROOT=$XCODE_PATH" >> $GITHUB_ENV | |
| - name: Setup .NET | |
| uses: actions/setup-dotnet@v4 | |
| with: | |
| global-json-file: global.json | |
| - name: Install workloads | |
| run: dotnet workload install maui macos --version 10.0.102 | |
| - name: Import signing certificate | |
| if: env.APPLE_CERTIFICATE_P12 != '' | |
| env: | |
| APPLE_CERTIFICATE_P12: ${{ secrets.APPLE_CERTIFICATE_P12 }} | |
| APPLE_CERTIFICATE_PASSWORD: ${{ secrets.APPLE_CERTIFICATE_PASSWORD }} | |
| KEYCHAIN_PASSWORD: ${{ github.run_id }} | |
| run: | | |
| CERTIFICATE_PATH=$RUNNER_TEMP/certificate.p12 | |
| KEYCHAIN_PATH=$RUNNER_TEMP/app-signing.keychain-db | |
| echo "Importing signing certificate..." | |
| echo -n "$APPLE_CERTIFICATE_P12" | base64 --decode > $CERTIFICATE_PATH | |
| security create-keychain -p "$KEYCHAIN_PASSWORD" $KEYCHAIN_PATH | |
| security set-keychain-settings -lut 21600 $KEYCHAIN_PATH | |
| security unlock-keychain -p "$KEYCHAIN_PASSWORD" $KEYCHAIN_PATH | |
| security import $CERTIFICATE_PATH -P "$APPLE_CERTIFICATE_PASSWORD" -A -t cert -f pkcs12 -k $KEYCHAIN_PATH | |
| security set-key-partition-list -S apple-tool:,apple: -k "$KEYCHAIN_PASSWORD" $KEYCHAIN_PATH | |
| security list-keychain -d user -s $KEYCHAIN_PATH | |
| echo "Certificate setup complete." | |
| - name: Determine version | |
| id: version | |
| run: | | |
| SHA="${GITHUB_SHA::8}" | |
| if [[ "$GITHUB_REF" == refs/tags/v* ]]; then | |
| VER="${GITHUB_REF#refs/tags/v}" | |
| else | |
| VER=$(grep '<AppVersion>' Directory.Build.props | sed 's/.*<AppVersion>\(.*\)<\/AppVersion>.*/\1/') | |
| fi | |
| echo "app_version=$VER" >> $GITHUB_OUTPUT | |
| echo "commit_sha=$SHA" >> $GITHUB_OUTPUT | |
| echo "Version: $VER+$SHA" | |
| - name: Publish macOS App (arm64) | |
| run: | | |
| dotnet publish src/MauiSherpa.MacOS/MauiSherpa.MacOS.csproj \ | |
| -f net10.0-macos \ | |
| -c Release \ | |
| -r osx-arm64 \ | |
| -p:CreatePackage=false \ | |
| -p:AppVersion=${{ steps.version.outputs.app_version }} \ | |
| -p:AppCommitSha=${{ steps.version.outputs.commit_sha }} \ | |
| -p:SentryDsn=${{ secrets.SENTRY_DSN }} | |
| - name: Publish macOS App (x64) | |
| run: | | |
| dotnet publish src/MauiSherpa.MacOS/MauiSherpa.MacOS.csproj \ | |
| -f net10.0-macos \ | |
| -c Release \ | |
| -r osx-x64 \ | |
| -p:CreatePackage=false \ | |
| -p:AppVersion=${{ steps.version.outputs.app_version }} \ | |
| -p:AppCommitSha=${{ steps.version.outputs.commit_sha }} \ | |
| -p:SentryDsn=${{ secrets.SENTRY_DSN }} | |
| - name: Create universal binary | |
| run: | | |
| ARM64_APP="src/MauiSherpa.MacOS/bin/Release/net10.0-macos/osx-arm64/MAUI Sherpa.app" | |
| X64_APP="src/MauiSherpa.MacOS/bin/Release/net10.0-macos/osx-x64/MAUI Sherpa.app" | |
| mkdir -p ./artifacts/macos | |
| cp -R "$ARM64_APP" ./artifacts/macos/ | |
| DEST_APP="./artifacts/macos/MAUI Sherpa.app" | |
| BINARY_NAME="MauiSherpa.MacOS" | |
| # Create universal binary with lipo | |
| lipo -create \ | |
| "$ARM64_APP/Contents/MacOS/$BINARY_NAME" \ | |
| "$X64_APP/Contents/MacOS/$BINARY_NAME" \ | |
| -output "$DEST_APP/Contents/MacOS/$BINARY_NAME" | |
| # Merge any native dylibs that differ between architectures | |
| LIPO_FAILURES=0 | |
| while IFS= read -r arm64_lib; do | |
| rel_path="${arm64_lib#$ARM64_APP/}" | |
| x64_lib="$X64_APP/$rel_path" | |
| dest_lib="$DEST_APP/$rel_path" | |
| if [ -f "$x64_lib" ]; then | |
| arm64_arch=$(lipo -info "$arm64_lib" 2>/dev/null | grep -o 'arm64' || true) | |
| x64_arch=$(lipo -info "$x64_lib" 2>/dev/null | grep -o 'x86_64' || true) | |
| if [ -n "$arm64_arch" ] && [ -n "$x64_arch" ]; then | |
| if ! lipo -create "$arm64_lib" "$x64_lib" -output "$dest_lib" 2>/dev/null; then | |
| echo "::warning::lipo failed for $rel_path" | |
| LIPO_FAILURES=$((LIPO_FAILURES + 1)) | |
| fi | |
| fi | |
| fi | |
| done < <(find "$ARM64_APP/Contents" -name "*.dylib") | |
| if [ "$LIPO_FAILURES" -gt 0 ]; then | |
| echo "::warning::$LIPO_FAILURES dylib(s) failed lipo merge" | |
| fi | |
| # Copy x64-only dylibs not present in arm64 build | |
| find "$X64_APP/Contents" -name "*.dylib" | while read -r x64_lib; do | |
| rel_path="${x64_lib#$X64_APP/}" | |
| arm64_lib="$ARM64_APP/$rel_path" | |
| dest_lib="$DEST_APP/$rel_path" | |
| if [ ! -f "$arm64_lib" ] && [ ! -f "$dest_lib" ]; then | |
| mkdir -p "$(dirname "$dest_lib")" | |
| cp "$x64_lib" "$dest_lib" | |
| echo "Copied x64-only dylib: $rel_path" | |
| fi | |
| done | |
| # Handle architecture-specific managed assemblies (.dll files). | |
| # dotnet publish produces DLLs with ReadyToRun native code for the target | |
| # architecture. The PE header machine field != 0x14c (IL-only) for these. | |
| # The macOS bootstrapper adds both MonoBundle/ and .xamarin/<RID>/ to the | |
| # Trusted Platform Assemblies list. By moving arch-specific DLLs out of | |
| # MonoBundle into .xamarin/<RID>/ directories, each architecture loads only | |
| # compatible assemblies. | |
| MONO="$DEST_APP/Contents/MonoBundle" | |
| mkdir -p "$MONO/.xamarin/osx-arm64" | |
| mkdir -p "$MONO/.xamarin/osx-x64" | |
| for dll in "$MONO"/*.dll; do | |
| [ -f "$dll" ] || continue | |
| name=$(basename "$dll") | |
| # Read PE machine type: offset 0x3C has PE header offset, then +4 for signature, +0 for machine | |
| machine=$(python3 -c " | |
| import struct, sys | |
| with open(sys.argv[1], 'rb') as f: | |
| sig = f.read(2) | |
| if sig != b'MZ': | |
| print('0') | |
| sys.exit() | |
| f.seek(0x3c) | |
| pe_off = struct.unpack('<I', f.read(4))[0] | |
| f.seek(pe_off + 4) | |
| print(struct.unpack('<H', f.read(2))[0]) | |
| " "$dll" 2>/dev/null || echo "0") | |
| # 0x14c (332) = IL-only (works on any architecture) — leave in MonoBundle | |
| # 0 = unreadable/unknown — also leave in MonoBundle | |
| if [ "$machine" != "332" ] && [ "$machine" != "0" ]; then | |
| # Architecture-specific DLL: move arm64 version to .xamarin/osx-arm64/ | |
| mv "$dll" "$MONO/.xamarin/osx-arm64/$name" | |
| # Copy x64 version to .xamarin/osx-x64/ | |
| x64_dll="$X64_APP/Contents/MonoBundle/$name" | |
| if [ -f "$x64_dll" ]; then | |
| cp "$x64_dll" "$MONO/.xamarin/osx-x64/$name" | |
| fi | |
| fi | |
| done | |
| ARM64_COUNT=$(ls "$MONO/.xamarin/osx-arm64/"*.dll 2>/dev/null | wc -l | tr -d ' ') | |
| X64_COUNT=$(ls "$MONO/.xamarin/osx-x64/"*.dll 2>/dev/null | wc -l | tr -d ' ') | |
| echo "Moved $ARM64_COUNT arch-specific DLLs to .xamarin/osx-arm64/" | |
| echo "Copied $X64_COUNT arch-specific DLLs to .xamarin/osx-x64/" | |
| # Preserve runtime-specific files that aren't part of the lipo merge, such as | |
| # the Copilot CLI and bundled unxip helper under Contents/MonoBundle/runtimes/osx-x64/native/. | |
| if [ -d "$X64_APP/Contents/MonoBundle/runtimes" ]; then | |
| mkdir -p "$DEST_APP/Contents/MonoBundle/runtimes" | |
| cp -R "$X64_APP/Contents/MonoBundle/runtimes/." "$DEST_APP/Contents/MonoBundle/runtimes/" | |
| find "$DEST_APP/Contents/MonoBundle/runtimes" \( -name "copilot" -o -name "unxip" \) -type f -exec chmod +x {} \; | |
| fi | |
| # Merge deps.json from both architectures so the runtime resolver | |
| # knows about assemblies and native libraries for both RIDs. | |
| ARM64_DEPS="$ARM64_APP/Contents/MonoBundle/MauiSherpa.MacOS.deps.json" | |
| X64_DEPS="$X64_APP/Contents/MonoBundle/MauiSherpa.MacOS.deps.json" | |
| DEST_DEPS="$DEST_APP/Contents/MonoBundle/MauiSherpa.MacOS.deps.json" | |
| if [ -f "$ARM64_DEPS" ] && [ -f "$X64_DEPS" ]; then | |
| python3 -c " | |
| import json, sys | |
| with open(sys.argv[1]) as f: a = json.load(f) | |
| with open(sys.argv[2]) as f: b = json.load(f) | |
| def deep_merge(base, overlay): | |
| for k, v in overlay.items(): | |
| if k in base and isinstance(base[k], dict) and isinstance(v, dict): | |
| deep_merge(base[k], v) | |
| else: | |
| base.setdefault(k, v) | |
| for section in ['targets', 'libraries', 'runtimes']: | |
| if section in a and section in b: | |
| deep_merge(a[section], b[section]) | |
| elif section in b: | |
| a[section] = b[section] | |
| with open(sys.argv[3], 'w') as f: json.dump(a, f, indent=2) | |
| " "$ARM64_DEPS" "$X64_DEPS" "$DEST_DEPS" | |
| echo "Merged deps.json from both architectures" | |
| fi | |
| echo "Universal binary created:" | |
| lipo -info "$DEST_APP/Contents/MacOS/$BINARY_NAME" | |
| - name: Repair Copilot runtime load command | |
| run: | | |
| set -euo pipefail | |
| APP_PATH=$(find ./artifacts/macos -name "*.app" -type d | head -1) | |
| BINARY_PATH="$APP_PATH/Contents/MacOS/MauiSherpa.MacOS" | |
| # The arm64 slice ships two LC_LOAD_DYLIB entries for the Copilot runtime: | |
| # the linker records the dylib's own install name, which is the absolute | |
| # path it was built at on the copilot-agent-runtime CI runner, and the | |
| # macOS packaging adds a second, correct entry pointing into the bundle. | |
| # The absolute one does not exist on user machines, so dyld aborts at | |
| # launch (issue #232). | |
| STALE_PREFIX="/Users/runner/work/copilot-agent-runtime" | |
| BUNDLED="@executable_path/../../Contents/MonoBundle/runtimes/osx-arm64/native/libcopilot_runtime.dylib" | |
| # Equivalent spelling of the same file, relative to Contents/MacOS. | |
| # Used when $BUNDLED is already linked, because rewriting the stale entry | |
| # to that identical string produces a duplicate LC_LOAD_DYLIB, which dyld | |
| # rejects just as hard as the missing library ("duplicate linked dylib"). | |
| # install_name_tool cannot delete a load command, so the stale slot has to | |
| # be pointed at something valid rather than removed. | |
| BUNDLED_ALT="@executable_path/../MonoBundle/runtimes/osx-arm64/native/libcopilot_runtime.dylib" | |
| STALE=$(otool -arch arm64 -L "$BINARY_PATH" | awk '{print $1}' | grep -F "$STALE_PREFIX" || true) | |
| if [ -n "$STALE" ]; then | |
| if otool -arch arm64 -L "$BINARY_PATH" | awk '{print $1}' | grep -Fxq "$BUNDLED"; then | |
| TARGET="$BUNDLED_ALT" | |
| else | |
| TARGET="$BUNDLED" | |
| fi | |
| echo "Rewriting stale load command:" | |
| echo " $STALE" | |
| echo " -> $TARGET" | |
| install_name_tool -change "$STALE" "$TARGET" "$BINARY_PATH" | |
| else | |
| echo "No stale Copilot runtime load command found." | |
| fi | |
| - name: Verify Copilot runtime load commands | |
| run: | | |
| set -euo pipefail | |
| APP_PATH=$(find ./artifacts/macos -name "*.app" -type d | head -1) | |
| BINARY_PATH="$APP_PATH/Contents/MacOS/MauiSherpa.MacOS" | |
| FAILED=0 | |
| # No build-machine paths may survive into a shipped artifact. | |
| if otool -arch arm64 -L "$BINARY_PATH" | grep -F "/Users/runner/"; then | |
| echo "::error::arm64 slice still links a build-machine path" | |
| FAILED=1 | |
| fi | |
| # dyld refuses to launch an image with two identical LC_LOAD_DYLIB paths. | |
| DUPES=$(otool -arch arm64 -l "$BINARY_PATH" \ | |
| | awk '/LC_LOAD_DYLIB/,/time stamp/' | awk '/name /{print $2}' \ | |
| | sort | uniq -d || true) | |
| if [ -n "$DUPES" ]; then | |
| echo "::error::duplicate LC_LOAD_DYLIB entries in arm64 slice: $DUPES" | |
| FAILED=1 | |
| fi | |
| # Every non-system dependency must resolve inside the bundle. | |
| otool -arch arm64 -L "$BINARY_PATH" | tail -n +2 | awk '{print $1}' \ | |
| | grep -v '^/usr/lib/' | grep -v '^/System/' | while read -r dep; do | |
| case "$dep" in | |
| @executable_path/*) | |
| resolved="$APP_PATH/Contents/MacOS/${dep#@executable_path/}" | |
| if [ ! -f "$resolved" ]; then | |
| echo "::error::unresolved bundled dependency: $dep" | |
| exit 1 | |
| fi | |
| ;; | |
| @rpath/*|@loader_path/*) ;; | |
| *) | |
| echo "::error::absolute dependency outside the bundle: $dep" | |
| exit 1 | |
| ;; | |
| esac | |
| done || FAILED=1 | |
| [ "$FAILED" -eq 0 ] || exit 1 | |
| echo "arm64 load commands verified." | |
| - name: Re-sign with Hardened Runtime | |
| if: env.APPLE_CERTIFICATE_P12 != '' | |
| env: | |
| APPLE_CERTIFICATE_P12: ${{ secrets.APPLE_CERTIFICATE_P12 }} | |
| APPLE_CODESIGN_IDENTITY: ${{ secrets.APPLE_CODESIGN_IDENTITY }} | |
| run: | | |
| APP_PATH=$(find ./artifacts/macos -name "*.app" -type d | head -1) | |
| if [ -n "$APP_PATH" ]; then | |
| echo "Re-signing with hardened runtime: $APP_PATH" | |
| # Sign nested code first, then sign the app bundle last. | |
| # Avoid --deep on the final app sign: it overwrites the Copilot CLI's | |
| # Node/V8 entitlements and causes "Failed to reserve virtual memory | |
| # for CodeRange" crashes in release builds. | |
| if [ -d "$APP_PATH/Contents/MonoBundle/runtimes" ]; then | |
| find "$APP_PATH/Contents/MonoBundle/runtimes" \( -name "copilot" -o -name "unxip" \) -type f | while read -r item; do | |
| chmod +x "$item" | |
| if [ "$(basename "$item")" = "copilot" ]; then | |
| codesign --force --options runtime --timestamp \ | |
| --preserve-metadata=entitlements \ | |
| --sign "$APPLE_CODESIGN_IDENTITY" "$item" | |
| else | |
| codesign --force --options runtime --timestamp \ | |
| --sign "$APPLE_CODESIGN_IDENTITY" "$item" | |
| fi | |
| done | |
| fi | |
| # Sign all nested frameworks and dylibs first | |
| find "$APP_PATH/Contents" \( -name "*.dylib" -o -name "*.framework" \) | while read -r item; do | |
| codesign --force --options runtime --timestamp \ | |
| --sign "$APPLE_CODESIGN_IDENTITY" "$item" | |
| done | |
| # Sign the app bundle | |
| codesign --force --options runtime --timestamp \ | |
| --entitlements src/MauiSherpa.MacOS/Entitlements.plist \ | |
| --sign "$APPLE_CODESIGN_IDENTITY" \ | |
| "$APP_PATH" | |
| fi | |
| - name: Verify code signature | |
| if: env.APPLE_CERTIFICATE_P12 != '' | |
| env: | |
| APPLE_CERTIFICATE_P12: ${{ secrets.APPLE_CERTIFICATE_P12 }} | |
| run: | | |
| APP_PATH=$(find ./artifacts/macos -name "*.app" -type d | head -1) | |
| if [ -n "$APP_PATH" ]; then | |
| echo "Verifying signature for: $APP_PATH" | |
| codesign -dvv "$APP_PATH" 2>&1 | tee /tmp/codesign-output.txt | |
| if grep -q "flags=0x10000(runtime)" /tmp/codesign-output.txt; then | |
| echo "✅ Hardened Runtime is enabled" | |
| else | |
| echo "::error::Hardened Runtime is NOT enabled — notarization will fail" | |
| exit 1 | |
| fi | |
| COPILOT_PATH="$APP_PATH/Contents/MonoBundle/runtimes/osx-arm64/native/copilot" | |
| if [ -f "$COPILOT_PATH" ]; then | |
| codesign -d --entitlements :- "$COPILOT_PATH" 2>&1 | tee /tmp/copilot-entitlements.txt | |
| if grep -q "com.apple.security.cs.allow-jit" /tmp/copilot-entitlements.txt \ | |
| && grep -q "com.apple.security.cs.allow-unsigned-executable-memory" /tmp/copilot-entitlements.txt \ | |
| && grep -q "com.apple.security.cs.disable-library-validation" /tmp/copilot-entitlements.txt; then | |
| echo "✅ Copilot helper entitlements are preserved" | |
| else | |
| echo "::error::Copilot helper entitlements are missing — the CLI will crash in release builds" | |
| exit 1 | |
| fi | |
| fi | |
| fi | |
| - name: Create ZIP archive | |
| run: | | |
| APP_PATH=$(find ./artifacts/macos -name "*.app" -type d | head -1) | |
| if [ -n "$APP_PATH" ]; then | |
| ditto -c -k --keepParent "$APP_PATH" ./artifacts/MAUI-Sherpa.macos.zip | |
| echo "Created archive: ./artifacts/MAUI-Sherpa.macos.zip" | |
| ls -lh ./artifacts/MAUI-Sherpa.macos.zip | |
| else | |
| echo "No .app bundle found" | |
| exit 1 | |
| fi | |
| - name: Upload macOS App | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: MAUI-Sherpa.macos | |
| path: ./artifacts/MAUI-Sherpa.macos.zip | |
| retention-days: 30 | |
| - name: Set commit status with artifact link | |
| if: ${{ always() && (github.event_name != 'pull_request' || !github.event.pull_request.head.repo.fork) }} | |
| env: | |
| GH_TOKEN: ${{ github.token }} | |
| run: | | |
| ARTIFACT_URL="https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}" | |
| if [ -f "./artifacts/MAUI-Sherpa.macos.zip" ]; then | |
| STATE="success" | |
| DESC="MAUI Sherpa macOS app built successfully" | |
| else | |
| STATE="failure" | |
| DESC="MAUI Sherpa macOS app build failed" | |
| fi | |
| gh api repos/${{ github.repository }}/statuses/${{ github.sha }} \ | |
| -f state="$STATE" \ | |
| -f target_url="$ARTIFACT_URL" \ | |
| -f description="$DESC" \ | |
| -f context="MAUI Sherpa / Download macOS App" | |
| - name: Cleanup keychain | |
| if: always() | |
| run: | | |
| if [ -f "$RUNNER_TEMP/app-signing.keychain-db" ]; then | |
| security delete-keychain $RUNNER_TEMP/app-signing.keychain-db | |
| fi | |
| publish-windows: | |
| name: Publish Windows App | |
| runs-on: windows-latest | |
| needs: build-test | |
| if: >- | |
| github.event_name == 'push' | |
| || (github.event_name == 'workflow_dispatch' && inputs.publish_artifacts) | |
| || (github.event_name == 'pull_request' && ( | |
| contains(github.event.pull_request.labels.*.name, 'build-artifacts') | |
| || contains(github.event.pull_request.labels.*.name, 'build-artifacts-notarized'))) | |
| permissions: | |
| statuses: write | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@v4 | |
| - name: Setup .NET | |
| uses: actions/setup-dotnet@v4 | |
| with: | |
| global-json-file: global.json | |
| - name: Install MAUI workload | |
| run: dotnet workload install maui --version 10.0.102 | |
| - name: Determine version | |
| id: version | |
| shell: bash | |
| run: | | |
| SHA="${GITHUB_SHA::8}" | |
| if [[ "$GITHUB_REF" == refs/tags/v* ]]; then | |
| VER="${GITHUB_REF#refs/tags/v}" | |
| else | |
| VER=$(grep '<AppVersion>' Directory.Build.props | sed 's/.*<AppVersion>\(.*\)<\/AppVersion>.*/\1/') | |
| fi | |
| echo "app_version=$VER" >> $GITHUB_OUTPUT | |
| echo "commit_sha=$SHA" >> $GITHUB_OUTPUT | |
| echo "Version: $VER+$SHA" | |
| - name: Publish Windows App (x64) | |
| run: | | |
| dotnet publish src/MauiSherpa/MauiSherpa.csproj ` | |
| -f net10.0-windows10.0.19041.0 ` | |
| -c Release ` | |
| -p:RuntimeIdentifierOverride=win-x64 ` | |
| -p:WindowsPackageType=None ` | |
| -p:WindowsAppSDKSelfContained=true ` | |
| --self-contained true ` | |
| -p:AppVersion=${{ steps.version.outputs.app_version }} ` | |
| -p:AppCommitSha=${{ steps.version.outputs.commit_sha }} ` | |
| -p:SentryDsn=${{ secrets.SENTRY_DSN }} ` | |
| -o ./artifacts/win-x64 | |
| - name: Publish Windows App (arm64) | |
| run: | | |
| dotnet publish src/MauiSherpa/MauiSherpa.csproj ` | |
| -f net10.0-windows10.0.19041.0 ` | |
| -c Release ` | |
| -p:RuntimeIdentifierOverride=win-arm64 ` | |
| -p:WindowsPackageType=None ` | |
| -p:WindowsAppSDKSelfContained=true ` | |
| --self-contained true ` | |
| -p:AppVersion=${{ steps.version.outputs.app_version }} ` | |
| -p:AppCommitSha=${{ steps.version.outputs.commit_sha }} ` | |
| -p:SentryDsn=${{ secrets.SENTRY_DSN }} ` | |
| -o ./artifacts/win-arm64 | |
| - name: Create ZIP archives | |
| run: | | |
| $x64Dir = "./artifacts/win-x64" | |
| $x64Exe = Join-Path $x64Dir "MauiSherpa.exe" | |
| if (-not (Test-Path $x64Exe -PathType Leaf)) { | |
| throw "Expected self-contained executable at $x64Exe" | |
| } | |
| Compress-Archive -Path "$x64Dir/*" -DestinationPath ./artifacts/MAUI-Sherpa.win-x64.zip | |
| Write-Host "Created: MAUI-Sherpa.win-x64.zip" | |
| Get-Item ./artifacts/MAUI-Sherpa.win-x64.zip | Format-List Name, Length | |
| $arm64Dir = "./artifacts/win-arm64" | |
| $arm64Exe = Join-Path $arm64Dir "MauiSherpa.exe" | |
| if (-not (Test-Path $arm64Exe -PathType Leaf)) { | |
| throw "Expected self-contained executable at $arm64Exe" | |
| } | |
| Compress-Archive -Path "$arm64Dir/*" -DestinationPath ./artifacts/MAUI-Sherpa.win-arm64.zip | |
| Write-Host "Created: MAUI-Sherpa.win-arm64.zip" | |
| Get-Item ./artifacts/MAUI-Sherpa.win-arm64.zip | Format-List Name, Length | |
| - name: Create Windows installers | |
| shell: pwsh | |
| run: | | |
| function Find-InnoCompiler { | |
| @( | |
| "${env:ProgramFiles(x86)}\Inno Setup 6\ISCC.exe" | |
| "$env:ProgramFiles\Inno Setup 6\ISCC.exe" | |
| "$env:LOCALAPPDATA\Programs\Inno Setup 6\ISCC.exe" | |
| ) | Where-Object { Test-Path $_ -PathType Leaf } | Select-Object -First 1 | |
| } | |
| $compiler = Find-InnoCompiler | |
| if ([string]::IsNullOrWhiteSpace($compiler)) { | |
| winget install --id JRSoftware.InnoSetup --exact --source winget --accept-package-agreements --accept-source-agreements --disable-interactivity | |
| if ($LASTEXITCODE -ne 0) { | |
| throw "Failed to install Inno Setup." | |
| } | |
| $compiler = Find-InnoCompiler | |
| } | |
| if ([string]::IsNullOrWhiteSpace($compiler)) { | |
| throw "Inno Setup compiler was not found after installation." | |
| } | |
| $outputDir = (Resolve-Path "./artifacts").Path | |
| $version = "${{ steps.version.outputs.app_version }}" | |
| foreach ($arch in @("x64", "arm64")) { | |
| $sourceDir = (Resolve-Path "./artifacts/win-$arch").Path | |
| & $compiler ` | |
| "/DMyAppVersion=$version" ` | |
| "/DMyAppArch=$arch" ` | |
| "/DMySourceDir=$sourceDir" ` | |
| "/DMyOutputDir=$outputDir" ` | |
| "build/windows/MauiSherpa.iss" | |
| if ($LASTEXITCODE -ne 0) { | |
| throw "Failed to create the Windows $arch installer." | |
| } | |
| $installer = "./artifacts/MAUI-Sherpa.Setup.win-$arch.exe" | |
| if (-not (Test-Path $installer -PathType Leaf)) { | |
| throw "Expected installer at $installer." | |
| } | |
| Get-Item $installer | Format-List Name, Length | |
| } | |
| - name: Upload Windows App (x64) | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: MAUI-Sherpa.win-x64 | |
| path: | | |
| ./artifacts/MAUI-Sherpa.win-x64.zip | |
| ./artifacts/MAUI-Sherpa.Setup.win-x64.exe | |
| retention-days: 30 | |
| - name: Upload Windows App (arm64) | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: MAUI-Sherpa.win-arm64 | |
| path: | | |
| ./artifacts/MAUI-Sherpa.win-arm64.zip | |
| ./artifacts/MAUI-Sherpa.Setup.win-arm64.exe | |
| retention-days: 30 | |
| - name: Set commit status with artifact link | |
| if: ${{ always() && (github.event_name != 'pull_request' || !github.event.pull_request.head.repo.fork) }} | |
| shell: bash | |
| env: | |
| GH_TOKEN: ${{ github.token }} | |
| run: | | |
| ARTIFACT_URL="https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}" | |
| if [ -f "./artifacts/MAUI-Sherpa.win-x64.zip" ]; then | |
| STATE="success" | |
| DESC="MAUI Sherpa Windows built successfully (x64 + arm64)" | |
| else | |
| STATE="failure" | |
| DESC="MAUI Sherpa Windows build failed" | |
| fi | |
| gh api repos/${{ github.repository }}/statuses/${{ github.sha }} \ | |
| -f state="$STATE" \ | |
| -f target_url="$ARTIFACT_URL" \ | |
| -f description="$DESC" \ | |
| -f context="MAUI Sherpa / Download Windows App" | |
| publish-linux: | |
| name: Publish Linux App (${{ matrix.arch }}) | |
| runs-on: ${{ matrix.runner }} | |
| needs: build-test | |
| if: >- | |
| github.event_name == 'push' | |
| || (github.event_name == 'workflow_dispatch' && inputs.publish_artifacts) | |
| || (github.event_name == 'pull_request' && ( | |
| contains(github.event.pull_request.labels.*.name, 'build-artifacts') | |
| || contains(github.event.pull_request.labels.*.name, 'build-artifacts-notarized'))) | |
| permissions: | |
| statuses: write | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| include: | |
| - arch: x64 | |
| runner: ubuntu-latest | |
| rid: linux-x64 | |
| - arch: arm64 | |
| runner: ubuntu-24.04-arm | |
| rid: linux-arm64 | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@v4 | |
| - name: Setup .NET | |
| uses: actions/setup-dotnet@v4 | |
| with: | |
| global-json-file: global.json | |
| - name: Install GTK4 dependencies | |
| run: | | |
| sudo apt-get update | |
| sudo apt-get install -y libgtk-4-dev libadwaita-1-dev libwebkitgtk-6.0-dev flatpak flatpak-builder | |
| - name: Install MAUI workload | |
| run: dotnet workload install maui-android --version 10.0.102 | |
| - name: Install Flatpak runtimes | |
| run: | | |
| flatpak remote-add --if-not-exists --user flathub https://dl.flathub.org/repo/flathub.flatpakrepo | |
| flatpak install --user -y flathub org.gnome.Platform//47 org.gnome.Sdk//47 | |
| - name: Determine version | |
| id: version | |
| run: | | |
| SHA="${GITHUB_SHA::8}" | |
| if [[ "$GITHUB_REF" == refs/tags/v* ]]; then | |
| VER="${GITHUB_REF#refs/tags/v}" | |
| else | |
| VER=$(grep '<AppVersion>' Directory.Build.props | sed 's/.*<AppVersion>\(.*\)<\/AppVersion>.*/\1/') | |
| fi | |
| echo "app_version=$VER" >> $GITHUB_OUTPUT | |
| echo "commit_sha=$SHA" >> $GITHUB_OUTPUT | |
| echo "Version: $VER+$SHA" | |
| - name: Pre-build shared dependencies | |
| run: | | |
| dotnet build src/MauiSherpa.Workloads/MauiSherpa.Workloads.csproj \ | |
| -c Release \ | |
| --disable-build-servers \ | |
| -nodeReuse:false | |
| - name: Publish Linux App (${{ matrix.arch }}) with all packages | |
| run: | | |
| dotnet publish src/MauiSherpa.LinuxGtk/MauiSherpa.LinuxGtk.csproj \ | |
| -c Release \ | |
| -r ${{ matrix.rid }} \ | |
| --self-contained true \ | |
| --disable-build-servers \ | |
| -p:AppVersion=${{ steps.version.outputs.app_version }} \ | |
| -p:AppCommitSha=${{ steps.version.outputs.commit_sha }} \ | |
| -p:SentryDsn=${{ secrets.SENTRY_DSN }} \ | |
| -p:UseSharedCompilation=false \ | |
| -nodeReuse:false \ | |
| -maxcpucount:1 \ | |
| -p:CreateAppImage=false \ | |
| -p:CreateDeb=true \ | |
| -p:CreateFlatpak=false | |
| - name: Collect artifacts | |
| run: | | |
| mkdir -p ./artifacts | |
| # tar.gz archive from publish output | |
| PUBLISH_DIR=$(find src/MauiSherpa.LinuxGtk/bin/Release -type d -name publish | grep "${{ matrix.rid }}" | head -1) | |
| if [ -n "$PUBLISH_DIR" ]; then | |
| tar -czf "./artifacts/MAUI-Sherpa.${{ matrix.rid }}.tar.gz" -C "$PUBLISH_DIR" . | |
| echo "Created: MAUI-Sherpa.${{ matrix.rid }}.tar.gz ($(du -h ./artifacts/MAUI-Sherpa.${{ matrix.rid }}.tar.gz | cut -f1))" | |
| fi | |
| # Collect generated packages (AppImage, Deb, Flatpak are in bin/{type}/, not bin/Release/) | |
| find src/MauiSherpa.LinuxGtk/bin -name "*.AppImage" -o -name "*.deb" -o -name "*.flatpak" | while read f; do | |
| cp "$f" "./artifacts/" | |
| echo "Collected: $(basename $f) ($(du -h $f | cut -f1))" | |
| done | |
| echo "=== All artifacts ===" | |
| ls -lh ./artifacts/ | |
| - name: Upload Linux tar.gz | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: MAUI-Sherpa.${{ matrix.rid }} | |
| path: ./artifacts/MAUI-Sherpa.${{ matrix.rid }}.tar.gz | |
| retention-days: 30 | |
| - name: Upload Linux .deb packages | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: MAUI-Sherpa.linux-deb-${{ matrix.arch }} | |
| path: ./artifacts/*.deb | |
| retention-days: 30 | |
| - name: Upload Linux AppImage | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: MAUI-Sherpa.linux-appimage-${{ matrix.arch }} | |
| path: ./artifacts/*.AppImage | |
| if-no-files-found: ignore | |
| retention-days: 30 | |
| - name: Upload Linux Flatpak | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: MAUI-Sherpa.linux-flatpak-${{ matrix.arch }} | |
| path: ./artifacts/*.flatpak | |
| if-no-files-found: ignore | |
| retention-days: 30 | |
| - name: Set commit status with artifact link | |
| if: ${{ always() && (github.event_name != 'pull_request' || !github.event.pull_request.head.repo.fork) }} | |
| env: | |
| GH_TOKEN: ${{ github.token }} | |
| run: | | |
| ARTIFACT_URL="https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}" | |
| if [ -f "./artifacts/MAUI-Sherpa.${{ matrix.rid }}.tar.gz" ]; then | |
| STATE="success" | |
| DESC="MAUI Sherpa Linux ${{ matrix.arch }} built successfully" | |
| else | |
| STATE="failure" | |
| DESC="MAUI Sherpa Linux ${{ matrix.arch }} build failed" | |
| fi | |
| gh api repos/${{ github.repository }}/statuses/${{ github.sha }} \ | |
| -f state="$STATE" \ | |
| -f target_url="$ARTIFACT_URL" \ | |
| -f description="$DESC" \ | |
| -f context="MAUI Sherpa / Download Linux App (${{ matrix.arch }})" | |
| publish-inspector-tool: | |
| name: Publish Inspector Tool | |
| runs-on: ubuntu-latest | |
| needs: build-test | |
| if: >- | |
| github.event_name == 'push' | |
| || (github.event_name == 'workflow_dispatch' && inputs.publish_artifacts) | |
| || (github.event_name == 'pull_request' && ( | |
| contains(github.event.pull_request.labels.*.name, 'build-artifacts') | |
| || contains(github.event.pull_request.labels.*.name, 'build-artifacts-notarized'))) | |
| permissions: | |
| contents: read | |
| statuses: write | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@v4 | |
| - name: Setup .NET | |
| uses: actions/setup-dotnet@v4 | |
| with: | |
| global-json-file: global.json | |
| - name: Determine version | |
| id: version | |
| run: | | |
| SHA="${GITHUB_SHA::8}" | |
| if [[ "$GITHUB_REF" == refs/tags/v* ]]; then | |
| VER="${GITHUB_REF#refs/tags/v}" | |
| else | |
| VER=$(grep '<AppVersion>' Directory.Build.props | sed 's/.*<AppVersion>\(.*\)<\/AppVersion>.*/\1/') | |
| fi | |
| echo "app_version=$VER" >> $GITHUB_OUTPUT | |
| echo "commit_sha=$SHA" >> $GITHUB_OUTPUT | |
| echo "Version: $VER+$SHA" | |
| - name: Pack sherpa-inspector dotnet tool | |
| run: | | |
| dotnet pack src/MauiSherpa.AppInspector.Cli/Sherpa.AppInspector.Cli.csproj \ | |
| -c Release \ | |
| -p:AppVersion=${{ steps.version.outputs.app_version }} \ | |
| -p:AppCommitSha=${{ steps.version.outputs.commit_sha }} \ | |
| -o ./artifacts/nuget | |
| - name: Upload sherpa-inspector NuGet package | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: sherpa-inspector-dotnet-tool | |
| path: ./artifacts/nuget/*.nupkg | |
| retention-days: 30 | |
| - name: Publish sherpa-inspector to NuGet.org | |
| if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/v') | |
| env: | |
| NUGET_API_KEY: ${{ secrets.NUGET_API_KEY }} | |
| run: | | |
| if [ -z "$NUGET_API_KEY" ]; then | |
| echo "::warning::NUGET_API_KEY is not configured; skipping NuGet.org publication" | |
| exit 0 | |
| fi | |
| dotnet nuget push "./artifacts/nuget/*.nupkg" \ | |
| --api-key "$NUGET_API_KEY" \ | |
| --source https://api.nuget.org/v3/index.json \ | |
| --skip-duplicate | |
| - name: Set commit status with package link | |
| if: ${{ always() && (github.event_name != 'pull_request' || !github.event.pull_request.head.repo.fork) }} | |
| env: | |
| GH_TOKEN: ${{ github.token }} | |
| run: | | |
| ARTIFACT_URL="https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}" | |
| if compgen -G "./artifacts/nuget/*.nupkg" > /dev/null; then | |
| STATE="success" | |
| DESC="sherpa-inspector dotnet tool package built" | |
| else | |
| STATE="failure" | |
| DESC="sherpa-inspector dotnet tool package failed" | |
| fi | |
| gh api repos/${{ github.repository }}/statuses/${{ github.sha }} \ | |
| -f state="$STATE" \ | |
| -f target_url="$ARTIFACT_URL" \ | |
| -f description="$DESC" \ | |
| -f context="MAUI Sherpa / sherpa-inspector Tool" | |
| notarize-macos: | |
| name: Notarize macOS App | |
| runs-on: macos-26 | |
| needs: publish-macos | |
| if: >- | |
| ((github.ref == 'refs/heads/main' || startsWith(github.ref, 'refs/tags/v')) && github.event_name == 'push') | |
| || (github.event_name == 'workflow_dispatch' && inputs.publish_artifacts) | |
| || (github.event_name == 'pull_request' | |
| && !github.event.pull_request.head.repo.fork | |
| && contains(github.event.pull_request.labels.*.name, 'build-artifacts-notarized')) | |
| permissions: | |
| statuses: write | |
| steps: | |
| - name: Download macOS artifact | |
| uses: actions/download-artifact@v4 | |
| with: | |
| name: MAUI-Sherpa.macos | |
| path: ./artifacts | |
| - name: Notarize with retry | |
| env: | |
| APPLE_NOTARIZATION_APPLE_ID: ${{ secrets.APPLE_NOTARIZATION_APPLE_ID }} | |
| APPLE_NOTARIZATION_PASSWORD: ${{ secrets.APPLE_NOTARIZATION_PASSWORD }} | |
| APPLE_NOTARIZATION_TEAM_ID: ${{ secrets.APPLE_NOTARIZATION_TEAM_ID }} | |
| run: | | |
| mkdir -p ./artifacts/macos | |
| ditto -x -k ./artifacts/MAUI-Sherpa.macos.zip ./artifacts/macos | |
| APP_PATH=$(find ./artifacts/macos -name "*.app" -type d | head -1) | |
| if [ -z "$APP_PATH" ]; then | |
| echo "::error::No .app bundle found in artifact" | |
| exit 1 | |
| fi | |
| rm ./artifacts/MAUI-Sherpa.macos.zip | |
| ditto -c -k --keepParent "$APP_PATH" ./artifacts/notarize.zip | |
| MAX_RETRIES=3 | |
| RETRY_DELAY=30 | |
| for i in $(seq 1 $MAX_RETRIES); do | |
| echo "Notarization attempt $i of $MAX_RETRIES..." | |
| RESULT_FILE=$(mktemp) | |
| if xcrun notarytool submit ./artifacts/notarize.zip \ | |
| --apple-id "$APPLE_NOTARIZATION_APPLE_ID" \ | |
| --password "$APPLE_NOTARIZATION_PASSWORD" \ | |
| --team-id "$APPLE_NOTARIZATION_TEAM_ID" \ | |
| --wait \ | |
| --output-format json >"$RESULT_FILE"; then | |
| STATUS=$(python3 -c "import json,sys; print(json.load(open(sys.argv[1], encoding='utf-8')).get('status', ''))" "$RESULT_FILE") | |
| SUBMISSION_ID=$(python3 -c "import json,sys; print(json.load(open(sys.argv[1], encoding='utf-8')).get('id', ''))" "$RESULT_FILE") | |
| rm -f "$RESULT_FILE" | |
| if [ "$STATUS" = "Accepted" ]; then | |
| echo "Notarization succeeded on attempt $i" | |
| break | |
| fi | |
| echo "::error::Notarization returned status '$STATUS'" | |
| if [ -n "$SUBMISSION_ID" ]; then | |
| xcrun notarytool log "$SUBMISSION_ID" \ | |
| --apple-id "$APPLE_NOTARIZATION_APPLE_ID" \ | |
| --password "$APPLE_NOTARIZATION_PASSWORD" \ | |
| --team-id "$APPLE_NOTARIZATION_TEAM_ID" || true | |
| fi | |
| exit 1 | |
| fi | |
| rm -f "$RESULT_FILE" | |
| if [ $i -lt $MAX_RETRIES ]; then | |
| echo "Attempt $i failed, retrying in ${RETRY_DELAY}s..." | |
| sleep $RETRY_DELAY | |
| RETRY_DELAY=$((RETRY_DELAY * 2)) | |
| else | |
| echo "::error::Notarization failed after $MAX_RETRIES attempts" | |
| exit 1 | |
| fi | |
| done | |
| rm ./artifacts/notarize.zip | |
| STAPLER_RETRIES=3 | |
| STAPLER_DELAY=15 | |
| for i in $(seq 1 $STAPLER_RETRIES); do | |
| if xcrun stapler staple "$APP_PATH"; then | |
| echo "Stapling succeeded on attempt $i" | |
| break | |
| fi | |
| if [ $i -lt $STAPLER_RETRIES ]; then | |
| echo "Stapler attempt $i failed, retrying in ${STAPLER_DELAY}s..." | |
| sleep $STAPLER_DELAY | |
| else | |
| echo "::error::Stapler failed after $STAPLER_RETRIES attempts" | |
| exit 1 | |
| fi | |
| done | |
| ditto -c -k --keepParent "$APP_PATH" ./artifacts/MAUI-Sherpa.macos.zip | |
| echo "Created notarized archive" | |
| ls -lh ./artifacts/MAUI-Sherpa.macos.zip | |
| - name: Upload notarized macOS App | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: MAUI-Sherpa.macos | |
| path: ./artifacts/MAUI-Sherpa.macos.zip | |
| overwrite: true | |
| retention-days: 30 | |
| - name: Set commit status | |
| if: ${{ always() && (github.event_name != 'pull_request' || !github.event.pull_request.head.repo.fork) }} | |
| env: | |
| GH_TOKEN: ${{ github.token }} | |
| run: | | |
| ARTIFACT_URL="https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}" | |
| if [ -f "./artifacts/MAUI-Sherpa.macos.zip" ]; then | |
| STATE="success" | |
| DESC="macOS app notarized" | |
| else | |
| STATE="failure" | |
| DESC="macOS notarization failed" | |
| fi | |
| gh api repos/${{ github.repository }}/statuses/${{ github.sha }} \ | |
| -f state="$STATE" \ | |
| -f target_url="$ARTIFACT_URL" \ | |
| -f description="$DESC" \ | |
| -f context="MAUI Sherpa / Notarized macOS App" | |
| create-release: | |
| name: Create GitHub Release | |
| runs-on: ubuntu-latest | |
| needs: [notarize-macos, publish-windows, publish-linux, publish-inspector-tool] | |
| if: startsWith(github.ref, 'refs/tags/v') | |
| permissions: | |
| contents: write | |
| steps: | |
| - name: Download macOS artifact | |
| uses: actions/download-artifact@v4 | |
| with: | |
| name: MAUI-Sherpa.macos | |
| path: ./release | |
| - name: Download Windows artifact (x64) | |
| uses: actions/download-artifact@v4 | |
| with: | |
| name: MAUI-Sherpa.win-x64 | |
| path: ./release | |
| - name: Download Windows artifact (arm64) | |
| uses: actions/download-artifact@v4 | |
| with: | |
| name: MAUI-Sherpa.win-arm64 | |
| path: ./release | |
| - name: Download Linux tar.gz (x64) | |
| uses: actions/download-artifact@v4 | |
| with: | |
| name: MAUI-Sherpa.linux-x64 | |
| path: ./release | |
| - name: Download Linux tar.gz (arm64) | |
| uses: actions/download-artifact@v4 | |
| with: | |
| name: MAUI-Sherpa.linux-arm64 | |
| path: ./release | |
| - name: Download Linux .deb packages (x64) | |
| uses: actions/download-artifact@v4 | |
| with: | |
| name: MAUI-Sherpa.linux-deb-x64 | |
| path: ./release | |
| - name: Download Linux .deb packages (arm64) | |
| uses: actions/download-artifact@v4 | |
| with: | |
| name: MAUI-Sherpa.linux-deb-arm64 | |
| path: ./release | |
| - name: Download Linux AppImage (x64) | |
| uses: actions/download-artifact@v4 | |
| continue-on-error: true | |
| with: | |
| name: MAUI-Sherpa.linux-appimage-x64 | |
| path: ./release | |
| - name: Download Linux AppImage (arm64) | |
| uses: actions/download-artifact@v4 | |
| continue-on-error: true | |
| with: | |
| name: MAUI-Sherpa.linux-appimage-arm64 | |
| path: ./release | |
| - name: Download Linux Flatpak (x64) | |
| uses: actions/download-artifact@v4 | |
| continue-on-error: true | |
| with: | |
| name: MAUI-Sherpa.linux-flatpak-x64 | |
| path: ./release | |
| - name: Download Linux Flatpak (arm64) | |
| uses: actions/download-artifact@v4 | |
| continue-on-error: true | |
| with: | |
| name: MAUI-Sherpa.linux-flatpak-arm64 | |
| path: ./release | |
| - name: Download sherpa-inspector dotnet tool package | |
| uses: actions/download-artifact@v4 | |
| with: | |
| name: sherpa-inspector-dotnet-tool | |
| path: ./release | |
| - name: Create Release | |
| uses: softprops/action-gh-release@v1 | |
| with: | |
| files: | | |
| ./release/MAUI-Sherpa.macos.zip | |
| ./release/MAUI-Sherpa.win-x64.zip | |
| ./release/MAUI-Sherpa.win-arm64.zip | |
| ./release/MAUI-Sherpa.Setup.win-x64.exe | |
| ./release/MAUI-Sherpa.Setup.win-arm64.exe | |
| ./release/MAUI-Sherpa.linux-x64.tar.gz | |
| ./release/MAUI-Sherpa.linux-arm64.tar.gz | |
| ./release/*.deb | |
| ./release/*.AppImage | |
| ./release/*.flatpak | |
| ./release/*.nupkg | |
| generate_release_notes: false | |
| draft: false | |
| prerelease: ${{ contains(github.ref, '-') }} | |
| publish-winget: | |
| name: Publish WinGet Manifest | |
| needs: [create-release] | |
| if: startsWith(github.ref, 'refs/tags/v') && !contains(github.ref, '-') | |
| uses: ./.github/workflows/publish-winget.yml | |
| with: | |
| tag: ${{ github.ref_name }} | |
| secrets: inherit | |
| update-homebrew: | |
| name: Update Homebrew Tap | |
| runs-on: macos-latest | |
| needs: [create-release] | |
| if: startsWith(github.ref, 'refs/tags/v') && !contains(github.ref, '-') | |
| steps: | |
| - name: Extract version from tag | |
| run: echo "VERSION=${GITHUB_REF#refs/tags/v}" >> $GITHUB_ENV | |
| - name: Download release asset and compute SHA256 | |
| run: | | |
| curl -fSL -o MAUI-Sherpa.macos.zip \ | |
| "https://github.com/Redth/MAUI.Sherpa/releases/download/v${VERSION}/MAUI-Sherpa.macos.zip" | |
| SHA256=$(shasum -a 256 MAUI-Sherpa.macos.zip | awk '{print $1}') | |
| echo "SHA256=$SHA256" >> $GITHUB_ENV | |
| - name: Update cask in tap repo | |
| env: | |
| HOMEBREW_TAP_TOKEN: ${{ secrets.HOMEBREW_TAP_TOKEN }} | |
| run: | | |
| git clone https://x-access-token:${HOMEBREW_TAP_TOKEN}@github.com/Redth/homebrew-tap.git | |
| cd homebrew-tap | |
| cat > Casks/maui-sherpa.rb << CASK | |
| cask "maui-sherpa" do | |
| version "${VERSION}" | |
| sha256 "${SHA256}" | |
| url "https://github.com/Redth/MAUI.Sherpa/releases/download/v#{version}/MAUI-Sherpa.macos.zip" | |
| name "MAUI Sherpa" | |
| desc "Desktop app for managing .NET MAUI developer tools" | |
| homepage "https://github.com/Redth/MAUI.Sherpa" | |
| depends_on macos: :ventura | |
| app "MAUI Sherpa.app" | |
| zap trash: [ | |
| "~/.maui-sherpa", | |
| ] | |
| end | |
| CASK | |
| # Remove leading whitespace from heredoc | |
| sed -i '' 's/^ //' Casks/maui-sherpa.rb | |
| git config user.name "github-actions[bot]" | |
| git config user.email "github-actions[bot]@users.noreply.github.com" | |
| git add Casks/maui-sherpa.rb | |
| git diff --cached --quiet && echo "No changes to commit" && exit 0 | |
| git commit -m "Update maui-sherpa to v${VERSION}" | |
| git push |