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
653 lines
26 KiB
YAML
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.
|