Files
DeepSeek-TUI/.github/workflows/release-artifacts.yml
CodeWhale Bot d3d349deb6 feat(packaging): add verified Omarchy AUR path
Render codewhale-bin PKGBUILD and .SRCINFO metadata from the workspace version and checksum-verified x64/arm64 release archives. Preserve the Arch runtime dependency contract, reject placeholders and tampered assets, and validate package contents deterministically.\n\nWire the renderer into CI and release-candidate smoke without publishing to AUR, and document Omarchy install/update plus the separately authorized release handoff.
2026-08-24 14:04:58 -07:00

422 lines
18 KiB
YAML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
name: Release artifacts
on:
workflow_call:
inputs:
source_sha:
description: Exact 40-character source commit to build
required: true
type: string
version:
description: Workspace version without a v prefix
required: true
type: string
retention_days:
description: Retention for Actions-only intermediate and assembled artifacts
required: false
default: 7
type: number
permissions:
contents: read
env:
CARGO_TERM_COLOR: always
CARGO_INCREMENTAL: 0
RUSTFLAGS: -Dwarnings
# Build identity is the trusted workflow SHA. Callers pass source_sha only
# so `pin` can refuse a mismatch; it must not retarget checkout or caches.
CODEWHALE_BUILD_SHA: ${{ github.sha }}
jobs:
pin:
name: Pin caller SHA to this run
timeout-minutes: 10
runs-on: ubuntu-latest
steps:
- name: Require source_sha equals github.sha
env:
SOURCE_SHA: ${{ inputs.source_sha }}
run: |
set -euo pipefail
if [[ "${#SOURCE_SHA}" -ne 40 || "${SOURCE_SHA}" =~ [^0-9a-fA-F] ]]; then
echo "::error::source_sha must be a full 40-character commit SHA." >&2
exit 1
fi
expected="$(printf '%s' "${SOURCE_SHA}" | tr '[:upper:]' '[:lower:]')"
actual="$(printf '%s' "${GITHUB_SHA}" | tr '[:upper:]' '[:lower:]')"
if [[ "${actual}" != "${expected}" ]]; then
echo "::error::Reusable workflow SHA ${actual} does not match source_sha ${SOURCE_SHA}." >&2
exit 1
fi
build:
name: Build ${{ matrix.platform }}
timeout-minutes: 90
# FreeBSD is a source-build target validated via `cargo check --target x86_64-unknown-freebsd -p codewhale-cli --locked`
# (see packaging/freebsd/README.md and docs/INSTALL.md#freebsd). The 7×1 prebuilt matrix stays 7 targets;
# FreeBSD has no prebuilt asset, no npm binary, and no matrix bloat — it builds from source.
strategy:
fail-fast: false
matrix:
include:
- os: ubuntu-latest
target: x86_64-unknown-linux-musl
platform: linux-x64
cli_binary: codewhale
shim_binary: codew
cli_artifact: codewhale-linux-x64
shim_artifact: codew-linux-x64
compat_tui_artifact: codewhale-tui-linux-x64
- os: ubuntu-24.04-arm
target: aarch64-unknown-linux-musl
platform: linux-arm64
cli_binary: codewhale
shim_binary: codew
cli_artifact: codewhale-linux-arm64
shim_artifact: codew-linux-arm64
compat_tui_artifact: codewhale-tui-linux-arm64
- os: ubuntu-latest
target: aarch64-linux-android
platform: android-arm64
cli_binary: codewhale
shim_binary: codew
cli_artifact: codewhale-android-arm64
shim_artifact: codew-android-arm64
compat_tui_artifact: codewhale-tui-android-arm64
- os: macos-latest
target: x86_64-apple-darwin
platform: macos-x64
cli_binary: codewhale
shim_binary: codew
cli_artifact: codewhale-macos-x64
shim_artifact: codew-macos-x64
compat_tui_artifact: codewhale-tui-macos-x64
- os: macos-latest
target: aarch64-apple-darwin
platform: macos-arm64
cli_binary: codewhale
shim_binary: codew
cli_artifact: codewhale-macos-arm64
shim_artifact: codew-macos-arm64
compat_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
cli_artifact: codewhale-windows-x64.exe
shim_artifact: codew-windows-x64.exe
compat_tui_artifact: codewhale-tui-windows-x64.exe
- os: windows-11-arm
target: aarch64-pc-windows-msvc
platform: windows-arm64
cli_binary: codewhale.exe
shim_binary: codew.exe
cli_artifact: codewhale-windows-arm64.exe
shim_artifact: codew-windows-arm64.exe
compat_tui_artifact: codewhale-tui-windows-arm64.exe
runs-on: ${{ matrix.os }}
needs: pin
steps:
# No ref: — GITHUB_SHA only. CodeQL treats workflow_call checkout-with-ref
# and any ref named *sha* as an untrusted checkout (cache-poisoning).
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7
- uses: dtolnay/rust-toolchain@6c977a6ca4077a0ceb28ffbe03f59d46e9ac8772 # master 2026-07-18
with:
toolchain: stable
targets: ${{ matrix.target }}
- uses: mozilla-actions/sccache-action@fc920bf0ec8de6ee65d409111f7ec508035751ba # v0.0.11
id: sccache
continue-on-error: true
- name: Enable sccache
if: steps.sccache.outcome == 'success'
shell: bash
run: |
{
echo "SCCACHE_GHA_ENABLED=true"
echo "RUSTC_WRAPPER=sccache"
echo "SCCACHE_IGNORE_SERVER_IO_ERROR=1"
} >> "${GITHUB_ENV}"
# Restore after the trusted lockfile is on disk. Key is OS + arch +
# explicit stable toolchain + rust-cache's Cargo.lock / rust-toolchain
# hash. Never interpolate github.event, github.ref, github.sha, or inputs.
- uses: Swatinem/rust-cache@e18b497796c12c097a38f9edb9d0641fb99eee32 # v2
with:
cache-bin: false
prefix-key: v1-${{ runner.os }}-${{ runner.arch }}-stable
- name: Build static Linux binaries (musl)
if: endsWith(matrix.target, '-unknown-linux-musl')
shell: bash
run: |
sudo apt-get update
sudo apt-get install -y binutils musl-tools
rustup target add --toolchain stable ${{ matrix.target }}
cargo build --profile dist --locked --target ${{ matrix.target }} -p codewhale-cli
- 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}"
echo "ANDROID_NDK_HOME=${ndk}"
echo "CC_aarch64_linux_android=${linker}"
echo "AR_aarch64_linux_android=${ar}"
echo "CARGO_TARGET_AARCH64_LINUX_ANDROID_LINKER=${linker}"
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: ${{ !endsWith(matrix.target, '-unknown-linux-musl') }}
shell: bash
run: cargo build --profile dist --locked --target ${{ matrix.target }} -p codewhale-cli
- name: Materialize codew command alias
shell: bash
run: |
bin_dir="target/${{ matrix.target }}/dist"
cp "${bin_dir}/${{ matrix.cli_binary }}" "${bin_dir}/${{ matrix.shim_binary }}"
cmp "${bin_dir}/${{ matrix.cli_binary }}" "${bin_dir}/${{ matrix.shim_binary }}"
- name: Verify static Linux binaries and launch on matching native runners
if: >-
endsWith(matrix.target, '-unknown-linux-musl') &&
((startsWith(matrix.target, 'x86_64-') && runner.arch == 'X64') ||
(startsWith(matrix.target, 'aarch64-') && runner.arch == 'ARM64'))
shell: bash
run: |
set -euo pipefail
bin_dir="target/${{ matrix.target }}/dist"
for binary in "${{ matrix.cli_binary }}" "${{ matrix.shim_binary }}"; do
bin_path="${bin_dir}/${binary}"
if readelf -l "${bin_path}" | grep -Fq 'INTERP'; then
echo "Expected a static musl binary, but ${bin_path} has an ELF interpreter" >&2
exit 1
fi
"${bin_path}" --version
done
- name: Smoke binaries on matching native runners
if: >-
matrix.target != 'aarch64-linux-android' &&
((startsWith(matrix.target, 'x86_64-') && runner.arch == 'X64') ||
(startsWith(matrix.target, 'aarch64-') && runner.arch == 'ARM64'))
shell: bash
run: |
bin_dir="target/${{ matrix.target }}/dist"
"${bin_dir}/${{ matrix.cli_binary }}" --version
"${bin_dir}/${{ matrix.shim_binary }}" --version
- name: Stage binaries
shell: bash
run: |
stage_binary() {
local binary="$1"
local artifact="$2"
local bin_path="target/${{ matrix.target }}/dist/${binary}"
if [[ ! -f "${bin_path}" ]]; then
echo "Binary not at ${bin_path}; searching target/ for ${binary}:" >&2
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 }}"
# Compatibility bridge for v0.9.4's hard-coded release
# completeness/updater contract. This is the same runtime, not a
# separately compiled or installed TUI command.
stage_binary "${{ matrix.cli_binary }}" "${{ matrix.compat_tui_artifact }}"
- uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7
with:
name: ${{ matrix.cli_artifact }}
path: ${{ matrix.cli_artifact }}
if-no-files-found: error
retention-days: ${{ inputs.retention_days }}
overwrite: true
- uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7
with:
name: ${{ matrix.shim_artifact }}
path: ${{ matrix.shim_artifact }}
if-no-files-found: error
retention-days: ${{ inputs.retention_days }}
overwrite: true
- uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7
with:
name: ${{ matrix.compat_tui_artifact }}
path: ${{ matrix.compat_tui_artifact }}
if-no-files-found: error
retention-days: ${{ inputs.retention_days }}
overwrite: true
bundle:
timeout-minutes: 15
needs: build
if: ${{ !cancelled() && needs.build.result == 'success' }}
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7
- uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8
with:
path: artifacts
pattern: '*'
- name: Create and checksum platform archives
shell: bash
env:
SOURCE_SHA: ${{ github.sha }}
run: |
set -euo pipefail
source_date_epoch="$(git show -s --format=%ct "${SOURCE_SHA}")"
if [[ ! "${source_date_epoch}" =~ ^[0-9]+$ ]]; then
echo "Could not read a Unix timestamp for source commit ${SOURCE_SHA}" >&2
exit 1
fi
SOURCE_DATE_EPOCH="${source_date_epoch}" \
bash scripts/release/create-release-bundles.sh artifacts bundles
- uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7
with:
name: codewhale-bundles
path: |
bundles/*.tar.gz
bundles/*.zip
bundles/codewhale-bundles-sha256.txt
if-no-files-found: error
retention-days: ${{ inputs.retention_days }}
overwrite: true
windows-installer:
timeout-minutes: 15
needs: build
if: ${{ !cancelled() && needs.build.result == 'success' }}
runs-on: windows-latest
steps:
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7
- uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # 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"
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"
$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=${{ inputs.version }}" "codewhale.nsi"
Pop-Location
if (!(Test-Path "scripts\installer\CodeWhaleSetup.exe")) {
throw "CodeWhaleSetup.exe was not produced"
}
- uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7
with:
name: CodeWhaleSetup.exe
path: scripts/installer/CodeWhaleSetup.exe
if-no-files-found: error
retention-days: ${{ inputs.retention_days }}
overwrite: true
assemble:
timeout-minutes: 15
needs: [bundle, windows-installer]
if: ${{ !cancelled() && needs.bundle.result == 'success' && needs.windows-installer.result == 'success' }}
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7
- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
with:
node-version: 20
package-manager-cache: false
- uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8
with:
path: intermediate-artifacts
pattern: '*'
- name: Assemble exact authoritative release inventory
run: node scripts/release/assemble-release-assets.js intermediate-artifacts release-assets
- uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7
with:
name: codewhale-release-assets
path: release-assets/*
if-no-files-found: error
retention-days: ${{ inputs.retention_days }}
compression-level: 0
overwrite: true
smoke:
timeout-minutes: 15
needs: assemble
if: ${{ !cancelled() && needs.assemble.result == 'success' }}
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7
- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
with:
node-version: 20
package-manager-cache: false
- uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8
with:
name: codewhale-release-assets
path: release-assets
- name: Verify 34-asset bridge inventory and checksum manifests (single binary)
run: node scripts/release/assemble-release-assets.js --verify release-assets
- name: Test release inventory contracts
run: |
node --test scripts/release/assemble-release-assets.test.js
node --test npm/codewhale/test/artifacts.test.js npm/codewhale/test/release-assets.test.js
- name: Render AUR metadata from candidate Linux archives
run: bash packaging/aur/render.sh release-assets "${RUNNER_TEMP}/codewhale-bin"
- name: Smoke packed npm wrapper against candidate assets
env:
CODEWHALE_SMOKE_ASSETS_DIR: ${{ github.workspace }}/release-assets
run: node scripts/release/npm-wrapper-smoke.js
- name: Record non-public candidate identity
shell: bash
run: |
{
echo "### Release artifact candidate"
echo ""
echo "- Source: \`${{ github.sha }}\`"
echo "- Version metadata: \`${{ inputs.version }}\`"
echo "- Inventory: 7 targets / 34 files (single binary; 7 legacy alias assets)"
echo "- Publication: none (Actions artifact \`codewhale-release-assets\` only)"
} >> "${GITHUB_STEP_SUMMARY}"