Unsloth whisper prebuilt (full release) #8
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
| # SPDX-License-Identifier: AGPL-3.0-only | |
| # Copyright 2026-present the Unsloth AI Inc. team. All rights reserved. | |
| name: Unsloth whisper prebuilt (full release) | |
| # One workflow run, one release per upstream whisper.cpp tag. The heavy build | |
| # work is split into reusable per-accelerator children: | |
| # unsloth-prebuilt-cpu.yml -- CPU bundles (Linux x64/arm64, Windows x64 [P0] + arm64 [best-effort], macOS x64) | |
| # unsloth-prebuilt-macos.yml -- macOS arm64 Metal (+ x64 CPU slice) [P0] | |
| # unsloth-prebuilt-cuda.yml -- Linux CUDA bundles (x64 + arm64, matrix profiles) [P1] | |
| # unsloth-prebuilt-cuda-windows.yml -- Windows CUDA bundles (x64, matrix profiles) [P1] | |
| # unsloth-prebuilt-vulkan.yml -- Vulkan bundles (Linux + Windows, x64) [P1] | |
| # unsloth-prebuilt-rocm.yml -- ROCm bundles (Linux + Windows, per gfx target) [P1] | |
| # | |
| # Tiered atomicity: the `assemble` job hard-requires the P0 children (CPU + | |
| # macOS) and only best-effort waits on the P1 GPU children. A P1 failure does | |
| # NOT block the release -- its bundles are simply absent from the manifest and | |
| # those hosts fall back to a CPU bundle. A P0 failure blocks publication | |
| # entirely: the installer needs the full P0 set at one tag. | |
| # | |
| # Immutability: `resolve` dereferences the requested upstream tag to an exact | |
| # commit SHA and records it. Every child builds that one stamped source tree | |
| # (uploaded once as a workflow artifact), so a tag re-point upstream cannot | |
| # change what a given packaging tag shipped. | |
| on: | |
| # Auto-stream from mainline whisper.cpp: every 5 days at ~3PM San Francisco | |
| # (22:41 UTC; a fixed offset, not DST-adjusted, so ~3:41PM PDT / ~2:41PM PST). | |
| # `*/5` fires on days 1,6,11,16,21,26,31, so the spacing resets at each month | |
| # boundary -- close enough given upstream tags land weeks apart. A scheduled | |
| # run resolves the newest aged upstream release and publishes it; if that | |
| # release is already published the run is a cheap no-op (resolve sets | |
| # exists=true and every build/assemble job is skipped). Manual dispatch can | |
| # still build or publish any tag on demand. | |
| schedule: | |
| - cron: '41 22 */5 * *' | |
| workflow_dispatch: | |
| inputs: | |
| upstream_tag: | |
| description: 'Upstream ggml-org/whisper.cpp tag to build, or "latest" to resolve the newest aged release' | |
| default: 'latest' | |
| required: false | |
| type: string | |
| packaging_suffix: | |
| description: 'Packaging suffix appended to the tag: <upstream_tag>-<suffix>' | |
| default: 'unsloth.1' | |
| required: true | |
| type: string | |
| min_age_hours: | |
| description: 'For "latest": only build an upstream release public for at least this many hours (blank = 6)' | |
| default: '' | |
| required: false | |
| type: string | |
| publish: | |
| description: 'Publish a GitHub Release (off = build + upload artifacts only). Scheduled runs always publish.' | |
| default: false | |
| required: false | |
| type: boolean | |
| permissions: | |
| contents: write | |
| concurrency: | |
| group: ${{ github.workflow }}-${{ github.event.inputs.upstream_tag || 'scheduled' }}-${{ github.event.inputs.packaging_suffix || 'auto' }} | |
| cancel-in-progress: false | |
| jobs: | |
| resolve: | |
| name: Resolve tag + stamp source | |
| runs-on: ubuntu-22.04 | |
| permissions: | |
| contents: read | |
| outputs: | |
| upstream_tag: ${{ steps.r.outputs.upstream_tag }} | |
| tag: ${{ steps.r.outputs.tag }} | |
| commit: ${{ steps.r.outputs.commit }} | |
| repo: ${{ steps.r.outputs.repo }} | |
| source_artifact: ${{ steps.r.outputs.source_artifact }} | |
| exists: ${{ steps.r.outputs.exists }} | |
| cuda_matrix: ${{ steps.r.outputs.cuda_matrix }} | |
| win_cuda_matrix: ${{ steps.r.outputs.win_cuda_matrix }} | |
| rocm_matrix: ${{ steps.r.outputs.rocm_matrix }} | |
| macos_matrix: ${{ steps.r.outputs.macos_matrix }} | |
| env: | |
| GH_TOKEN: ${{ github.token }} | |
| steps: | |
| - name: Resolve immutable commit + stamp source tree | |
| id: r | |
| run: | | |
| set -euo pipefail | |
| UT='${{ github.event.inputs.upstream_tag || 'latest' }}' | |
| SUFFIX='${{ github.event.inputs.packaging_suffix || 'unsloth.1' }}' | |
| AGE_H='${{ github.event.inputs.min_age_hours }}' | |
| [ -n "$AGE_H" ] || AGE_H=6 | |
| # Resolve "latest" (the default, and what every scheduled run uses) to | |
| # the newest published upstream release that has been public for >= | |
| # AGE_H hours -- a supply-chain aging window so a yanked or malicious | |
| # upstream tag is not shipped the instant it appears. GitHub does not | |
| # guarantee list order, so pick the max by published_at explicitly. An | |
| # explicit vX.Y.Z tag (manual dispatch) skips the filter. | |
| if [ "$UT" = "latest" ]; then | |
| CUTOFF="$(date -u -d "-${AGE_H} hours" +%s)" | |
| UT="$(gh api 'repos/ggml-org/whisper.cpp/releases?per_page=100' \ | |
| --jq "[.[] | select(.draft==false and .prerelease==false) | select((.published_at|fromdateiso8601) <= ${CUTOFF})] | max_by(.published_at|fromdateiso8601) | .tag_name")" | |
| if [ -z "$UT" ] || [ "$UT" = "null" ]; then | |
| echo "refusing: no ggml-org/whisper.cpp release older than ${AGE_H}h in the last 100 releases" >&2 | |
| exit 1 | |
| fi | |
| echo "selected upstream ${UT} (aged >= ${AGE_H}h)" | |
| fi | |
| printf '%s' "$UT" | grep -qE '^v[0-9]+\.[0-9]+(\.[0-9]+)?$' \ | |
| || { echo "refusing non-release upstream tag '$UT' (want vMAJOR.MINOR[.PATCH])" >&2; exit 1; } | |
| printf '%s' "$SUFFIX" | grep -qE '^[A-Za-z0-9._-]+$' \ | |
| || { echo "refusing packaging suffix '$SUFFIX'" >&2; exit 1; } | |
| TAG="${UT}-${SUFFIX}" | |
| REPO="$GITHUB_REPOSITORY" | |
| # Dereference the tag to an exact commit (annotated tags resolve to the | |
| # tag object first, then to the commit it points at). | |
| REF_JSON="$(gh api "repos/ggml-org/whisper.cpp/git/ref/tags/${UT}")" | |
| OBJ_TYPE="$(jq -r '.object.type' <<<"$REF_JSON")" | |
| OBJ_SHA="$(jq -r '.object.sha' <<<"$REF_JSON")" | |
| if [ "$OBJ_TYPE" = "tag" ]; then | |
| COMMIT="$(gh api "repos/ggml-org/whisper.cpp/git/tags/${OBJ_SHA}" --jq '.object.sha')" | |
| else | |
| COMMIT="$OBJ_SHA" | |
| fi | |
| printf '%s' "$COMMIT" | grep -qE '^[0-9a-f]{40}$' \ | |
| || { echo "could not resolve ${UT} to a commit" >&2; exit 1; } | |
| echo "resolved ${UT} -> ${COMMIT}" | |
| EXISTS=false | |
| if [ "$(gh release view "$TAG" --repo "$REPO" --json isDraft --jq .isDraft 2>/dev/null || true)" = "false" ]; then | |
| EXISTS=true | |
| fi | |
| # Check out the exact commit and stamp cmake/build-info.cmake so the | |
| # server reports a real version (the tarball ships without .git, where | |
| # build-info falls back to BUILD_NUMBER 0 / "unknown"). Unlike | |
| # llama.cpp, whisper.cpp does NOT compile BUILD_TARGET into whisper-server | |
| # (it only feeds BUILD_NUMBER/COMMIT into install-time cmake config | |
| # files), so the Unsloth fingerprint cannot be baked into the binary | |
| # here; package_bundle.py carries it in every bundle's BUILD_INFO.txt, | |
| # which the assemble verify gate then checks. | |
| git init -q whisper-src | |
| cd whisper-src | |
| git remote add origin https://github.com/ggml-org/whisper.cpp.git | |
| git fetch -q --depth 1 origin "$COMMIT" | |
| git checkout -q --detach FETCH_HEAD | |
| COUNT="$(git rev-list --count HEAD)" | |
| SHORT="$(git rev-parse --short HEAD)" | |
| sed -i "s/^set(BUILD_NUMBER 0)$/set(BUILD_NUMBER ${COUNT})/" cmake/build-info.cmake | |
| sed -i "s/^set(BUILD_COMMIT \"unknown\")$/set(BUILD_COMMIT \"${SHORT}\")/" cmake/build-info.cmake | |
| grep -q "set(BUILD_NUMBER ${COUNT})" cmake/build-info.cmake \ | |
| && grep -q "set(BUILD_COMMIT \"${SHORT}\")" cmake/build-info.cmake \ | |
| || { echo "cmake/build-info.cmake lost its expected fallback lines" >&2; exit 1; } | |
| cd .. | |
| tar -czf "${RUNNER_TEMP}/whisper.cpp-source-${TAG}.tar.gz" \ | |
| --exclude-vcs --transform "s,^whisper-src,whisper.cpp-${TAG}," whisper-src | |
| # x64 CUDA profiles (mirror unslothai/llama.cpp coverage names) + one | |
| # arm64 cuda13-portable slice. cuda12 via Jimver; cuda13 via NVIDIA | |
| # redist (Jimver lacks 13.3). | |
| ALL='[ | |
| {"profile":"cuda12-legacy", "arch":"x64", "runner":"ubuntu-22.04", "cuda":"12.8.0","toolkit_line":"12.8","line":"cuda12","klass":"legacy", "archs":"50-virtual 61-virtual","sms":"50 52 60 61"}, | |
| {"profile":"cuda12-older", "arch":"x64", "runner":"ubuntu-22.04", "cuda":"12.8.0","toolkit_line":"12.8","line":"cuda12","klass":"older", "archs":"70 75 80 86 89","sms":"70 75 80 86 89"}, | |
| {"profile":"cuda12-newer", "arch":"x64", "runner":"ubuntu-22.04", "cuda":"12.8.0","toolkit_line":"12.8","line":"cuda12","klass":"newer", "archs":"86 89 90 100 120","sms":"86 89 90 100 120"}, | |
| {"profile":"cuda12-portable","arch":"x64", "runner":"ubuntu-22.04", "cuda":"12.8.0","toolkit_line":"12.8","line":"cuda12","klass":"portable","archs":"70 75 80 86 89 90 100 120","sms":"70 75 80 86 89 90 100 120"}, | |
| {"profile":"cuda13-older", "arch":"x64", "runner":"ubuntu-22.04", "cuda":"13.3","toolkit_line":"13.3","line":"cuda13","klass":"older", "archs":"75 80 86 89","sms":"75 80 86 89"}, | |
| {"profile":"cuda13-newer", "arch":"x64", "runner":"ubuntu-22.04", "cuda":"13.3","toolkit_line":"13.3","line":"cuda13","klass":"newer", "archs":"86 89 90 100 120","sms":"86 89 90 100 120"}, | |
| {"profile":"cuda13-portable","arch":"x64", "runner":"ubuntu-22.04", "cuda":"13.3","toolkit_line":"13.3","line":"cuda13","klass":"portable","archs":"75 80 86 89 90 100 120","sms":"75 80 86 89 90 100 120"}, | |
| {"profile":"cuda13-portable","arch":"arm64","runner":"ubuntu-24.04-arm","cuda":"13.3","toolkit_line":"13.3","line":"cuda13","klass":"portable","archs":"90 100 120 121","sms":"90 100 120 121"} | |
| ]' | |
| CUDA_INCLUDE="$(jq -c . <<<"$ALL")" | |
| WIN_CUDA_INCLUDE="$(jq -c '[.[] | select(.arch=="x64") | .runner="windows-2022"]' <<<"$CUDA_INCLUDE")" | |
| ROCM_MATRIX='{"gfx_target":["gfx1151","gfx1150","gfx120X","gfx110X","gfx103X","gfx90a","gfx908"]}' | |
| MACOS_INCLUDE='[ | |
| {"build":"arm64","runner":"macos-26", "expect_arch":"arm64", "deploy_target":"14.0","accel":"metal","backend":"metal","defines":"-DGGML_METAL=ON -DGGML_METAL_EMBED_LIBRARY=ON -DGGML_METAL_USE_BF16=ON","shared":"OFF","static":"true"}, | |
| {"build":"x64", "runner":"macos-15-intel","expect_arch":"x86_64","deploy_target":"13.3","accel":"cpu", "backend":"cpu", "defines":"-DGGML_METAL=OFF -DGGML_BACKEND_DL=ON -DGGML_CPU_ALL_VARIANTS=ON","shared":"ON","static":"false"} | |
| ]' | |
| MACOS_INCLUDE="$(jq -c . <<<"$MACOS_INCLUDE")" | |
| { | |
| echo "upstream_tag=$UT" | |
| echo "tag=$TAG" | |
| echo "commit=$COMMIT" | |
| echo "repo=$REPO" | |
| echo "source_artifact=whisper-source-${TAG}" | |
| echo "exists=$EXISTS" | |
| echo "cuda_matrix={\"include\":$CUDA_INCLUDE}" | |
| echo "win_cuda_matrix={\"include\":$WIN_CUDA_INCLUDE}" | |
| echo "rocm_matrix=$ROCM_MATRIX" | |
| echo "macos_matrix={\"include\":$MACOS_INCLUDE}" | |
| } >> "$GITHUB_OUTPUT" | |
| echo "Resolved $UT -> $TAG ($COMMIT); release exists=$EXISTS" | |
| - name: Upload stamped source artifact | |
| if: ${{ steps.r.outputs.exists != 'true' || github.event_name == 'workflow_dispatch' }} | |
| uses: actions/upload-artifact@v6 | |
| with: | |
| name: ${{ steps.r.outputs.source_artifact }} | |
| path: ${{ runner.temp }}/whisper.cpp-source-${{ steps.r.outputs.tag }}.tar.gz | |
| if-no-files-found: error | |
| retention-days: 7 | |
| build-cpu: | |
| name: CPU | |
| needs: resolve | |
| if: ${{ needs.resolve.outputs.exists != 'true' || github.event_name == 'workflow_dispatch' }} | |
| uses: ./.github/workflows/unsloth-prebuilt-cpu.yml | |
| with: | |
| tag: ${{ needs.resolve.outputs.tag }} | |
| upstream_tag: ${{ needs.resolve.outputs.upstream_tag }} | |
| repo: ${{ needs.resolve.outputs.repo }} | |
| commit: ${{ needs.resolve.outputs.commit }} | |
| source_artifact: ${{ needs.resolve.outputs.source_artifact }} | |
| build-macos: | |
| name: macOS | |
| needs: resolve | |
| if: ${{ needs.resolve.outputs.exists != 'true' || github.event_name == 'workflow_dispatch' }} | |
| uses: ./.github/workflows/unsloth-prebuilt-macos.yml | |
| with: | |
| tag: ${{ needs.resolve.outputs.tag }} | |
| upstream_tag: ${{ needs.resolve.outputs.upstream_tag }} | |
| repo: ${{ needs.resolve.outputs.repo }} | |
| commit: ${{ needs.resolve.outputs.commit }} | |
| source_artifact: ${{ needs.resolve.outputs.source_artifact }} | |
| matrix: ${{ needs.resolve.outputs.macos_matrix }} | |
| build-cuda: | |
| name: CUDA | |
| needs: resolve | |
| if: ${{ needs.resolve.outputs.exists != 'true' || github.event_name == 'workflow_dispatch' }} | |
| uses: ./.github/workflows/unsloth-prebuilt-cuda.yml | |
| with: | |
| tag: ${{ needs.resolve.outputs.tag }} | |
| upstream_tag: ${{ needs.resolve.outputs.upstream_tag }} | |
| repo: ${{ needs.resolve.outputs.repo }} | |
| commit: ${{ needs.resolve.outputs.commit }} | |
| source_artifact: ${{ needs.resolve.outputs.source_artifact }} | |
| matrix: ${{ needs.resolve.outputs.cuda_matrix }} | |
| build-windows-cuda: | |
| name: CUDA Windows | |
| needs: resolve | |
| if: ${{ needs.resolve.outputs.exists != 'true' || github.event_name == 'workflow_dispatch' }} | |
| uses: ./.github/workflows/unsloth-prebuilt-cuda-windows.yml | |
| with: | |
| tag: ${{ needs.resolve.outputs.tag }} | |
| upstream_tag: ${{ needs.resolve.outputs.upstream_tag }} | |
| repo: ${{ needs.resolve.outputs.repo }} | |
| commit: ${{ needs.resolve.outputs.commit }} | |
| source_artifact: ${{ needs.resolve.outputs.source_artifact }} | |
| matrix: ${{ needs.resolve.outputs.win_cuda_matrix }} | |
| build-vulkan: | |
| name: Vulkan | |
| needs: resolve | |
| if: ${{ needs.resolve.outputs.exists != 'true' || github.event_name == 'workflow_dispatch' }} | |
| uses: ./.github/workflows/unsloth-prebuilt-vulkan.yml | |
| with: | |
| tag: ${{ needs.resolve.outputs.tag }} | |
| upstream_tag: ${{ needs.resolve.outputs.upstream_tag }} | |
| repo: ${{ needs.resolve.outputs.repo }} | |
| commit: ${{ needs.resolve.outputs.commit }} | |
| source_artifact: ${{ needs.resolve.outputs.source_artifact }} | |
| build-rocm: | |
| name: ROCm | |
| needs: resolve | |
| if: ${{ needs.resolve.outputs.exists != 'true' || github.event_name == 'workflow_dispatch' }} | |
| uses: ./.github/workflows/unsloth-prebuilt-rocm.yml | |
| with: | |
| tag: ${{ needs.resolve.outputs.tag }} | |
| upstream_tag: ${{ needs.resolve.outputs.upstream_tag }} | |
| repo: ${{ needs.resolve.outputs.repo }} | |
| commit: ${{ needs.resolve.outputs.commit }} | |
| source_artifact: ${{ needs.resolve.outputs.source_artifact }} | |
| matrix: ${{ needs.resolve.outputs.rocm_matrix }} | |
| assemble: | |
| name: Assemble metadata + publish | |
| needs: [resolve, build-cpu, build-macos, build-cuda, build-windows-cuda, build-vulkan, build-rocm] | |
| # Tiered gate: run whenever the P0 children (CPU + macOS) succeeded, even if | |
| # a P1 GPU child failed or was skipped. A P0 failure short-circuits here and | |
| # nothing is published. | |
| if: >- | |
| always() | |
| && needs.resolve.result == 'success' | |
| && needs.build-cpu.result == 'success' | |
| && needs.build-macos.result == 'success' | |
| && (needs.resolve.outputs.exists != 'true' || github.event_name == 'workflow_dispatch') | |
| runs-on: ubuntu-22.04 | |
| env: | |
| GH_TOKEN: ${{ github.token }} | |
| steps: | |
| - name: Checkout build tooling (this repo) | |
| uses: actions/checkout@v6 | |
| with: | |
| path: tooling | |
| - name: Download built bundles | |
| id: download | |
| uses: actions/download-artifact@v6 | |
| continue-on-error: true | |
| with: | |
| path: dist | |
| pattern: whisper-* | |
| merge-multiple: true | |
| - name: Download built bundles (retry) | |
| if: steps.download.outcome == 'failure' | |
| uses: actions/download-artifact@v6 | |
| with: | |
| path: dist | |
| pattern: whisper-* | |
| merge-multiple: true | |
| - name: Fetch stamped source archive | |
| run: | | |
| set -eux | |
| TAG='${{ needs.resolve.outputs.tag }}' | |
| # The resolve job's source artifact lands whisper.cpp-source-<tag>.tar.gz | |
| # in dist/ via the whisper-* download; also stamp the commit-named copy. | |
| SHA='${{ needs.resolve.outputs.commit }}' | |
| cp "dist/whisper.cpp-source-${TAG}.tar.gz" "dist/whisper.cpp-source-commit-${SHA}.tar.gz" | |
| # Branding gate: every bundle must carry the Unsloth fingerprint that | |
| # package_bundle.py writes into its BUILD_INFO.txt (whisper-server has no | |
| # compiled build-info string to bake it into, unlike llama.cpp). MARK must | |
| # stay byte-identical to that string. grep -ar scans recursively. | |
| - name: Verify Unsloth fingerprint in every bundle | |
| run: | | |
| set -euo pipefail | |
| MARK='Compiled by the Unsloth team' | |
| shopt -s nullglob | |
| tmp="$(mktemp -d)"; fail=0; checked=0 | |
| for arc in dist/whisper-*.tar.gz dist/whisper-*.zip; do | |
| case "$arc" in *whisper.cpp-source-*) continue ;; esac | |
| d="$tmp/x"; rm -rf "$d"; mkdir -p "$d" | |
| case "$arc" in | |
| *.zip) unzip -qo "$arc" -d "$d" ;; | |
| *.tar.gz) tar -xzf "$arc" -C "$d" ;; | |
| esac | |
| if grep -arq "$MARK" "$d"; then checked=$((checked+1)); | |
| else echo "ERROR: $(basename "$arc"): no file carries the Unsloth fingerprint" >&2; fail=1; fi | |
| done | |
| rm -rf "$tmp" | |
| [ "$checked" -gt 0 ] || { echo "ERROR: no bundles found to verify" >&2; exit 1; } | |
| [ "$fail" = 0 ] || { echo "ERROR: refusing to publish unbranded binaries" >&2; exit 1; } | |
| echo "fingerprint verified in $checked bundles" | |
| - name: Verify P0 coverage before publish | |
| if: ${{ (inputs.publish || github.event_name == 'schedule') && needs.resolve.outputs.exists != 'true' }} | |
| run: | | |
| set -eu | |
| TAG='${{ needs.resolve.outputs.tag }}' | |
| fail=0 | |
| for f in \ | |
| "whisper-${TAG}-linux-x64-cpu.tar.gz" \ | |
| "whisper-${TAG}-linux-arm64-cpu.tar.gz" \ | |
| "whisper-${TAG}-windows-x64-cpu.zip" \ | |
| "whisper-${TAG}-macos-x64-cpu.tar.gz" \ | |
| "whisper-${TAG}-macos-arm64-metal.tar.gz"; do | |
| [ -s "dist/$f" ] || { echo "ERROR: missing required P0 asset $f" >&2; fail=1; } | |
| done | |
| [ "$fail" = 0 ] || { echo "ERROR: refusing to publish an incomplete P0 set" >&2; exit 1; } | |
| echo "P0 coverage complete" | |
| - name: Generate manifest + sha256 index | |
| run: | | |
| set -eux | |
| python3 tooling/scripts/package_bundle.py \ | |
| --emit-manifest --emit-sha256 \ | |
| --dist dist --out dist \ | |
| --tag '${{ needs.resolve.outputs.tag }}' \ | |
| --upstream-tag '${{ needs.resolve.outputs.upstream_tag }}' \ | |
| --commit '${{ needs.resolve.outputs.commit }}' \ | |
| --source-repo "$GITHUB_REPOSITORY" | |
| # Drop the per-asset .entry.json sidecars: the manifest supersedes them. | |
| rm -f dist/*.entry.json | |
| ls -la dist | |
| - name: Upload full release set (artifacts) | |
| uses: actions/upload-artifact@v6 | |
| with: | |
| name: unsloth-whisper-prebuilt-${{ needs.resolve.outputs.tag }} | |
| path: dist/* | |
| if-no-files-found: error | |
| retention-days: 7 | |
| - name: Publish GitHub release (draft first, then atomic) | |
| if: ${{ (inputs.publish || github.event_name == 'schedule') && needs.resolve.outputs.exists != 'true' }} | |
| run: | | |
| set -eux | |
| TAG='${{ needs.resolve.outputs.tag }}' | |
| UT='${{ needs.resolve.outputs.upstream_tag }}' | |
| REPO="$GITHUB_REPOSITORY" | |
| NOTES="Automated Unsloth whisper.cpp (whisper-server) prebuild for upstream [${UT}](https://github.com/ggml-org/whisper.cpp/releases/tag/${UT})." | |
| # Atomic publish: upload as a draft (hidden from the anon API the | |
| # installer reads), then flip draft=false only once every asset landed. | |
| if [ "$(gh release view "$TAG" --repo "$REPO" --json isDraft --jq .isDraft 2>/dev/null || true)" = "true" ]; then | |
| gh release delete "$TAG" --repo "$REPO" --yes | |
| fi | |
| # Exclude the per-asset entry sidecars (already removed) and upload the | |
| # bundles, source tarballs, manifest and sha256 index. | |
| gh release create "$TAG" --repo "$REPO" --draft \ | |
| --title "whisper.cpp prebuilt $TAG" \ | |
| --notes "$NOTES" \ | |
| dist/* | |
| gh release edit "$TAG" --repo "$REPO" --draft=false |