mirror of
https://github.com/Hmbown/DeepSeek-TUI.git
synced 2026-09-03 06:50:13 +08:00
All 19 open actions/cache-poisoning/poisonable-step alerts (#88-#106) sit in release.yml, release-candidate.yml and release-artifacts.yml: jobs interpolated the caller SHA into ref/caches and let setup-node's implicit npm cache key on run identity. Now caller source_sha is pinned against github.sha by a pin job (refusing any retarget), checkout and build identity use GITHUB_SHA through env indirection instead of template interpolation, rust-cache keys use stable prefix-key values, and setup-node's implicit package cache is off where it cannot be keyed safely. Also carries the CodeQL #107 fix from the same lane: catalog_models_dev.py prints remote limit values as numbers/null/redacted only and drops query and fragment material from source URLs before logging. Harvested from #5401 (workflow+script files; CHANGELOG edit intentionally excluded here and lands with the release branch; GHSA advisory text split to a later PR as advised). No-Issue: CodeQL alert remediation (alerts #88-#107); no single user-facing issue tracks these Signed-off-by: Hunter Bown <hunter@hmbown.com>
414 lines
17 KiB
YAML
414 lines
17 KiB
YAML
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.
|
||
DEEPSEEK_BUILD_SHA: ${{ github.sha }}
|
||
|
||
jobs:
|
||
pin:
|
||
name: Pin caller SHA to this run
|
||
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 }}
|
||
# 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
|
||
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
|
||
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
|
||
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
|
||
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
|
||
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
|
||
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
|
||
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 }}"
|
||
# One-release compatibility bridge for v0.9.4's hard-coded release
|
||
# completeness/updater contract. This is the same runtime, not a
|
||
# separately compiled TUI binary.
|
||
stage_binary "${{ matrix.cli_binary }}" "${{ matrix.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.tui_artifact }}
|
||
path: ${{ matrix.tui_artifact }}
|
||
if-no-files-found: error
|
||
retention-days: ${{ inputs.retention_days }}
|
||
overwrite: true
|
||
|
||
bundle:
|
||
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:
|
||
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:
|
||
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:
|
||
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: 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}"
|