XS⚠️ ◾ Fix camera/audio not working - request permissions before enumerating devices #1155
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: PR Pre-release | |
| on: | |
| pull_request: | |
| types: [opened, synchronize, reopened, closed] | |
| permissions: | |
| contents: write | |
| pull-requests: write | |
| env: | |
| APP_NAME: SSW.YakShaver | |
| NODE_VERSION: 20 | |
| concurrency: | |
| group: pr-pre-release-${{ github.event.pull_request.number || github.ref }} | |
| cancel-in-progress: true | |
| jobs: | |
| cleanup-release: | |
| if: | | |
| github.event_name != 'pull_request' || | |
| github.event.action != 'closed' | |
| runs-on: ubuntu-latest | |
| outputs: | |
| pr_number: ${{ steps.pr_info.outputs.pr_number }} | |
| release_tag: ${{ steps.pr_info.outputs.tag }} | |
| channel: ${{ steps.pr_info.outputs.channel }} | |
| version: ${{ steps.pr_info.outputs.version }} | |
| pr_sha: ${{ steps.pr_info.outputs.pr_sha }} | |
| steps: | |
| - uses: actions/checkout@v6 | |
| with: | |
| ref: ${{ github.event.pull_request.head.ref || github.ref }} | |
| - uses: actions/setup-node@v6 | |
| with: | |
| node-version: ${{ env.NODE_VERSION }} | |
| - name: Get PR info | |
| id: pr_info | |
| run: | | |
| PR_NUMBER="${{ github.event.pull_request.number }}" | |
| PR_SHA="${{ github.event.pull_request.head.sha || github.sha }}" | |
| SHORT_SHA="${PR_SHA:0:7}" | |
| BUILD_TIMESTAMP=$(date +%s) | |
| BASE_VERSION=$(node -p 'require("./package.json").version.split("-")[0]') | |
| CHANNEL_NAME="beta.${PR_NUMBER}" | |
| PRE_RELEASE_VERSION="${BASE_VERSION}-${CHANNEL_NAME}.${BUILD_TIMESTAMP}" | |
| TAG_NAME="${PRE_RELEASE_VERSION}" | |
| echo "tag=${TAG_NAME}" >> $GITHUB_OUTPUT | |
| echo "pr_number=${PR_NUMBER}" >> $GITHUB_OUTPUT | |
| echo "channel=${CHANNEL_NAME}" >> $GITHUB_OUTPUT | |
| echo "version=${PRE_RELEASE_VERSION}" >> $GITHUB_OUTPUT | |
| echo "pr_sha=${SHORT_SHA}" >> $GITHUB_OUTPUT | |
| echo "PR: ${PR_NUMBER}, Tag: ${TAG_NAME}, Channel: ${CHANNEL_NAME}, Version: ${PRE_RELEASE_VERSION}" | |
| - name: Delete existing PR releases | |
| continue-on-error: true | |
| env: | |
| GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| run: | | |
| PR_NUMBER="${{ steps.pr_info.outputs.pr_number }}" | |
| echo "Finding and deleting existing releases for PR #${PR_NUMBER}" | |
| # Collect both release IDs and tag names BEFORE deleting anything | |
| RELEASES_DATA=$(gh api --paginate /repos/${{ github.repository }}/releases --jq ".[] | select(.body | contains(\"PR #${PR_NUMBER}\")) | {id: .id, tag: .tag_name}") | |
| RELEASE_IDS=$(echo "$RELEASES_DATA" | jq -r '.id') | |
| TAG_NAMES=$(echo "$RELEASES_DATA" | jq -r '.tag') | |
| # Now delete the releases | |
| for id in $RELEASE_IDS; do | |
| if [ -n "$id" ]; then | |
| echo "Deleting release ID: $id" | |
| gh api -X DELETE /repos/${{ github.repository }}/releases/$id || true | |
| fi | |
| done | |
| # Clean up associated tags (collected before deletion) | |
| for tag in $TAG_NAMES; do | |
| if [ -n "$tag" ]; then | |
| echo "Deleting tag: $tag" | |
| gh api -X DELETE /repos/${{ github.repository }}/git/refs/tags/$tag || true | |
| fi | |
| done | |
| echo "Cleanup completed" | |
| build-windows: | |
| needs: cleanup-release | |
| if: | | |
| github.event_name != 'pull_request' || | |
| github.event.action != 'closed' | |
| runs-on: windows-latest | |
| steps: | |
| - uses: actions/checkout@v6 | |
| with: | |
| ref: ${{ github.event.pull_request.head.ref || github.ref }} | |
| - uses: actions/setup-node@v6 | |
| with: | |
| node-version: ${{ env.NODE_VERSION }} | |
| cache: npm | |
| cache-dependency-path: package-lock.json | |
| # TODO: Remove this step once this PBI is done: https://github.com/SSWConsulting/SSW.YakShaver/issues/3095 | |
| - name: Create .env from secrets | |
| shell: pwsh | |
| env: | |
| MCP_CALLBACK_PORT: 8090 | |
| MCP_AUTH_TIMEOUT_MS: 60000 | |
| PORTAL_API_URL: https://api-staging.yakshaver.ai/api | |
| AZURE_ENTRA_APP_CLIENT_ID: ${{ secrets.AZURE_ENTRA_APP_CLIENT_ID }} | |
| AZURE_TENANT_ID: ${{ secrets.AZURE_TENANT_ID }} | |
| AZURE_AUTH_SCOPE: ${{ secrets.AZURE_AUTH_SCOPE }} | |
| AZURE_AUTH_CUSTOM_PROTOCOL: yakshaver-desktop | |
| run: | | |
| Add-Content -Path ".env" -Value "`nMCP_CALLBACK_PORT=$env:MCP_CALLBACK_PORT" | |
| Add-Content -Path ".env" -Value "`nMCP_AUTH_TIMEOUT_MS=$env:MCP_AUTH_TIMEOUT_MS" | |
| Add-Content -Path ".env" -Value "`nPORTAL_API_URL=$env:PORTAL_API_URL" | |
| Add-Content -Path ".env" -Value "`nAZURE_ENTRA_APP_CLIENT_ID=$env:AZURE_ENTRA_APP_CLIENT_ID" | |
| Add-Content -Path ".env" -Value "`nAZURE_TENANT_ID=$env:AZURE_TENANT_ID" | |
| Add-Content -Path ".env" -Value "`nAZURE_AUTH_SCOPE=$env:AZURE_AUTH_SCOPE" | |
| Add-Content -Path ".env" -Value "`nAZURE_AUTH_CUSTOM_PROTOCOL=$env:AZURE_AUTH_CUSTOM_PROTOCOL" | |
| $COMMIT_HASH = git rev-parse HEAD | |
| Add-Content -Path ".env" -Value "`nCOMMIT_HASH=$COMMIT_HASH" | |
| - name: Install dependencies | |
| run: npm ci --prefer-offline | |
| env: | |
| # Prevent youtube-dl-exec from auto-downloading yt-dlp during npm install, which would hit the GitHub API unauthenticated rate limit | |
| YOUTUBE_DL_SKIP_DOWNLOAD: true | |
| # Manual download is needed because the 'youtube-dl-exec' script hits the GitHub API unauthenticated rate limit (60/hr) when attempting to download the binary | |
| - name: Install yt-dlp binary manually | |
| shell: bash | |
| run: | | |
| mkdir -p node_modules/youtube-dl-exec/bin | |
| PATTERN="yt-dlp.exe" | |
| OUTPUT_FILE="node_modules/youtube-dl-exec/bin/yt-dlp.exe" | |
| MAX_ATTEMPTS=3 | |
| ATTEMPT=1 | |
| until gh release download --repo yt-dlp/yt-dlp --pattern "$PATTERN" --clobber --output "$OUTPUT_FILE"; do | |
| echo "Attempt $ATTEMPT to download yt-dlp binary failed." >&2 | |
| if [ $ATTEMPT -ge $MAX_ATTEMPTS ]; then exit 1; fi | |
| ATTEMPT=$((ATTEMPT+1)) | |
| sleep 5 | |
| done | |
| env: | |
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| - name: Install UI dependencies | |
| run: npm ci --prefer-offline | |
| working-directory: src/ui | |
| - name: Update version for pre-release | |
| shell: pwsh | |
| run: | | |
| $version = "${{ needs.cleanup-release.outputs.version }}" | |
| if (-not $version) { | |
| throw "Missing version output" | |
| } | |
| npm version $version --no-git-tag-version | |
| - name: Publish Windows pre-release | |
| run: npm run publish -- --win --publish never | |
| env: | |
| NODE_ENV: production | |
| CI: true | |
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| CHANNEL: ${{ needs.cleanup-release.outputs.channel }} | |
| # Disable minification for PR preview builds | |
| VITE_DEBUG_BUILD: "true" | |
| - name: Fix update manifest filenames | |
| shell: pwsh | |
| run: | | |
| Get-ChildItem "build/*.yml" | ForEach-Object { | |
| $content = Get-Content $_.FullName -Raw | |
| $content = $content -replace "YakShaver-Setup-", "YakShaver.Setup." | |
| $content | Out-File -FilePath $_.FullName -Encoding utf8 -NoNewline | |
| Write-Host "Fixed $($_.Name)" | |
| } | |
| - name: Upload Windows artifacts | |
| uses: actions/upload-artifact@v6 | |
| with: | |
| name: windows-build | |
| path: | | |
| build/*.yml | |
| build/YakShaver*Setup*.exe | |
| build/YakShaver*Setup*.exe.blockmap | |
| retention-days: 1 | |
| build-macos: | |
| needs: cleanup-release | |
| if: | | |
| github.event_name != 'pull_request' || | |
| github.event.action != 'closed' | |
| runs-on: macos-latest | |
| env: | |
| GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| steps: | |
| - uses: actions/checkout@v6 | |
| with: | |
| ref: ${{ github.event.pull_request.head.ref || github.ref }} | |
| - uses: actions/setup-node@v6 | |
| with: | |
| node-version: ${{ env.NODE_VERSION }} | |
| cache: npm | |
| cache-dependency-path: package-lock.json | |
| # TODO: Remove this step once this PBI is done: https://github.com/SSWConsulting/SSW.YakShaver/issues/3095 | |
| - name: Create .env from secrets | |
| env: | |
| MCP_CALLBACK_PORT: 8090 | |
| MCP_AUTH_TIMEOUT_MS: 60000 | |
| PORTAL_API_URL: https://api-staging.yakshaver.ai/api | |
| AZURE_ENTRA_APP_CLIENT_ID: ${{ secrets.AZURE_ENTRA_APP_CLIENT_ID }} | |
| AZURE_TENANT_ID: ${{ secrets.AZURE_TENANT_ID }} | |
| AZURE_AUTH_SCOPE: ${{ secrets.AZURE_AUTH_SCOPE }} | |
| AZURE_AUTH_CUSTOM_PROTOCOL: yakshaver-desktop | |
| run: | | |
| printf "MCP_CALLBACK_PORT=%s\n" "$MCP_CALLBACK_PORT" >> .env | |
| printf "MCP_AUTH_TIMEOUT_MS=%s\n" "$MCP_AUTH_TIMEOUT_MS" >> .env | |
| printf "PORTAL_API_URL=%s\n" "$PORTAL_API_URL" >> .env | |
| printf "AZURE_ENTRA_APP_CLIENT_ID=%s\n" "$AZURE_ENTRA_APP_CLIENT_ID" >> .env | |
| printf "AZURE_TENANT_ID=%s\n" "$AZURE_TENANT_ID" >> .env | |
| printf "AZURE_AUTH_SCOPE=%s\n" "$AZURE_AUTH_SCOPE" >> .env | |
| printf "AZURE_AUTH_CUSTOM_PROTOCOL=%s\n" "$AZURE_AUTH_CUSTOM_PROTOCOL" >> .env | |
| - name: Install dependencies | |
| run: npm ci --prefer-offline | |
| env: | |
| YOUTUBE_DL_SKIP_DOWNLOAD: true | |
| # Manual download is needed because the youtube-dl-exec script hits the GitHub API unauthenticated rate limit (60/hr) while attempting to find the yt-dlp binary. | |
| - name: Install yt-dlp binary manually | |
| run: | | |
| mkdir -p node_modules/youtube-dl-exec/bin | |
| PATTERN="yt-dlp_macos" | |
| OUTPUT_FILE="node_modules/youtube-dl-exec/bin/yt-dlp" | |
| MAX_ATTEMPTS=3 | |
| ATTEMPT=1 | |
| until gh release download \ | |
| --repo yt-dlp/yt-dlp \ | |
| --pattern "$PATTERN" \ | |
| --clobber \ | |
| --output "$OUTPUT_FILE"; do | |
| echo "Attempt $ATTEMPT to download yt-dlp binary failed." >&2 | |
| if [ $ATTEMPT -ge $MAX_ATTEMPTS ]; then | |
| echo "Failed to download yt-dlp binary after $MAX_ATTEMPTS attempts." >&2 | |
| exit 1 | |
| fi | |
| ATTEMPT=$((ATTEMPT+1)) | |
| sleep 5 | |
| done | |
| # Make the binary executable | |
| chmod +x "$OUTPUT_FILE" | |
| env: | |
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| - name: Install UI dependencies | |
| run: npm ci --prefer-offline | |
| working-directory: src/ui | |
| - name: Update version for pre-release | |
| run: | | |
| VERSION="${{ needs.cleanup-release.outputs.version }}" | |
| if [[ -z "$VERSION" ]]; then | |
| echo "Missing version output" | |
| exit 1 | |
| fi | |
| npm version "$VERSION" --no-git-tag-version | |
| - name: Publish macOS pre-release | |
| run: npm run publish -- --mac --publish never | |
| env: | |
| NODE_ENV: production | |
| CI: true | |
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| CHANNEL: ${{ needs.cleanup-release.outputs.channel }} | |
| # Disable minification for PR preview builds | |
| VITE_DEBUG_BUILD: "true" | |
| - name: Upload macOS artifacts | |
| uses: actions/upload-artifact@v6 | |
| with: | |
| name: macos-build | |
| path: | | |
| build/YakShaver-*-mac.zip | |
| build/YakShaver-*-mac.zip.blockmap | |
| build/*-mac.yml | |
| retention-days: 1 | |
| create-and-comment-release: | |
| runs-on: ubuntu-latest | |
| needs: | |
| - cleanup-release | |
| - build-windows | |
| - build-macos | |
| if: | | |
| github.event.action != 'closed' && | |
| needs.cleanup-release.outputs.pr_number != '' | |
| steps: | |
| - name: Download Windows artifacts | |
| uses: actions/download-artifact@v7 | |
| with: | |
| name: windows-build | |
| path: artifacts/ | |
| - name: Download macOS artifacts | |
| uses: actions/download-artifact@v7 | |
| with: | |
| name: macos-build | |
| path: artifacts/ | |
| - name: Create single GitHub release with all artifacts | |
| uses: softprops/action-gh-release@v2 | |
| with: | |
| tag_name: ${{ needs.cleanup-release.outputs.release_tag }} | |
| name: "Pre-release PR #${{ needs.cleanup-release.outputs.pr_number }} (${{ needs.cleanup-release.outputs.pr_sha }})" | |
| body: | | |
| Pre-release build for PR #${{ needs.cleanup-release.outputs.pr_number }} | |
| Commit: ${{ needs.cleanup-release.outputs.pr_sha }} | |
| PR: ${{ github.event.pull_request.html_url || 'N/A' }} | |
| This is an automated pre-release. It will be updated automatically when new commits are pushed to the PR. | |
| prerelease: true | |
| files: artifacts/**/* | |
| draft: false | |
| token: ${{ secrets.GITHUB_TOKEN }} | |
| - name: Comment release link on PR | |
| uses: peter-evans/create-or-update-comment@v5 | |
| with: | |
| token: ${{ secrets.GITHUB_TOKEN }} | |
| issue-number: ${{ needs.cleanup-release.outputs.pr_number }} | |
| body: | | |
| 🚀 Pre-release build is available for this PR: | |
| https://github.com/${{ github.repository }}/releases/tag/${{ needs.cleanup-release.outputs.release_tag }} | |
| delete-release: | |
| runs-on: ubuntu-latest | |
| if: | | |
| github.event_name == 'pull_request' && | |
| github.event.action == 'closed' | |
| steps: | |
| - name: Delete PR pre-releases | |
| env: | |
| GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| run: | | |
| PR_NUMBER="${{ github.event.pull_request.number }}" | |
| echo "Deleting all releases for closed PR #${PR_NUMBER}" | |
| # Collect both release IDs and tag names BEFORE deleting anything | |
| RELEASES_DATA=$(gh api --paginate /repos/${{ github.repository }}/releases --jq ".[] | select(.body | contains(\"PR #${PR_NUMBER}\")) | {id: .id, tag: .tag_name}") | |
| RELEASE_IDS=$(echo "$RELEASES_DATA" | jq -r '.id') | |
| TAG_NAMES=$(echo "$RELEASES_DATA" | jq -r '.tag') | |
| # Delete the releases | |
| for id in $RELEASE_IDS; do | |
| if [ -n "$id" ]; then | |
| echo "Deleting release ID: $id" | |
| gh api -X DELETE /repos/${{ github.repository }}/releases/$id || true | |
| fi | |
| done | |
| # Clean up associated tags (collected before deletion) | |
| for tag in $TAG_NAMES; do | |
| if [ -n "$tag" ]; then | |
| echo "Deleting tag: $tag" | |
| gh api -X DELETE /repos/${{ github.repository }}/git/refs/tags/$tag || true | |
| fi | |
| done | |
| echo "Deletion completed for closed PR" |