Fix YouTube handle case in README #17
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: Desktop Release | |
| on: | |
| push: | |
| branches: | |
| - main | |
| workflow_dispatch: | |
| permissions: | |
| contents: write | |
| concurrency: | |
| group: desktop-release-${{ github.ref_name }} | |
| cancel-in-progress: false | |
| jobs: | |
| validate_release_ref: | |
| name: Validate Release Ref | |
| runs-on: ubuntu-latest | |
| steps: | |
| - name: Enforce Supported Release Branches | |
| shell: bash | |
| run: | | |
| if [[ "${GITHUB_REF_NAME}" == "main" ]]; then | |
| exit 0 | |
| fi | |
| echo "Desktop releases may only run from main." >&2 | |
| exit 1 | |
| prepare_release: | |
| name: Prepare Release | |
| runs-on: ubuntu-latest | |
| needs: | |
| - validate_release_ref | |
| outputs: | |
| application_version: ${{ steps.resolve_version.outputs.application_version }} | |
| previous_tag: ${{ steps.previous_tag.outputs.value }} | |
| release_tag: ${{ steps.resolve_version.outputs.release_tag }} | |
| release_version: ${{ steps.resolve_version.outputs.display_version }} | |
| steps: | |
| - uses: actions/checkout@v6 | |
| with: | |
| fetch-depth: 0 | |
| - name: Install Dependencies | |
| uses: "./.github/steps/install_dependencies" | |
| - name: Fetch Tags | |
| shell: bash | |
| run: git fetch --tags --force | |
| - name: Capture Previous Tag | |
| id: previous_tag | |
| shell: bash | |
| run: | | |
| previous_tag="$(git tag --list 'v*' --sort=-version:refname | head -n 1)" | |
| echo "value=${previous_tag}" >> "$GITHUB_OUTPUT" | |
| - name: Resolve Release Version | |
| id: resolve_version | |
| shell: bash | |
| run: | | |
| version_prefix="$(dotnet msbuild ./DotPilot/DotPilot.csproj -getProperty:DotPilotVersionPrefix | tail -n 1 | tr -d '\r')" | |
| if [[ ! "${version_prefix}" =~ ^[0-9]+\.[0-9]+$ ]]; then | |
| echo "DotPilotVersionPrefix must be a two-segment numeric prefix. Found: '${version_prefix}'." >&2 | |
| exit 1 | |
| fi | |
| display_version="${version_prefix}.${{ github.run_number }}" | |
| { | |
| echo "display_version=${display_version}" | |
| echo "application_version=${{ github.run_number }}" | |
| echo "release_tag=v${display_version}" | |
| } >> "$GITHUB_OUTPUT" | |
| publish_macos: | |
| name: Publish macOS Release Asset | |
| runs-on: macos-latest | |
| timeout-minutes: 60 | |
| needs: | |
| - prepare_release | |
| steps: | |
| - uses: actions/checkout@v6 | |
| with: | |
| fetch-depth: 0 | |
| ref: ${{ github.sha }} | |
| - name: Install Dependencies | |
| timeout-minutes: 60 | |
| uses: "./.github/steps/install_dependencies" | |
| - name: Resolve macOS RID | |
| id: macos_rid | |
| shell: bash | |
| run: | | |
| architecture="$(uname -m)" | |
| case "${architecture}" in | |
| arm64) | |
| rid="osx-arm64" | |
| asset_suffix="macos-arm64" | |
| ;; | |
| x86_64) | |
| rid="osx-x64" | |
| asset_suffix="macos-x64" | |
| ;; | |
| *) | |
| echo "Unsupported macOS runner architecture: ${architecture}" >&2 | |
| exit 1 | |
| ;; | |
| esac | |
| echo "rid=${rid}" >> "$GITHUB_OUTPUT" | |
| echo "asset_suffix=${asset_suffix}" >> "$GITHUB_OUTPUT" | |
| - name: Publish macOS Disk Image | |
| shell: bash | |
| working-directory: ./DotPilot | |
| run: | | |
| dotnet publish DotPilot.csproj \ | |
| -c Release \ | |
| -f net10.0-desktop \ | |
| -r "${{ steps.macos_rid.outputs.rid }}" \ | |
| -warnaserror \ | |
| -m:1 \ | |
| -p:BuildInParallel=false \ | |
| -p:CI=true \ | |
| -p:SelfContained=true \ | |
| -p:PackageFormat=dmg \ | |
| -p:CodesignKey=- \ | |
| -p:DiskImageSigningKey=- \ | |
| -p:DotPilotReleaseVersion=${{ needs.prepare_release.outputs.release_version }} \ | |
| -p:DotPilotBuildNumber=${{ needs.prepare_release.outputs.application_version }} | |
| - name: Stage macOS Release Asset | |
| shell: bash | |
| run: | | |
| mkdir -p ./artifacts/releases | |
| source_path="./DotPilot/bin/Release/net10.0-desktop/${{ steps.macos_rid.outputs.rid }}/publish/DotPilot.dmg" | |
| target_path="./artifacts/releases/dotpilot-${{ needs.prepare_release.outputs.release_version }}-${{ steps.macos_rid.outputs.asset_suffix }}.dmg" | |
| if [[ ! -f "${source_path}" ]]; then | |
| echo "Expected macOS release asset was not produced: ${source_path}" >&2 | |
| exit 1 | |
| fi | |
| cp "${source_path}" "${target_path}" | |
| - name: Upload macOS Release Artifact | |
| uses: actions/upload-artifact@v7 | |
| with: | |
| name: dotpilot-release-macos | |
| path: ./artifacts/releases/*.dmg | |
| if-no-files-found: error | |
| retention-days: 14 | |
| publish_windows: | |
| name: Publish Windows Release Asset | |
| runs-on: windows-latest | |
| timeout-minutes: 60 | |
| needs: | |
| - prepare_release | |
| steps: | |
| - uses: actions/checkout@v6 | |
| with: | |
| fetch-depth: 0 | |
| ref: ${{ github.sha }} | |
| - name: Install Dependencies | |
| timeout-minutes: 60 | |
| uses: "./.github/steps/install_dependencies" | |
| - name: Publish Windows Single-File Executable | |
| shell: pwsh | |
| working-directory: .\DotPilot | |
| run: > | |
| dotnet publish DotPilot.csproj | |
| -c Release | |
| -f net10.0-desktop | |
| -r win-x64 | |
| -warnaserror | |
| -m:1 | |
| -p:BuildInParallel=false | |
| -p:CI=true | |
| -p:SelfContained=true | |
| -p:PublishSingleFile=true | |
| -p:IncludeNativeLibrariesForSelfExtract=true | |
| -p:IncludeAllContentForSelfExtract=true | |
| -p:DotPilotReleaseVersion=${{ needs.prepare_release.outputs.release_version }} | |
| -p:DotPilotBuildNumber=${{ needs.prepare_release.outputs.application_version }} | |
| - name: Stage Windows Release Asset | |
| shell: pwsh | |
| run: | | |
| New-Item -ItemType Directory -Force -Path .\artifacts\releases | Out-Null | |
| $sourcePath = ".\DotPilot\bin\Release\net10.0-desktop\win-x64\publish\DotPilot.exe" | |
| $targetPath = ".\artifacts\releases\dotpilot-${{ needs.prepare_release.outputs.release_version }}-windows-x64.exe" | |
| if (-not (Test-Path $sourcePath)) { | |
| throw "Expected Windows release asset was not produced: $sourcePath" | |
| } | |
| Copy-Item $sourcePath $targetPath -Force | |
| - name: Upload Windows Release Artifact | |
| uses: actions/upload-artifact@v7 | |
| with: | |
| name: dotpilot-release-windows | |
| path: ./artifacts/releases/*.exe | |
| if-no-files-found: error | |
| retention-days: 14 | |
| publish_linux: | |
| name: Publish Linux Snap Release Asset | |
| runs-on: ubuntu-24.04 | |
| timeout-minutes: 60 | |
| needs: | |
| - prepare_release | |
| steps: | |
| - uses: actions/checkout@v6 | |
| with: | |
| fetch-depth: 0 | |
| ref: ${{ github.sha }} | |
| - name: Install Dependencies | |
| timeout-minutes: 60 | |
| uses: "./.github/steps/install_dependencies" | |
| - name: Install Linux Snap Packaging Prerequisites | |
| shell: bash | |
| run: | | |
| sudo apt-get update | |
| sudo apt-get install -y snapd | |
| sudo systemctl start snapd.socket | |
| sudo snap wait system seed.loaded | |
| sudo snap install core24 | |
| sudo snap install multipass | |
| sudo snap install lxd | |
| sudo snap install snapcraft --classic | |
| sudo lxd init --minimal | |
| - name: Publish Linux Snap Package | |
| shell: bash | |
| working-directory: ./DotPilot | |
| run: | | |
| sudo dotnet publish DotPilot.csproj \ | |
| -c Release \ | |
| -f net10.0-desktop \ | |
| -r linux-x64 \ | |
| -warnaserror \ | |
| -m:1 \ | |
| -p:BuildInParallel=false \ | |
| -p:CI=true \ | |
| -p:SelfContained=true \ | |
| -p:PackageFormat=snap \ | |
| -p:UnoSnapcraftAdditionalParameters=--destructive-mode \ | |
| -p:DotPilotReleaseVersion=${{ needs.prepare_release.outputs.release_version }} \ | |
| -p:DotPilotBuildNumber=${{ needs.prepare_release.outputs.application_version }} | |
| sudo chown -R "$USER:$USER" ./bin/Release/net10.0-desktop/linux-x64 | |
| - name: Stage Linux Release Asset | |
| shell: bash | |
| run: | | |
| mkdir -p ./artifacts/releases | |
| source_path="$(find ./DotPilot/bin/Release/net10.0-desktop/linux-x64/publish -maxdepth 1 -type f -name '*.snap' | sort | head -n 1)" | |
| target_path="./artifacts/releases/dotpilot-${{ needs.prepare_release.outputs.release_version }}-linux-x64.snap" | |
| if [[ -z "${source_path}" || ! -f "${source_path}" ]]; then | |
| echo "Expected Linux snap release asset was not produced." >&2 | |
| exit 1 | |
| fi | |
| cp "${source_path}" "${target_path}" | |
| - name: Upload Linux Release Artifact | |
| uses: actions/upload-artifact@v7 | |
| with: | |
| name: dotpilot-release-linux | |
| path: ./artifacts/releases/*.snap | |
| if-no-files-found: error | |
| retention-days: 14 | |
| create_release: | |
| name: Create GitHub Release | |
| runs-on: ubuntu-latest | |
| needs: | |
| - prepare_release | |
| - publish_macos | |
| - publish_windows | |
| - publish_linux | |
| steps: | |
| - uses: actions/checkout@v6 | |
| with: | |
| fetch-depth: 0 | |
| ref: ${{ github.sha }} | |
| - name: Fetch Tags | |
| shell: bash | |
| run: git fetch --tags --force | |
| - name: Download Release Artifacts | |
| uses: actions/download-artifact@v8 | |
| with: | |
| path: ./artifacts/release-assets | |
| - name: Generate Feature Summary | |
| shell: bash | |
| env: | |
| PREVIOUS_TAG: ${{ needs.prepare_release.outputs.previous_tag }} | |
| RELEASE_TAG: ${{ needs.prepare_release.outputs.release_tag }} | |
| REPOSITORY: ${{ github.repository }} | |
| run: | | |
| mkdir -p ./artifacts | |
| commit_range="HEAD" | |
| if [[ -n "${PREVIOUS_TAG}" ]]; then | |
| commit_range="${PREVIOUS_TAG}..HEAD" | |
| fi | |
| git log --no-merges --pretty=format:%s "${commit_range}" | | |
| awk 'NF && tolower($0) !~ /^chore\(release\):/ && !seen[$0]++' > ./artifacts/release-commits.txt | |
| if [[ ! -s ./artifacts/release-commits.txt ]]; then | |
| printf '%s\n' "Maintenance and release preparation changes." > ./artifacts/release-commits.txt | |
| fi | |
| : > ./artifacts/release-feature-docs.txt | |
| if [[ -n "${PREVIOUS_TAG}" ]]; then | |
| git diff --name-only "${PREVIOUS_TAG}..HEAD" -- 'docs/Features/*.md' | | |
| awk '/^docs\/Features\// && !seen[$0]++' > ./artifacts/release-feature-docs.txt | |
| elif [[ -d ./docs/Features ]]; then | |
| find ./docs/Features -maxdepth 1 -type f -name '*.md' | sed 's#^\./##' | sort > ./artifacts/release-feature-docs.txt | |
| fi | |
| { | |
| echo "## Feature Summary" | |
| while IFS= read -r subject; do | |
| if [[ -n "${subject}" ]]; then | |
| echo "- ${subject}" | |
| fi | |
| done < ./artifacts/release-commits.txt | |
| } > ./artifacts/release-summary.md | |
| if [[ -s ./artifacts/release-feature-docs.txt ]]; then | |
| { | |
| echo | |
| echo "## Feature Specs" | |
| while IFS= read -r feature_doc; do | |
| if [[ -z "${feature_doc}" ]]; then | |
| continue | |
| fi | |
| title="$(sed -n 's/^# //p' "${feature_doc}" | head -n 1)" | |
| if [[ -z "${title}" ]]; then | |
| title="$(basename "${feature_doc}" .md)" | |
| fi | |
| printf -- '- [%s](https://github.com/%s/blob/%s/%s)\n' \ | |
| "${title}" \ | |
| "${REPOSITORY}" \ | |
| "${RELEASE_TAG}" \ | |
| "${feature_doc}" | |
| done < ./artifacts/release-feature-docs.txt | |
| } >> ./artifacts/release-summary.md | |
| fi | |
| - name: Publish GitHub Release | |
| shell: bash | |
| env: | |
| GH_TOKEN: ${{ github.token }} | |
| PREVIOUS_TAG: ${{ needs.prepare_release.outputs.previous_tag }} | |
| RELEASE_TAG: ${{ needs.prepare_release.outputs.release_tag }} | |
| RELEASE_TARGET_SHA: ${{ github.sha }} | |
| RELEASE_VERSION: ${{ needs.prepare_release.outputs.release_version }} | |
| REPOSITORY: ${{ github.repository }} | |
| run: | | |
| mapfile -t release_assets < <(find ./artifacts/release-assets -type f | sort) | |
| if [[ ${#release_assets[@]} -eq 0 ]]; then | |
| echo "No release assets were downloaded." >&2 | |
| exit 1 | |
| fi | |
| release_notes="$(cat ./artifacts/release-summary.md)" | |
| release_command=( | |
| gh release create "${RELEASE_TAG}" | |
| "${release_assets[@]}" | |
| --repo "${REPOSITORY}" | |
| --target "${RELEASE_TARGET_SHA}" | |
| --title "DotPilot ${RELEASE_VERSION}" | |
| --generate-notes | |
| --notes "${release_notes}" | |
| ) | |
| if [[ -n "${PREVIOUS_TAG}" ]]; then | |
| release_command+=(--notes-start-tag "${PREVIOUS_TAG}") | |
| fi | |
| "${release_command[@]}" |