Type the BUILD_TARGETS filter params (fix typecheck / pre-push hook) #4
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: utmstack-release | |
| # Builds the branded `utmstack` binaries for every platform the installers can | |
| # request, and publishes them with a checksums.txt the installers verify. | |
| # | |
| # The installer downloads "$APP-$target" (e.g. utmstack-linux-x64.tar.gz), so a | |
| # target missing here is a 404 for those users. The -baseline variants matter: | |
| # without them, any x64 machine lacking AVX2 — which includes a lot of cloud | |
| # VMs — cannot install. | |
| on: | |
| push: | |
| tags: ["v*"] | |
| workflow_dispatch: | |
| inputs: | |
| version: | |
| description: "Version to build (without the leading v)" | |
| required: true | |
| permissions: | |
| contents: write | |
| jobs: | |
| build: | |
| name: ${{ matrix.target }} | |
| runs-on: ${{ matrix.runner }} | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| include: | |
| # macOS. Built natively so PyInstaller/Bun ad-hoc signing applies — | |
| # an unsigned arm64 binary will not execute on Apple Silicon. | |
| - { runner: macos-14, target: darwin-arm64, ext: zip } | |
| - { runner: macos-15-intel, target: darwin-x64, ext: zip, baseline: true } | |
| # Linux. x64 builds on 22.04 (glibc 2.35) so it runs on Ubuntu 22.04+ | |
| # and RHEL 9+. arm64 has no 22.04 ARM runner we can rely on, so it | |
| # builds on 24.04 (glibc 2.39) and needs Ubuntu 24.04+ / Debian 13+. | |
| - { runner: ubuntu-22.04, target: linux-x64, ext: tar.gz, baseline: true } | |
| - { runner: ubuntu-24.04-arm, target: linux-arm64, ext: tar.gz } | |
| # Windows, both architectures. | |
| - { runner: windows-2022, target: windows-x64, ext: zip, baseline: true } | |
| - { runner: windows-11-arm, target: windows-arm64, ext: zip } | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - uses: oven-sh/setup-bun@v2 | |
| with: | |
| bun-version: 1.3.13 | |
| - name: Resolve version | |
| id: version | |
| shell: bash | |
| run: | | |
| if [ -n "${{ github.event.inputs.version }}" ]; then | |
| echo "value=${{ github.event.inputs.version }}" >> "$GITHUB_OUTPUT" | |
| else | |
| echo "value=${GITHUB_REF_NAME#v}" >> "$GITHUB_OUTPUT" | |
| fi | |
| - name: Install dependencies | |
| run: bun install | |
| - name: Build | |
| working-directory: packages/opencode | |
| shell: bash | |
| env: | |
| OPENCODE_VERSION: ${{ steps.version.outputs.value }} | |
| run: | | |
| # --baseline additionally emits the no-AVX2 variant for x64 targets, | |
| # which requires Bun to fetch extra cross-compilation artifacts. That | |
| # download is known to be flaky ("Failed to extract executable for | |
| # bun-<target>-baseline"), so retry before giving up. | |
| build() { | |
| if [ "${{ matrix.baseline }}" = "true" ]; then | |
| bun run script/build.ts --single --baseline | |
| else | |
| bun run script/build.ts --single | |
| fi | |
| } | |
| ok=0 | |
| for attempt in 1 2 3; do | |
| echo "build attempt $attempt" | |
| if build; then ok=1; break; fi | |
| echo "attempt $attempt failed; retrying in 15s" | |
| sleep 15 | |
| done | |
| if [ "$ok" != "1" ] && [ "${{ matrix.baseline }}" = "true" ]; then | |
| # A flaky baseline artifact must not cost us the primary binary for | |
| # this platform. Fall back to the native build alone; the installer | |
| # only requests -baseline on non-AVX2 x64 hardware, and the native | |
| # asset still serves everyone else. | |
| echo "baseline builds failed 3x — falling back to the native build only" | |
| bun run script/build.ts --single && ok=1 | |
| fi | |
| [ "$ok" = "1" ] || { echo "build failed after retries"; exit 1; } | |
| ls -la dist/ | |
| - name: Verify the binary is branded and self-contained | |
| shell: bash | |
| working-directory: packages/opencode | |
| run: | | |
| set -eu | |
| # Address the exact matrix target. `find | head -1` picked whichever | |
| # directory sorted first, which on x64 runs was the musl variant — | |
| # a musl-linked binary cannot execute on a glibc runner (missing ELF | |
| # interpreter, which surfaces as "No such file or directory" / 127). | |
| DIST="dist/opencode-${{ matrix.target }}" | |
| BIN="$DIST/bin/utmstack" | |
| [ -f "$BIN" ] || BIN="$DIST/bin/utmstack.exe" | |
| [ -f "$BIN" ] || { echo "no binary at $DIST/bin"; ls -R dist | head -40; exit 1; } | |
| echo "binary: $BIN" | |
| got=$("$BIN" --version | tr -d '\r') | |
| [ "$got" = "${{ steps.version.outputs.value }}" ] || { | |
| echo "version mismatch: got '$got'"; exit 1; } | |
| "$BIN" --help 2>&1 | grep -q "utmstack completion" || { | |
| echo "help output is not branded"; exit 1; } | |
| # First run must seed config and extract the embedded skills/agents. | |
| TMPCFG=$(mktemp -d) | |
| XDG_CONFIG_HOME="$TMPCFG" APPDATA="$TMPCFG" "$BIN" --help >/dev/null 2>&1 || true | |
| XDG_CONFIG_HOME="$TMPCFG" APPDATA="$TMPCFG" "$BIN" mcp list >/dev/null 2>&1 || true | |
| test -f "$TMPCFG/utmstack/utmstack.json" || { | |
| echo "default config was not seeded"; exit 1; } | |
| skills=$(ls "$TMPCFG/utmstack/skills" 2>/dev/null | wc -l | tr -d ' ') | |
| agents=$(ls "$TMPCFG/utmstack/agent"/*.md 2>/dev/null | wc -l | tr -d ' ') | |
| echo "seeded config, $skills skills, $agents agents" | |
| [ "$skills" -gt 0 ] || { echo "no skills extracted"; exit 1; } | |
| [ "$agents" -gt 0 ] || { echo "no agents extracted"; exit 1; } | |
| grep -q '"utmstack-mcp"' "$TMPCFG/utmstack/utmstack.json" || { | |
| echo "default config does not reference utmstack-mcp"; exit 1; } | |
| - name: Package | |
| shell: bash | |
| working-directory: packages/opencode | |
| run: | | |
| set -eu | |
| # Absolute output dir. A relative "../.." from inside dist/<name>/bin | |
| # is five levels from the repo root and is trivially miscounted. | |
| OUT="$GITHUB_WORKSPACE/out" | |
| mkdir -p "$OUT" | |
| for dir in dist/*/; do | |
| name=$(basename "$dir") | |
| [ -d "$dir/bin" ] || continue | |
| # The build emits musl variants for free, but they are untested and | |
| # unadvertised, and the installer refuses musl outright. Publishing | |
| # them would promise support we have not verified. | |
| case "$name" in *-musl*) echo "skipping $name (musl, not published)"; continue ;; esac | |
| asset=$(echo "$name" | sed 's/^opencode-/utmstack-/') | |
| if [ "${{ matrix.ext }}" = "tar.gz" ]; then | |
| tar -czf "$OUT/${asset}.tar.gz" -C "$dir/bin" . | |
| else | |
| # COPYFILE_DISABLE keeps macOS AppleDouble "._*" files out of the | |
| # zip; -X drops extended attributes. Both are no-ops on Windows. | |
| (cd "$dir/bin" && env COPYFILE_DISABLE=1 7z a -tzip "$OUT/${asset}.zip" . >/dev/null) | |
| fi | |
| done | |
| ls -lh "$OUT" | |
| - uses: actions/upload-artifact@v4 | |
| with: | |
| name: ${{ matrix.target }} | |
| path: out/* | |
| release: | |
| needs: build | |
| runs-on: ubuntu-22.04 | |
| if: startsWith(github.ref, 'refs/tags/v') | |
| steps: | |
| - uses: actions/download-artifact@v4 | |
| with: { path: artifacts } | |
| - name: Collect and checksum | |
| run: | | |
| mkdir -p out | |
| find artifacts -type f \( -name '*.tar.gz' -o -name '*.zip' \) -exec cp {} out/ \; | |
| cd out && sha256sum * > checksums.txt | |
| echo "Publishing:"; cat checksums.txt | |
| - uses: softprops/action-gh-release@v2 | |
| with: | |
| files: out/* | |
| generate_release_notes: true |