Files
wehub-resource-sync d68f003000
CI / Change detection (push) Has been cancelled
CI / Version drift (push) Has been cancelled
CI / Lint (push) Has been cancelled
CI / Workflow RLM cache (push) Has been cancelled
CI / Test (macos-latest) (push) Has been cancelled
CI / Test (ubuntu-latest) (push) Has been cancelled
CI / Test (windows-latest) (push) Has been cancelled
CI / npm wrapper smoke (push) Has been cancelled
CI / Mobile runtime smoke (push) Has been cancelled
CI / Workflow lint (push) Has been cancelled
CI / Documentation (push) Has been cancelled
Web Frontend / Lint & Type Check (push) Failing after 1s
Auto-close harvested PRs / close (push) Failing after 1s
cargo-deny / cargo-deny (bans licenses sources) (push) Failing after 1s
Security audit / cargo-audit (push) Failing after 1s
Sync to CNB / sync (push) Failing after 1s
Web Frontend / Deploy to Cloudflare (push) Waiting to run
cargo-deny / cargo-deny (advisories) (push) Failing after 3s
chore: import upstream snapshot with attribution
2026-07-13 12:08:23 +08:00

653 lines
26 KiB
YAML

name: Release
on:
push:
tags: ['v*']
workflow_dispatch:
inputs:
version:
description: 'Package/release version to publish to npm, without the leading v'
required: true
type: string
permissions:
contents: read
env:
CARGO_TERM_COLOR: always
CARGO_INCREMENTAL: 0
RUSTFLAGS: -Dwarnings
jobs:
parity:
if: github.event_name == 'push'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
- uses: dtolnay/rust-toolchain@master
with:
toolchain: stable
components: clippy, rustfmt
- uses: mozilla-actions/sccache-action@v0.0.10
id: sccache
# A cache bootstrap failure must degrade to an uncached build, never
# fail a release gate.
continue-on-error: true
- name: Enable sccache
if: steps.sccache.outcome == 'success'
shell: bash
run: |
echo "SCCACHE_GHA_ENABLED=true" >> "${GITHUB_ENV}"
echo "RUSTC_WRAPPER=sccache" >> "${GITHUB_ENV}"
echo "SCCACHE_IGNORE_SERVER_IO_ERROR=1" >> "${GITHUB_ENV}"
- name: Install Linux system dependencies
if: runner.os == 'Linux'
run: |
for i in 1 2 3 4 5; do
sudo apt-get update && break
echo "apt-get update failed (attempt $i); retrying in 15s"
sleep 15
done
sudo apt-get install -y libdbus-1-dev pkg-config
- uses: Swatinem/rust-cache@v2
with:
cache-bin: false
- name: Format check
run: cargo fmt --all -- --check
- name: Compile check
run: cargo check --workspace --all-targets --locked
- name: OHOS dependency graph
run: ./scripts/release/check-ohos-deps.sh
- name: Clippy
run: |
cargo clippy --workspace --all-targets --all-features --locked -- \
-D warnings \
-A clippy::uninlined_format_args \
-A clippy::too_many_arguments \
-A clippy::unnecessary_map_or \
-A clippy::collapsible_if \
-A clippy::assertions_on_constants
- name: Workspace tests
run: cargo test --workspace --all-features --locked
- name: Protocol schema parity
run: cargo test -p codewhale-protocol --test parity_protocol --locked
- name: State persistence parity
run: cargo test -p codewhale-state --test parity_state --locked
- name: Lockfile drift guard
run: git diff --exit-code -- Cargo.lock
resolve:
runs-on: ubuntu-latest
outputs:
tag: ${{ steps.release.outputs.tag }}
source_ref: ${{ steps.release.outputs.source_ref }}
sha: ${{ steps.release.outputs.sha }}
steps:
- uses: actions/checkout@v7
with:
fetch-depth: 0
- name: Resolve release source
id: release
shell: bash
run: |
if [ "${GITHUB_EVENT_NAME}" = "workflow_dispatch" ]; then
tag="v${{ inputs.version }}"
if git rev-parse "refs/tags/${tag}" >/dev/null 2>&1; then
sha="$(git rev-list -n 1 "${tag}")"
source_ref="${tag}"
else
# Tag doesn't exist yet — build from HEAD
sha="${GITHUB_SHA}"
source_ref="${GITHUB_SHA}"
echo "Tag ${tag} not found; building from ${source_ref} @ ${sha}"
fi
else
tag="${GITHUB_REF_NAME}"
sha="${GITHUB_SHA}"
source_ref="${GITHUB_REF_NAME}"
fi
if [ -z "${sha}" ]; then
echo "Unable to resolve release source for ${tag}" >&2
exit 1
fi
echo "tag=${tag}" >> "$GITHUB_OUTPUT"
echo "source_ref=${source_ref}" >> "$GITHUB_OUTPUT"
echo "sha=${sha}" >> "$GITHUB_OUTPUT"
- name: Require release source on main
run: ./scripts/release/ensure-release-on-main.sh "${{ steps.release.outputs.sha }}"
build:
needs: [parity, resolve]
# `parity` is gated to tag-push events. On manual `workflow_dispatch`,
# parity is skipped, so let `build` proceed when resolve succeeded and
# parity either succeeded or was skipped — but never when parity actually
# failed or the run was cancelled. Operators using dispatch are expected
# to have already run the same gates locally / via ci.yml on `main`.
if: ${{ !cancelled() && needs.resolve.result == 'success' && (needs.parity.result == 'success' || needs.parity.result == 'skipped') }}
strategy:
fail-fast: false
matrix:
include:
- os: ubuntu-latest
target: x86_64-unknown-linux-musl
platform: linux-x64
cli_binary: codewhale
shim_binary: codew
tui_binary: codewhale-tui
cli_artifact: codewhale-linux-x64
shim_artifact: codew-linux-x64
tui_artifact: codewhale-tui-linux-x64
- os: ubuntu-latest
target: aarch64-unknown-linux-gnu
platform: linux-arm64
cli_binary: codewhale
shim_binary: codew
tui_binary: codewhale-tui
cli_artifact: codewhale-linux-arm64
shim_artifact: codew-linux-arm64
tui_artifact: codewhale-tui-linux-arm64
- os: ubuntu-latest
target: aarch64-linux-android
platform: android-arm64
cli_binary: codewhale
shim_binary: codew
tui_binary: codewhale-tui
cli_artifact: codewhale-android-arm64
shim_artifact: codew-android-arm64
tui_artifact: codewhale-tui-android-arm64
- os: macos-latest
target: x86_64-apple-darwin
platform: macos-x64
cli_binary: codewhale
shim_binary: codew
tui_binary: codewhale-tui
cli_artifact: codewhale-macos-x64
shim_artifact: codew-macos-x64
tui_artifact: codewhale-tui-macos-x64
- os: macos-latest
target: aarch64-apple-darwin
platform: macos-arm64
cli_binary: codewhale
shim_binary: codew
tui_binary: codewhale-tui
cli_artifact: codewhale-macos-arm64
shim_artifact: codew-macos-arm64
tui_artifact: codewhale-tui-macos-arm64
- os: windows-latest
target: x86_64-pc-windows-msvc
platform: windows-x64
cli_binary: codewhale.exe
shim_binary: codew.exe
tui_binary: codewhale-tui.exe
cli_artifact: codewhale-windows-x64.exe
shim_artifact: codew-windows-x64.exe
tui_artifact: codewhale-tui-windows-x64.exe
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v7
with:
ref: ${{ needs.resolve.outputs.source_ref }}
- uses: dtolnay/rust-toolchain@master
with:
toolchain: stable
targets: ${{ matrix.target }}
- name: Install Rust target
run: rustup target add --toolchain stable ${{ matrix.target }}
- uses: mozilla-actions/sccache-action@v0.0.10
id: sccache
# A cache bootstrap failure must degrade to an uncached build, never
# fail a release gate.
continue-on-error: true
- name: Enable sccache
if: steps.sccache.outcome == 'success'
shell: bash
run: |
echo "SCCACHE_GHA_ENABLED=true" >> "${GITHUB_ENV}"
echo "RUSTC_WRAPPER=sccache" >> "${GITHUB_ENV}"
echo "SCCACHE_IGNORE_SERVER_IO_ERROR=1" >> "${GITHUB_ENV}"
- uses: Swatinem/rust-cache@v2
with:
cache-bin: false
- name: Install Linux system dependencies
if: runner.os == 'Linux' && matrix.target == 'aarch64-unknown-linux-gnu'
run: |
for i in 1 2 3 4 5; do
sudo apt-get update && break
echo "apt-get update failed (attempt $i); retrying in 15s"
sleep 15
done
sudo apt-get install -y libdbus-1-dev pkg-config
- name: Build static Linux x64 binary (musl)
if: matrix.target == 'x86_64-unknown-linux-musl'
shell: bash
run: |
sudo apt-get update
sudo apt-get install -y musl-tools
rustup target add --toolchain stable x86_64-unknown-linux-musl
cargo build --release --locked --target x86_64-unknown-linux-musl -p codewhale-cli -p codewhale-tui
- name: Install AArch64 cross-compilation toolchain
if: matrix.target == 'aarch64-unknown-linux-gnu' && runner.os == 'Linux'
run: |
. /etc/os-release
sudo tee /etc/apt/sources.list.d/arm64.sources <<SRC
Types: deb
URIs: http://ports.ubuntu.com/ubuntu-ports/
Suites: ${UBUNTU_CODENAME} ${UBUNTU_CODENAME}-updates ${UBUNTU_CODENAME}-security
Components: main universe
Architectures: arm64
Signed-By: /usr/share/keyrings/ubuntu-archive-keyring.gpg
SRC
sudo dpkg --add-architecture arm64
sudo apt-get update -o Dir::Etc::sourcelist=/etc/apt/sources.list.d/arm64.sources -o Dir::Etc::sourceparts=- -o APT::Get::List-Cleanup=0
sudo apt-get install -y gcc-aarch64-linux-gnu libc6-dev-arm64-cross libdbus-1-dev:arm64 pkg-config
- name: Configure Android NDK linker
if: matrix.target == 'aarch64-linux-android' && runner.os == 'Linux'
shell: bash
env:
ANDROID_NDK_VERSION: 27.2.12479018
run: |
set -euo pipefail
sudo apt-get update
sudo apt-get install -y libclang-dev
ndk="${ANDROID_NDK_ROOT:-${ANDROID_NDK_HOME:-}}"
linker=""
if [[ -n "${ndk}" ]]; then
linker="${ndk}/toolchains/llvm/prebuilt/linux-x86_64/bin/aarch64-linux-android24-clang"
fi
if [[ -z "${linker}" || ! -x "${linker}" ]]; then
if ! command -v sdkmanager >/dev/null 2>&1; then
echo "sdkmanager is required to install Android NDK ${ANDROID_NDK_VERSION}" >&2
exit 1
fi
android_home="${ANDROID_HOME:-${ANDROID_SDK_ROOT:-}}"
if [[ -z "${android_home}" ]]; then
echo "ANDROID_HOME or ANDROID_SDK_ROOT is required to install Android NDK ${ANDROID_NDK_VERSION}" >&2
exit 1
fi
yes | sdkmanager --licenses >/dev/null || true
sdkmanager --install "ndk;${ANDROID_NDK_VERSION}"
ndk="${android_home}/ndk/${ANDROID_NDK_VERSION}"
linker="${ndk}/toolchains/llvm/prebuilt/linux-x86_64/bin/aarch64-linux-android24-clang"
fi
ar="${ndk}/toolchains/llvm/prebuilt/linux-x86_64/bin/llvm-ar"
if [[ ! -x "${linker}" ]]; then
echo "Android linker not found: ${linker}" >&2
exit 1
fi
if [[ ! -x "${ar}" ]]; then
echo "Android archiver not found: ${ar}" >&2
exit 1
fi
echo "ANDROID_NDK_ROOT=${ndk}" >> "${GITHUB_ENV}"
echo "ANDROID_NDK_HOME=${ndk}" >> "${GITHUB_ENV}"
echo "CC_aarch64_linux_android=${linker}" >> "${GITHUB_ENV}"
echo "AR_aarch64_linux_android=${ar}" >> "${GITHUB_ENV}"
echo "CARGO_TARGET_AARCH64_LINUX_ANDROID_LINKER=${linker}" >> "${GITHUB_ENV}"
echo "BINDGEN_EXTRA_CLANG_ARGS_aarch64_linux_android=--target=aarch64-linux-android24 --sysroot=${ndk}/toolchains/llvm/prebuilt/linux-x86_64/sysroot" >> "${GITHUB_ENV}"
- name: Build
if: matrix.target != 'x86_64-unknown-linux-musl'
shell: bash
env:
DEEPSEEK_BUILD_SHA: ${{ needs.resolve.outputs.sha }}
CC_aarch64_unknown_linux_gnu: aarch64-linux-gnu-gcc
CARGO_TARGET_AARCH64_UNKNOWN_LINUX_GNU_LINKER: aarch64-linux-gnu-gcc
PKG_CONFIG_ALLOW_CROSS: 1
PKG_CONFIG_LIBDIR_aarch64_unknown_linux_gnu: /usr/lib/aarch64-linux-gnu/pkgconfig
run: cargo build --release --locked --target ${{ matrix.target }} -p codewhale-cli -p codewhale-tui
- name: Stage binaries
shell: bash
run: |
stage_binary() {
local binary="$1"
local artifact="$2"
local bin_path="target/${{ matrix.target }}/release/${binary}"
if [ ! -f "${bin_path}" ]; then
echo "Binary not at ${bin_path}; searching target/ for ${binary}:"
find target -name "${binary}" -type f
exit 1
fi
cp "${bin_path}" "${artifact}"
}
stage_binary "${{ matrix.cli_binary }}" "${{ matrix.cli_artifact }}"
stage_binary "${{ matrix.shim_binary }}" "${{ matrix.shim_artifact }}"
stage_binary "${{ matrix.tui_binary }}" "${{ matrix.tui_artifact }}"
- uses: actions/upload-artifact@v7
with:
name: ${{ matrix.cli_artifact }}
path: ${{ matrix.cli_artifact }}
- uses: actions/upload-artifact@v7
with:
name: ${{ matrix.shim_artifact }}
path: ${{ matrix.shim_artifact }}
- uses: actions/upload-artifact@v7
with:
name: ${{ matrix.tui_artifact }}
path: ${{ matrix.tui_artifact }}
bundle:
needs: [build, resolve]
if: ${{ !cancelled() && needs.build.result == 'success' }}
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
with:
ref: ${{ needs.resolve.outputs.source_ref }}
- uses: actions/download-artifact@v8
with:
path: artifacts
pattern: '*'
- name: Create platform archives
shell: bash
run: |
set -euo pipefail
mkdir -p bundles/checksums
MANIFEST="bundles/checksums/codewhale-bundles-sha256.txt"
: > "$MANIFEST"
bundle() {
local platform="$1" # linux-x64, linux-arm64, android-arm64, macos-x64, macos-arm64, windows-x64
local cli_src="$2" # artifact name for codewhale binary
local shim_src="$3" # artifact name for codew shim
local tui_src="$4" # artifact name for codewhale-tui binary
local ext="$5" # tar.gz or zip
local variant="$6" # '' (standard) or 'portable' (Windows only, no install script)
shift 6
local dir="bundles/codewhale-${platform}${variant:+-}${variant}"
mkdir -p "$dir"
# Copy binaries, stripping platform suffixes
local cli_dst="codewhale"
local shim_dst="codew"
local tui_dst="codewhale-tui"
if [[ "$platform" == windows-* ]]; then
cli_dst="codewhale.exe"
shim_dst="codew.exe"
tui_dst="codewhale-tui.exe"
fi
cp "artifacts/${cli_src}/${cli_src}" "$dir/${cli_dst}"
cp "artifacts/${shim_src}/${shim_src}" "$dir/${shim_dst}"
cp "artifacts/${tui_src}/${tui_src}" "$dir/${tui_dst}"
# Add install script (standard variant only)
if [[ "$variant" != "portable" ]]; then
if [[ "$platform" == windows-* ]]; then
cp scripts/release/install.bat "$dir/"
# Convert line endings to CRLF for Windows
sed -i 's/$/\r/' "$dir/install.bat" 2>/dev/null || true
else
cp scripts/release/install.sh "$dir/"
chmod +x "$dir/install.sh"
fi
fi
if [[ "$ext" == "zip" ]]; then
(cd bundles && zip -r "codewhale-${platform}${variant:+-}${variant}.zip" "codewhale-${platform}${variant:+-}${variant}/")
else
tar -czf "bundles/codewhale-${platform}${variant:+-}${variant}.tar.gz" -C bundles "codewhale-${platform}${variant:+-}${variant}/"
fi
local archive="codewhale-${platform}${variant:+-}${variant}.${ext}"
sha256sum "bundles/${archive}" | awk '{printf "%s %s\n", $1, $2}' >> "$MANIFEST"
echo " Created bundles/${archive}"
}
# Platform: linux-x64
bundle linux-x64 \
codewhale-linux-x64 codew-linux-x64 codewhale-tui-linux-x64 tar.gz ""
# Platform: linux-arm64
bundle linux-arm64 \
codewhale-linux-arm64 codew-linux-arm64 codewhale-tui-linux-arm64 tar.gz ""
# Platform: android-arm64 (Termux)
bundle android-arm64 \
codewhale-android-arm64 codew-android-arm64 codewhale-tui-android-arm64 tar.gz ""
# Platform: macos-x64
bundle macos-x64 \
codewhale-macos-x64 codew-macos-x64 codewhale-tui-macos-x64 tar.gz ""
# Platform: macos-arm64
bundle macos-arm64 \
codewhale-macos-arm64 codew-macos-arm64 codewhale-tui-macos-arm64 tar.gz ""
# Platform: windows-x64 (standard + portable)
bundle windows-x64 \
codewhale-windows-x64.exe codew-windows-x64.exe codewhale-tui-windows-x64.exe zip ""
bundle windows-x64 \
codewhale-windows-x64.exe codew-windows-x64.exe codewhale-tui-windows-x64.exe zip "portable"
echo ""
echo "=== Archive checksums ==="
cat "$MANIFEST"
- name: Upload bundle artifacts
uses: actions/upload-artifact@v7
with:
name: codewhale-bundles
path: bundles/*
if-no-files-found: error
windows-installer:
needs: [build, resolve]
if: ${{ !cancelled() && needs.build.result == 'success' }}
runs-on: windows-latest
steps:
- uses: actions/checkout@v7
with:
ref: ${{ needs.resolve.outputs.source_ref }}
- uses: actions/download-artifact@v8
with:
path: artifacts
pattern: '*windows-x64.exe'
- name: Install NSIS
shell: pwsh
run: choco install nsis -y --no-progress
- name: Build NSIS installer
shell: pwsh
run: |
$ErrorActionPreference = "Stop"
$version = "${{ needs.resolve.outputs.tag }}".TrimStart("v")
Copy-Item "artifacts\codewhale-windows-x64.exe\codewhale-windows-x64.exe" "scripts\installer\codewhale.exe"
Copy-Item "artifacts\codew-windows-x64.exe\codew-windows-x64.exe" "scripts\installer\codew.exe"
Copy-Item "artifacts\codewhale-tui-windows-x64.exe\codewhale-tui-windows-x64.exe" "scripts\installer\codewhale-tui.exe"
$makensis = "${env:ProgramFiles(x86)}\NSIS\makensis.exe"
if (!(Test-Path $makensis)) {
$makensis = "${env:ProgramFiles}\NSIS\makensis.exe"
}
if (!(Test-Path $makensis)) {
throw "makensis.exe not found after NSIS install"
}
Push-Location scripts\installer
& $makensis "/DVERSION=$version" "codewhale.nsi"
Pop-Location
if (!(Test-Path "scripts\installer\CodeWhaleSetup.exe")) {
throw "CodeWhaleSetup.exe was not produced"
}
- name: Upload installer artifact
uses: actions/upload-artifact@v7
with:
name: CodeWhaleSetup.exe
path: scripts/installer/CodeWhaleSetup.exe
if-no-files-found: error
docker:
needs: [build, resolve]
if: ${{ !cancelled() && needs.build.result == 'success' }}
runs-on: ubuntu-latest
permissions:
contents: read
packages: write
steps:
- name: Checkout release source
uses: actions/checkout@v7
with:
ref: ${{ needs.resolve.outputs.source_ref }}
path: source
- name: Checkout release infrastructure
uses: actions/checkout@v7
with:
path: infra
- name: Set up QEMU
uses: docker/setup-qemu-action@v4
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v4
- name: Log in to GitHub Container Registry
uses: docker/login-action@v4
with:
registry: ghcr.io
username: ${{ github.repository_owner }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Normalize image name
id: image
shell: bash
run: echo "name=ghcr.io/${GITHUB_REPOSITORY,,}" >> "$GITHUB_OUTPUT"
- name: Extract metadata
id: meta
uses: docker/metadata-action@v6
with:
images: |
${{ steps.image.outputs.name }}
tags: |
type=semver,pattern={{version}}
type=semver,pattern={{major}}.{{minor}}
type=semver,pattern=v{{major}}
type=ref,event=tag
type=semver,pattern={{version}},value=${{ needs.resolve.outputs.tag }},enable=${{ github.event_name == 'workflow_dispatch' }}
type=semver,pattern={{major}}.{{minor}},value=${{ needs.resolve.outputs.tag }},enable=${{ github.event_name == 'workflow_dispatch' }}
type=semver,pattern=v{{major}},value=${{ needs.resolve.outputs.tag }},enable=${{ github.event_name == 'workflow_dispatch' }}
type=raw,value=${{ inputs.version }},enable=${{ github.event_name == 'workflow_dispatch' }}
type=raw,value=v${{ inputs.version }},enable=${{ github.event_name == 'workflow_dispatch' }}
type=raw,value=latest
- name: Build and push
uses: docker/build-push-action@v7
env:
# The build record is useful in CI, but it is uploaded as a
# `.dockerbuild` artifact. The release job intentionally downloads
# all binary artifacts, so suppress the extra record artifact there.
DOCKER_BUILD_RECORD_UPLOAD: false
DOCKER_BUILD_SUMMARY: false
with:
context: source
file: infra/Dockerfile
platforms: linux/amd64,linux/arm64
push: true
build-args: |
DEEPSEEK_BUILD_SHA=${{ needs.resolve.outputs.sha }}
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
cache-from: type=gha
cache-to: type=gha,mode=max
release:
needs: [build, bundle, windows-installer, docker, resolve]
if: ${{ !cancelled() && needs.build.result == 'success' && needs.bundle.result == 'success' && needs.windows-installer.result == 'success' }}
runs-on: ubuntu-latest
permissions:
contents: write
steps:
# Checked out into a subdirectory so it cannot clobber the downloaded
# artifacts; used for the release-body generator and the CHANGELOG.
- uses: actions/checkout@v7
with:
ref: ${{ needs.resolve.outputs.tag }}
path: repo
- uses: actions/download-artifact@v8
with:
path: artifacts
pattern: '*'
- name: Generate Windows npm launcher asset
shell: bash
run: |
set -euo pipefail
mkdir -p artifacts/codewhale-windows-launcher
{
printf '@echo off\r\n'
printf 'where wt >nul 2>nul\r\n'
printf 'set NO_ANIMATIONS=1\r\n'
printf 'if "%%ERRORLEVEL%%"=="0" (\r\n'
printf ' wt --title CodeWhale cmd /k "%%~dp0codewhale-windows-x64.exe"\r\n'
printf ') else (\r\n'
printf ' "%%~dp0codewhale-windows-x64.exe"\r\n'
printf ')\r\n'
printf '\r\n'
} > artifacts/codewhale-windows-launcher/codewhale.bat
- name: List artifacts
run: find artifacts -type f
- name: Generate checksum manifest
shell: bash
run: |
mkdir -p artifacts/checksums
# Canonical manifest used by codewhale's `codewhale update` flow.
manifest="artifacts/checksums/codewhale-artifacts-sha256.txt"
: > "${manifest}"
while IFS= read -r -d '' file; do
hash="$(sha256sum "${file}" | awk '{print $1}')"
base="$(basename "${file}")"
printf '%s %s\n' "${hash}" "${base}" >> "${manifest}"
done < <(find artifacts -type f ! -path 'artifacts/checksums/*' -print0 | sort -z)
cat "${manifest}"
- name: Generate release body from CHANGELOG
shell: bash
run: |
./repo/scripts/release/generate-release-body.sh \
"${{ needs.resolve.outputs.tag }}" repo/CHANGELOG.md > release-body.md
- uses: softprops/action-gh-release@v3
with:
tag_name: ${{ needs.resolve.outputs.tag }}
files: artifacts/*/*
prerelease: false
body_path: release-body.md
homebrew:
needs: [release, resolve]
if: ${{ !cancelled() && needs.release.result == 'success' }}
runs-on: ubuntu-latest
permissions:
contents: read
steps:
- name: Check Homebrew tap token
id: homebrew-token
env:
TOKEN: ${{ secrets.HOMEBREW_TAP_PAT || secrets.RELEASE_TAG_PAT }}
run: |
if [ -z "${TOKEN:-}" ]; then
echo "No Homebrew tap token configured; skipping tap update."
echo "available=false" >> "${GITHUB_OUTPUT}"
else
echo "available=true" >> "${GITHUB_OUTPUT}"
fi
# Checkout main (not the tag) so the release-infra script is always
# available, even for tags created before this workflow was added.
- uses: actions/checkout@v7
if: steps.homebrew-token.outputs.available == 'true'
with:
ref: main
- name: Download checksum manifest
if: steps.homebrew-token.outputs.available == 'true'
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
gh release download ${{ needs.resolve.outputs.tag }} \
--repo ${{ github.repository }} \
--pattern 'codewhale-artifacts-sha256.txt' \
--dir /tmp
- name: Update Homebrew tap
if: steps.homebrew-token.outputs.available == 'true'
env:
TAG: ${{ needs.resolve.outputs.tag }}
MANIFEST: /tmp/codewhale-artifacts-sha256.txt
TAP_REPO: Hmbown/homebrew-deepseek-tui
TOKEN: ${{ secrets.HOMEBREW_TAP_PAT || secrets.RELEASE_TAG_PAT }}
run: bash .github/scripts/update-homebrew-tap.sh
# npm publish is intentionally not automated. The npm account requires 2FA OTP
# on every publish, and a granular automation token that bypasses 2FA has not
# been provisioned. Release the npm wrapper manually from a developer machine
# after the GitHub Release has been created — see CLAUDE.md "Releases" for the
# exact commands.