v2.0.0-rc.3 #63
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: Update Homebrew Cask | |
| on: | |
| release: | |
| types: [published] | |
| workflow_dispatch: | |
| inputs: | |
| tag: | |
| description: "Release tag to publish to the tap (e.g. v1.4.0)" | |
| required: true | |
| type: string | |
| permissions: | |
| contents: read | |
| jobs: | |
| update-cask: | |
| runs-on: ubuntu-latest | |
| # The tap configuration has LEFT this `if:`, and that is the point of the change. | |
| # `vars.HOMEBREW_TAP_OWNER != '' && vars.HOMEBREW_TAP_REPO != ''` here made the job | |
| # `skipped`, and a skipped job is green: every release since this workflow was | |
| # written has "succeeded" without publishing a cask, because neither variable has | |
| # ever existed on the repository. Same failure as #148, where publish-winget.yml | |
| # spent eight releases green and silent for exactly this reason — see the comment | |
| # above its own `if:`. A guard that does nothing quietly guards nothing. | |
| # | |
| # So the job always starts, and a step announces the missing configuration. It costs | |
| # a runner-minute per release; it buys "nothing was published" being visible in the | |
| # run summary instead of inferable from a tap nobody thought to look at. | |
| if: github.event_name == 'workflow_dispatch' || !github.event.release.prerelease | |
| env: | |
| TAP_OWNER: ${{ vars.HOMEBREW_TAP_OWNER }} | |
| TAP_REPO: ${{ vars.HOMEBREW_TAP_REPO }} | |
| CASK_NAME: ${{ vars.HOMEBREW_CASK_NAME || 'openscreen' }} | |
| # `secrets` is not a context an `if:` can read, at job level or step level — only | |
| # `env` is. Hence this boolean-as-string, which exposes whether the token is set | |
| # without ever exposing its value. Same trick as publish-winget.yml. | |
| HAS_TOKEN: ${{ secrets.HOMEBREW_TAP_TOKEN != '' }} | |
| steps: | |
| - name: Check the tap configuration | |
| id: config | |
| run: | | |
| set -euo pipefail | |
| # `homebrew-?*`, not `-n`: the fourth requirement in the warning below is a | |
| # rule about the name itself, and a name test is the one of the four this step | |
| # can actually apply. `getopenscreen/openscreen-tap` would clone, commit and | |
| # push exactly like a real tap and still be untappable — a green run publishing | |
| # to somewhere `brew tap` cannot resolve, which is the failure this whole | |
| # workflow change exists to stop. `?*` also rejects a repository named the bare | |
| # `homebrew-`, which the prefix alone would accept. | |
| if [[ -n "$TAP_OWNER" && "$TAP_REPO" == homebrew-?* && "$HAS_TOKEN" == "true" ]]; then | |
| echo "configured=true" >> "$GITHUB_OUTPUT" | |
| echo "Tap: ${TAP_OWNER}/${TAP_REPO}, cask ${CASK_NAME}." | |
| exit 0 | |
| fi | |
| echo "configured=false" >> "$GITHUB_OUTPUT" | |
| echo "::warning title=Homebrew cask not updated::No cask was published. Needs (1) the repository variable HOMEBREW_TAP_OWNER, currently ${TAP_OWNER:-UNSET}; (2) HOMEBREW_TAP_REPO, currently ${TAP_REPO:-UNSET}; (3) the secret HOMEBREW_TAP_TOKEN, currently $([[ "$HAS_TOKEN" == "true" ]] && echo set || echo UNSET), with contents write on that repository; and (4) the tap repository itself, which must be named homebrew-<something> for Homebrew to recognise it. See https://github.com/getopenscreen/openscreen/issues/335" | |
| - name: Resolve and validate tag | |
| id: meta | |
| if: steps.config.outputs.configured == 'true' | |
| env: | |
| GH_EVENT_TAG: ${{ github.event.release.tag_name }} | |
| INPUT_TAG: ${{ inputs.tag }} | |
| run: | | |
| set -euo pipefail | |
| TAG="${GH_EVENT_TAG:-$INPUT_TAG}" | |
| if [[ -z "$TAG" ]]; then | |
| echo "::error::No tag resolved from release event or workflow input" | |
| exit 1 | |
| fi | |
| # The `prerelease` filter only covers the `release` event. `workflow_dispatch` | |
| # takes free text — no git ref rule applies to it — so a manual replay of | |
| # `v1.9.4-rc.2` would publish an RC as THE cask, and `brew upgrade` would hand | |
| # it to everyone on stable. Homebrew accepts that version string happily; only | |
| # this check refuses it. The same hole was closed in aur-publish.yml, where the | |
| # free-text input also escaped a `sed` expression; here every use is quoted, so | |
| # what is left is the wrong-version case and the tag reaching a Ruby file that | |
| # users execute. | |
| if [[ ! "$TAG" =~ ^v[0-9]+\.[0-9]+\.[0-9]+$ ]]; then | |
| echo "::error::Refusing tag '$TAG' — expected a stable vMAJOR.MINOR.PATCH tag" | |
| exit 1 | |
| fi | |
| VERSION="${TAG#v}" | |
| echo "tag=$TAG" >> "$GITHUB_OUTPUT" | |
| echo "version=$VERSION" >> "$GITHUB_OUTPUT" | |
| - name: Wait for release DMG assets | |
| if: steps.config.outputs.configured == 'true' | |
| env: | |
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| TAG: ${{ steps.meta.outputs.tag }} | |
| REPO: ${{ github.repository }} | |
| run: | | |
| set -euo pipefail | |
| TIMEOUT_MINUTES=12 | |
| POLL_INTERVAL=30 | |
| MAX_ATTEMPTS=$(( (TIMEOUT_MINUTES * 60) / POLL_INTERVAL )) | |
| # Match on the arch marker, not on an exact filename. build.yml names | |
| # the DMGs `Openscreen-macOS-Apple-Silicon-<ver>.dmg` and | |
| # `-Intel-`; older releases used `-Mac-arm64-` / `-Mac-x64-`. An | |
| # exact-name wait would poll for the full 12 minutes and warn, on a | |
| # release whose assets were there the whole time. These are the same | |
| # patterns the "Find macOS DMG assets" step below already matches on, | |
| # so the two steps cannot disagree about what counts as present. | |
| for i in $(seq 1 $MAX_ATTEMPTS); do | |
| NAMES=$(gh release view "$TAG" --repo "$REPO" --json assets --jq '.assets[].name' 2>/dev/null || true) | |
| DMGS=$(echo "$NAMES" | grep -iE '\.dmg$' || true) | |
| ARM_FOUND=$(echo "$DMGS" | grep -icE '(arm64|apple[-_. ]?silicon)' || true) | |
| X64_FOUND=$(echo "$DMGS" | grep -icE '(x64|x86[-_]?64|intel)' || true) | |
| if [[ "$ARM_FOUND" -ge 1 && "$X64_FOUND" -ge 1 ]]; then | |
| echo "Both DMG assets present:" | |
| echo "$DMGS" | |
| exit 0 | |
| fi | |
| echo "Waiting for DMG assets... (attempt $i/$MAX_ATTEMPTS)" | |
| sleep $POLL_INTERVAL | |
| done | |
| echo "::warning::Timeout after ${TIMEOUT_MINUTES}min waiting for DMG assets. Proceeding anyway." | |
| - name: Find macOS DMG assets | |
| id: assets | |
| if: steps.config.outputs.configured == 'true' | |
| env: | |
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| TAG: ${{ steps.meta.outputs.tag }} | |
| REPO: ${{ github.repository }} | |
| run: | | |
| set -euo pipefail | |
| NAMES=$(gh release view "$TAG" --repo "$REPO" --json assets --jq '.assets[].name') | |
| # arm64 DMG: explicit "arm64" / "apple silicon" / fallback to any .dmg | |
| # whose name does NOT contain "x64" or non-mac platform markers. | |
| ARM_NAME=$(echo "$NAMES" | grep -iE '\.dmg$' \ | |
| | grep -iE '(arm64|apple[-_. ]?silicon)' | head -n1 || true) | |
| if [[ -z "$ARM_NAME" ]]; then | |
| ARM_NAME=$(echo "$NAMES" | grep -iE '\.dmg$' \ | |
| | grep -iv 'x64' | grep -iv 'linux' | grep -iv 'win' | head -n1 || true) | |
| fi | |
| # x64 DMG | |
| X64_NAME=$(echo "$NAMES" | grep -iE '\.dmg$' \ | |
| | grep -iE '(x64|x86[-_]?64|intel)' | head -n1 || true) | |
| if [[ -z "$ARM_NAME" || -z "$X64_NAME" ]]; then | |
| echo "::error::Could not locate both arm64 and x64 DMGs in release assets" | |
| echo "Available assets:" | |
| echo "$NAMES" | |
| exit 1 | |
| fi | |
| echo "arm_name=$ARM_NAME" >> "$GITHUB_OUTPUT" | |
| echo "x64_name=$X64_NAME" >> "$GITHUB_OUTPUT" | |
| echo "Found arm64 asset: $ARM_NAME" | |
| echo "Found x64 asset: $X64_NAME" | |
| - name: Download DMGs and compute sha256 | |
| id: shas | |
| if: steps.config.outputs.configured == 'true' | |
| env: | |
| REPO: ${{ github.repository }} | |
| TAG: ${{ steps.meta.outputs.tag }} | |
| ARM_NAME: ${{ steps.assets.outputs.arm_name }} | |
| X64_NAME: ${{ steps.assets.outputs.x64_name }} | |
| run: | | |
| set -euo pipefail | |
| BASE="https://github.com/${REPO}/releases/download/${TAG}" | |
| curl -fsSL --retry 3 -o /tmp/arm.dmg "${BASE}/${ARM_NAME}" | |
| curl -fsSL --retry 3 -o /tmp/x64.dmg "${BASE}/${X64_NAME}" | |
| ARM_SHA=$(sha256sum /tmp/arm.dmg | awk '{print $1}') | |
| X64_SHA=$(sha256sum /tmp/x64.dmg | awk '{print $1}') | |
| echo "arm_sha=$ARM_SHA" >> "$GITHUB_OUTPUT" | |
| echo "x64_sha=$X64_SHA" >> "$GITHUB_OUTPUT" | |
| - name: Checkout tap | |
| if: steps.config.outputs.configured == 'true' | |
| uses: actions/checkout@v7 | |
| with: | |
| repository: ${{ env.TAP_OWNER }}/${{ env.TAP_REPO }} | |
| token: ${{ secrets.HOMEBREW_TAP_TOKEN }} | |
| path: tap | |
| - name: Write cask file | |
| if: steps.config.outputs.configured == 'true' | |
| env: | |
| REPO: ${{ github.repository }} | |
| TAG: ${{ steps.meta.outputs.tag }} | |
| VERSION: ${{ steps.meta.outputs.version }} | |
| ARM_NAME: ${{ steps.assets.outputs.arm_name }} | |
| X64_NAME: ${{ steps.assets.outputs.x64_name }} | |
| ARM_SHA: ${{ steps.shas.outputs.arm_sha }} | |
| X64_SHA: ${{ steps.shas.outputs.x64_sha }} | |
| run: | | |
| set -euo pipefail | |
| mkdir -p tap/Casks | |
| BASE="https://github.com/${REPO}/releases/download/${TAG}" | |
| # #{version} is Ruby interpolation written literally to the cask | |
| # file (bash heredoc leaves "#{...}" alone). \${VERSION}, \${ARM_SHA}, | |
| # etc. are bash variables expanded by the heredoc. The literal | |
| # #{version} fixes Homebrew's "URL is unversioned" audit warning by | |
| # making the version string statically detectable. | |
| cat > "tap/Casks/${CASK_NAME}.rb" <<EOF | |
| cask "${CASK_NAME}" do | |
| version "${VERSION}" | |
| on_arm do | |
| sha256 "${ARM_SHA}" | |
| url "https://github.com/${REPO}/releases/download/v#{version}/${ARM_NAME}" | |
| end | |
| on_intel do | |
| sha256 "${X64_SHA}" | |
| url "https://github.com/${REPO}/releases/download/v#{version}/${X64_NAME}" | |
| end | |
| name "Openscreen" | |
| desc "Screen recorder and video editor" | |
| homepage "https://github.com/${REPO}" | |
| auto_updates false | |
| depends_on macos: ">= :ventura" | |
| app "Openscreen.app" | |
| zap trash: [ | |
| "~/Library/Application Support/Openscreen", | |
| "~/Library/Caches/com.etiennelescot.openscreen", | |
| "~/Library/Logs/Openscreen", | |
| "~/Library/Preferences/com.etiennelescot.openscreen.plist", | |
| "~/Library/Saved Application State/com.etiennelescot.openscreen.savedState", | |
| ] | |
| end | |
| EOF | |
| - name: Commit and push to tap | |
| if: steps.config.outputs.configured == 'true' | |
| working-directory: tap | |
| env: | |
| VERSION: ${{ steps.meta.outputs.version }} | |
| run: | | |
| set -euo pipefail | |
| git config user.name "github-actions[bot]" | |
| git config user.email "41898282+github-actions[bot]@users.noreply.github.com" | |
| git add "Casks/${CASK_NAME}.rb" | |
| if git diff --cached --quiet; then | |
| echo "Cask already up to date for ${VERSION} — nothing to commit." | |
| exit 0 | |
| fi | |
| git commit -m "Bump ${CASK_NAME} to ${VERSION}" | |
| git push |