mirror of
https://github.com/NanmiCoder/claude-code-haha.git
synced 2026-10-10 11:53:10 +08:00
8d7b5ea56b
Add mobile provider and General settings while preserving desktop behavior. Harden remote credential handling, ngrok session ownership and consent upgrades.
946 lines
42 KiB
YAML
946 lines
42 KiB
YAML
name: Release Desktop
|
|
|
|
on:
|
|
push:
|
|
tags: ['v*.*.*']
|
|
workflow_dispatch:
|
|
inputs:
|
|
draft:
|
|
description: 'Create as draft release'
|
|
required: false
|
|
default: true
|
|
type: boolean
|
|
notarize_macos:
|
|
description: 'Notarize macOS artifacts'
|
|
required: false
|
|
default: true
|
|
type: boolean
|
|
skip_windows_signing:
|
|
description: 'Build unsigned Windows artifacts while SignPath onboarding is pending'
|
|
required: false
|
|
default: false
|
|
type: boolean
|
|
publish_draft_release:
|
|
description: 'Publish manual draft artifacts to GitHub Releases'
|
|
required: false
|
|
default: false
|
|
type: boolean
|
|
|
|
permissions:
|
|
actions: read
|
|
contents: write
|
|
|
|
concurrency:
|
|
group: release-desktop-${{ github.ref }}
|
|
cancel-in-progress: true
|
|
|
|
jobs:
|
|
signing-preflight:
|
|
runs-on: ubuntu-latest
|
|
outputs:
|
|
macos_signed: ${{ steps.validate.outputs.macos_signed }}
|
|
windows_signed: ${{ steps.validate.outputs.windows_signed }}
|
|
steps:
|
|
- name: Validate release signing and notarization secrets
|
|
id: validate
|
|
shell: bash
|
|
env:
|
|
CSC_LINK: ${{ secrets.MACOS_CERTIFICATE }}
|
|
CSC_KEY_PASSWORD: ${{ secrets.MACOS_CERTIFICATE_PASSWORD }}
|
|
APPLE_ID: ${{ secrets.APPLE_ID }}
|
|
APPLE_APP_SPECIFIC_PASSWORD: ${{ secrets.APPLE_APP_SPECIFIC_PASSWORD }}
|
|
APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }}
|
|
SIGNPATH_API_TOKEN: ${{ secrets.SIGNPATH_API_TOKEN }}
|
|
SIGNPATH_ORGANIZATION_ID: ${{ vars.SIGNPATH_ORGANIZATION_ID }}
|
|
SIGNPATH_PROJECT_SLUG: ${{ vars.SIGNPATH_PROJECT_SLUG }}
|
|
SIGNPATH_SIGNING_POLICY_SLUG: ${{ github.event_name == 'workflow_dispatch' && inputs.draft == true && vars.SIGNPATH_TEST_SIGNING_POLICY_SLUG || vars.SIGNPATH_RELEASE_SIGNING_POLICY_SLUG }}
|
|
SIGNPATH_APPLICATION_ARTIFACT_CONFIGURATION_SLUG: ${{ vars.SIGNPATH_APPLICATION_ARTIFACT_CONFIGURATION_SLUG }}
|
|
SIGNPATH_INSTALLER_ARTIFACT_CONFIGURATION_SLUG: ${{ vars.SIGNPATH_INSTALLER_ARTIFACT_CONFIGURATION_SLUG }}
|
|
RELEASE_DRAFT: ${{ github.event_name == 'workflow_dispatch' && inputs.draft == true }}
|
|
SKIP_WINDOWS_SIGNING: ${{ github.event_name == 'workflow_dispatch' && inputs.skip_windows_signing == true }}
|
|
run: |
|
|
# macOS signing + notarization is preferred: Squirrel.Mac auto-update and
|
|
# first-launch Gatekeeper approval and Computer Use client attestation
|
|
# require a consistent Developer ID build. Drafts use the same native
|
|
# runtime, so an unsigned macOS lane would be a knowingly broken app.
|
|
missing=()
|
|
[ -n "$CSC_LINK" ] || missing+=("MACOS_CERTIFICATE")
|
|
[ -n "$CSC_KEY_PASSWORD" ] || missing+=("MACOS_CERTIFICATE_PASSWORD")
|
|
[ -n "$APPLE_ID" ] || missing+=("APPLE_ID")
|
|
[ -n "$APPLE_APP_SPECIFIC_PASSWORD" ] || missing+=("APPLE_APP_SPECIFIC_PASSWORD")
|
|
[ -n "$APPLE_TEAM_ID" ] || missing+=("APPLE_TEAM_ID")
|
|
if [ "${#missing[@]}" -gt 0 ]; then
|
|
printf '::error::Missing macOS signing/notarization secrets (%s): refusing to build a macOS release whose Computer Use runtime cannot pass client attestation.\n' "${missing[*]}"
|
|
echo "macos_signed=false" >> "$GITHUB_OUTPUT"
|
|
exit 1
|
|
else
|
|
echo "macos_signed=true" >> "$GITHUB_OUTPUT"
|
|
fi
|
|
# A maintainer can explicitly release unsigned Windows builds while
|
|
# SignPath approval is pending, even when its configuration is present.
|
|
if [ "$SKIP_WINDOWS_SIGNING" = "true" ]; then
|
|
echo "::warning::Windows signing explicitly skipped for this manual release; Windows artifacts will be unsigned."
|
|
echo "windows_signed=false" >> "$GITHUB_OUTPUT"
|
|
exit 0
|
|
fi
|
|
# Drafts may remain unsigned while SignPath onboarding is being tested. Tags and
|
|
# non-draft releases otherwise require the full GitHub connector configuration.
|
|
win_missing=()
|
|
[ -n "$SIGNPATH_API_TOKEN" ] || win_missing+=("SIGNPATH_API_TOKEN secret")
|
|
[ -n "$SIGNPATH_ORGANIZATION_ID" ] || win_missing+=("SIGNPATH_ORGANIZATION_ID variable")
|
|
[ -n "$SIGNPATH_PROJECT_SLUG" ] || win_missing+=("SIGNPATH_PROJECT_SLUG variable")
|
|
[ -n "$SIGNPATH_SIGNING_POLICY_SLUG" ] || win_missing+=("selected SignPath signing policy variable")
|
|
[ -n "$SIGNPATH_APPLICATION_ARTIFACT_CONFIGURATION_SLUG" ] || win_missing+=("SIGNPATH_APPLICATION_ARTIFACT_CONFIGURATION_SLUG variable")
|
|
[ -n "$SIGNPATH_INSTALLER_ARTIFACT_CONFIGURATION_SLUG" ] || win_missing+=("SIGNPATH_INSTALLER_ARTIFACT_CONFIGURATION_SLUG variable")
|
|
if [ "${#win_missing[@]}" -gt 0 ]; then
|
|
printf '::warning::SignPath configuration missing (%s): the Windows build will be unsigned.\n' "${win_missing[*]}"
|
|
echo "windows_signed=false" >> "$GITHUB_OUTPUT"
|
|
if [ "$RELEASE_DRAFT" != "true" ]; then
|
|
echo "::error::Refusing to publish a non-draft desktop release without SignPath Windows signing."
|
|
exit 1
|
|
fi
|
|
else
|
|
echo "windows_signed=true" >> "$GITHUB_OUTPUT"
|
|
fi
|
|
|
|
build:
|
|
needs:
|
|
- signing-preflight
|
|
strategy:
|
|
fail-fast: false
|
|
matrix:
|
|
include:
|
|
- platform: macos-latest
|
|
target_triple: aarch64-apple-darwin
|
|
builder_args: --mac dmg zip --arm64
|
|
app_bundle_dir: mac-arm64
|
|
label: macOS-ARM64
|
|
smoke_platform: macos
|
|
arch: arm64
|
|
- platform: macos-latest
|
|
target_triple: x86_64-apple-darwin
|
|
builder_args: --mac dmg zip --x64
|
|
app_bundle_dir: mac
|
|
label: macOS-x64
|
|
smoke_platform: macos
|
|
arch: x64
|
|
- platform: ubuntu-22.04
|
|
target_triple: x86_64-unknown-linux-gnu
|
|
builder_args: --linux AppImage deb rpm --x64
|
|
label: Linux-x64
|
|
smoke_platform: linux
|
|
arch: x64
|
|
- platform: ubuntu-22.04-arm
|
|
target_triple: aarch64-unknown-linux-gnu
|
|
builder_args: --linux AppImage deb rpm --arm64
|
|
label: Linux-ARM64
|
|
smoke_platform: linux
|
|
arch: arm64
|
|
- platform: windows-latest
|
|
target_triple: x86_64-pc-windows-msvc
|
|
builder_args: --win nsis --x64
|
|
builder_arch_arg: --x64
|
|
unpacked_dir: win-unpacked
|
|
label: Windows-x64
|
|
smoke_platform: windows
|
|
arch: x64
|
|
- platform: windows-latest
|
|
target_triple: aarch64-pc-windows-msvc
|
|
builder_args: --win nsis --arm64
|
|
builder_arch_arg: --arm64
|
|
unpacked_dir: win-arm64-unpacked
|
|
label: Windows-ARM64
|
|
smoke_platform: windows
|
|
arch: arm64
|
|
|
|
runs-on: ${{ matrix.platform }}
|
|
name: Build (${{ matrix.label }})
|
|
env:
|
|
SIGNPATH_SIGNING_POLICY_SLUG: ${{ github.event_name == 'workflow_dispatch' && inputs.draft == true && vars.SIGNPATH_TEST_SIGNING_POLICY_SLUG || vars.SIGNPATH_RELEASE_SIGNING_POLICY_SLUG }}
|
|
|
|
steps:
|
|
- name: Checkout
|
|
uses: actions/checkout@v4
|
|
|
|
- name: Read version
|
|
id: version
|
|
shell: bash
|
|
run: |
|
|
VERSION=$(node -p "require('./desktop/package.json').version")
|
|
echo "value=$VERSION" >> "$GITHUB_OUTPUT"
|
|
|
|
- name: Validate tag matches version
|
|
if: github.event_name == 'push'
|
|
shell: bash
|
|
run: |
|
|
EXPECTED_TAG="v${{ steps.version.outputs.value }}"
|
|
if [ "${GITHUB_REF_NAME}" != "$EXPECTED_TAG" ]; then
|
|
echo "::error::Tag ${GITHUB_REF_NAME} does not match app version ${EXPECTED_TAG}"
|
|
exit 1
|
|
fi
|
|
|
|
- name: Install Linux dependencies
|
|
if: contains(matrix.platform, 'ubuntu')
|
|
run: |
|
|
sudo apt-get update
|
|
sudo apt-get install -y build-essential curl wget file libfuse2 rpm
|
|
|
|
- name: Setup Bun
|
|
uses: oven-sh/setup-bun@v2
|
|
with:
|
|
bun-version-file: package.json
|
|
|
|
- name: Setup Node.js
|
|
uses: actions/setup-node@v4
|
|
with:
|
|
node-version: 22
|
|
|
|
- name: Install root dependencies
|
|
run: bun install
|
|
|
|
- name: Install desktop dependencies
|
|
working-directory: desktop
|
|
# The x64 Windows runner also packages ARM64; retain both native addons.
|
|
run: bun install --cpu="*"
|
|
|
|
- name: Install adapter dependencies
|
|
working-directory: adapters
|
|
run: bun install
|
|
|
|
- name: Verify Windows legacy data recovery
|
|
if: matrix.smoke_platform == 'windows'
|
|
working-directory: desktop
|
|
run: bun run test:windows-storage-recovery
|
|
|
|
- name: Import macOS signing identity for native runtimes
|
|
if: matrix.smoke_platform == 'macos' && needs.signing-preflight.outputs.macos_signed == 'true'
|
|
shell: bash
|
|
env:
|
|
CSC_LINK: ${{ secrets.MACOS_CERTIFICATE }}
|
|
CSC_KEY_PASSWORD: ${{ secrets.MACOS_CERTIFICATE_PASSWORD }}
|
|
run: |
|
|
set -euo pipefail
|
|
certificate_path="${RUNNER_TEMP}/cc-haha-signing.p12"
|
|
keychain_path="${RUNNER_TEMP}/cc-haha-signing.keychain-db"
|
|
keychain_password="$(uuidgen)"
|
|
printf '%s' "$CSC_LINK" | base64 --decode > "$certificate_path"
|
|
security create-keychain -p "$keychain_password" "$keychain_path"
|
|
security set-keychain-settings -lut 21600 "$keychain_path"
|
|
security unlock-keychain -p "$keychain_password" "$keychain_path"
|
|
security import "$certificate_path" \
|
|
-k "$keychain_path" \
|
|
-P "$CSC_KEY_PASSWORD" \
|
|
-A \
|
|
-t cert \
|
|
-f pkcs12
|
|
security set-key-partition-list \
|
|
-S apple-tool:,apple:,codesign: \
|
|
-s \
|
|
-k "$keychain_password" \
|
|
"$keychain_path"
|
|
security list-keychains -d user -s "$keychain_path"
|
|
identity="$(
|
|
security find-identity -v -p codesigning "$keychain_path" \
|
|
| grep -E '"Developer ID Application:' \
|
|
| head -1 \
|
|
| sed -E 's/^[^"]*"([^"]+)".*$/\1/'
|
|
)"
|
|
if [ -z "$identity" ]; then
|
|
echo "::error::Imported certificate does not contain a Developer ID Application identity."
|
|
exit 1
|
|
fi
|
|
echo "CC_HAHA_SIGN_IDENTITY=$identity" >> "$GITHUB_ENV"
|
|
echo "CC_HAHA_CI_KEYCHAIN=$keychain_path" >> "$GITHUB_ENV"
|
|
echo "CC_HAHA_CI_CERTIFICATE=$certificate_path" >> "$GITHUB_ENV"
|
|
echo "Imported native-runtime signing identity: $identity"
|
|
|
|
- name: Prepare bundled ripgrep
|
|
working-directory: desktop
|
|
env:
|
|
SIDECAR_TARGET_TRIPLE: ${{ matrix.target_triple }}
|
|
run: bun run prepare:ripgrep
|
|
|
|
- name: Build sidecars
|
|
working-directory: desktop
|
|
env:
|
|
BUN_INSTALL_CACHE_DIR: ${{ runner.temp }}/bun-install-cache
|
|
SIDECAR_TARGET_TRIPLE: ${{ matrix.target_triple }}
|
|
run: bun run build:sidecars
|
|
|
|
- name: Verify compiled Windows sidecar startup
|
|
if: matrix.smoke_platform == 'windows' && matrix.arch == 'x64'
|
|
working-directory: desktop
|
|
env:
|
|
CC_HAHA_COMPILED_SIDECAR_SMOKE_STARTS: '20'
|
|
run: bun run test:compiled-sidecar-smoke
|
|
|
|
- name: Build renderer and Electron bundles
|
|
working-directory: desktop
|
|
run: |
|
|
bun run build
|
|
bun run build:electron
|
|
|
|
- name: Build signed macOS Electron release artifacts
|
|
if: matrix.smoke_platform == 'macos' && needs.signing-preflight.outputs.macos_signed == 'true'
|
|
working-directory: desktop
|
|
timeout-minutes: 80
|
|
env:
|
|
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
DEBUG: 'electron-builder,electron-osx-sign,electron-notarize*'
|
|
# Reuse the keychain prepared for native runtimes. Passing CSC_LINK
|
|
# would make electron-builder import the same certificate again.
|
|
APPLE_ID: ${{ secrets.APPLE_ID }}
|
|
APPLE_APP_SPECIFIC_PASSWORD: ${{ secrets.APPLE_APP_SPECIFIC_PASSWORD }}
|
|
APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }}
|
|
MACOS_NOTARIZE: ${{ github.event_name != 'workflow_dispatch' || inputs.notarize_macos }}
|
|
run: |
|
|
set -euo pipefail
|
|
export CSC_KEYCHAIN="${CC_HAHA_CI_KEYCHAIN:?macOS signing keychain was not prepared}"
|
|
echo "::group::macOS signing diagnostics"
|
|
echo "UTC start: $(date -u '+%Y-%m-%dT%H:%M:%SZ')"
|
|
sw_vers
|
|
xcodebuild -version
|
|
xcrun --find notarytool
|
|
xcrun notarytool --version || true
|
|
security find-identity -v -p codesigning || true
|
|
echo "macOS notarization requested: ${MACOS_NOTARIZE}"
|
|
echo "::endgroup::"
|
|
if [ "$MACOS_NOTARIZE" = "true" ]; then
|
|
max_attempts=1
|
|
build_timeout_seconds=900
|
|
builder_args=( ${{ matrix.builder_args }} --publish never -c.mac.notarize=false )
|
|
else
|
|
max_attempts=1
|
|
build_timeout_seconds=900
|
|
builder_args=( ${{ matrix.builder_args }} --publish never -c.mac.notarize=false )
|
|
echo "::warning::macOS notarization is disabled for this draft run; artifacts will be Developer ID signed but not notarized."
|
|
fi
|
|
|
|
run_signed_electron_builder() {
|
|
local timeout_seconds="$1"
|
|
shift
|
|
"$@" &
|
|
local build_pid=$!
|
|
local start_epoch
|
|
start_epoch=$(date +%s)
|
|
|
|
while true; do
|
|
if ! jobs -pr | grep -q "^${build_pid}$"; then
|
|
break
|
|
fi
|
|
|
|
local now_epoch elapsed_seconds
|
|
now_epoch=$(date +%s)
|
|
elapsed_seconds=$((now_epoch - start_epoch))
|
|
if [ "$elapsed_seconds" -ge "$timeout_seconds" ]; then
|
|
echo "::warning::Signed electron-builder timed out after ${elapsed_seconds}s; terminating process ${build_pid}"
|
|
pkill -TERM -P "$build_pid" 2>/dev/null || true
|
|
kill -TERM "$build_pid" 2>/dev/null || true
|
|
sleep 10
|
|
pkill -KILL -P "$build_pid" 2>/dev/null || true
|
|
kill -KILL "$build_pid" 2>/dev/null || true
|
|
wait "$build_pid" 2>/dev/null || true
|
|
return 124
|
|
fi
|
|
|
|
sleep 15
|
|
done
|
|
|
|
wait "$build_pid"
|
|
}
|
|
|
|
run_electron_builder_with_retries() {
|
|
local attempts="$1"
|
|
local timeout_seconds="$2"
|
|
local clean_before_attempt="$3"
|
|
shift 3
|
|
local status=0
|
|
|
|
for attempt in $(seq 1 "$attempts"); do
|
|
echo "Starting signed electron-builder attempt ${attempt}/${attempts} at $(date -u '+%Y-%m-%dT%H:%M:%SZ') with ${timeout_seconds}s watchdog"
|
|
if [ "$clean_before_attempt" = "true" ]; then
|
|
rm -rf build-artifacts/electron
|
|
fi
|
|
set +e
|
|
run_signed_electron_builder "$timeout_seconds" "$@"
|
|
status=$?
|
|
set -e
|
|
if [ "$status" -eq 0 ]; then
|
|
echo "Finished signed electron-builder attempt ${attempt}/${attempts} at $(date -u '+%Y-%m-%dT%H:%M:%SZ')"
|
|
return 0
|
|
fi
|
|
|
|
if [ "$attempt" -eq "$attempts" ]; then
|
|
echo "::error::Signed electron-builder failed after ${attempts} attempts"
|
|
return "$status"
|
|
fi
|
|
echo "::warning::Signed electron-builder attempt ${attempt}/${attempts} failed with exit code ${status}; retrying after 120 seconds"
|
|
sleep 120
|
|
done
|
|
}
|
|
|
|
notarize_app_bundle() {
|
|
local app_path="$1"
|
|
local notary_zip="${RUNNER_TEMP}/${{ matrix.label }}-notary.zip"
|
|
local notary_attempts=3
|
|
local notary_timeout=20m
|
|
local status=0
|
|
|
|
if [ ! -d "$app_path" ]; then
|
|
echo "::error::Expected signed macOS app bundle does not exist: ${app_path}"
|
|
return 1
|
|
fi
|
|
|
|
echo "::group::macOS notarization"
|
|
echo "Verifying signed app before notarization: ${app_path}"
|
|
codesign --verify --deep --strict --verbose=2 "$app_path"
|
|
rm -f "$notary_zip"
|
|
(
|
|
cd "$(dirname "$app_path")"
|
|
ditto -c -k --sequesterRsrc --keepParent "$(basename "$app_path")" "$notary_zip"
|
|
)
|
|
|
|
for attempt in $(seq 1 "$notary_attempts"); do
|
|
echo "Starting notarytool attempt ${attempt}/${notary_attempts} at $(date -u '+%Y-%m-%dT%H:%M:%SZ') with ${notary_timeout} timeout"
|
|
set +e
|
|
xcrun notarytool submit "$notary_zip" \
|
|
--apple-id "$APPLE_ID" \
|
|
--password "$APPLE_APP_SPECIFIC_PASSWORD" \
|
|
--team-id "$APPLE_TEAM_ID" \
|
|
--wait \
|
|
--timeout "$notary_timeout" \
|
|
--output-format json
|
|
status=$?
|
|
set -e
|
|
if [ "$status" -eq 0 ]; then
|
|
echo "Notarytool attempt ${attempt}/${notary_attempts} succeeded at $(date -u '+%Y-%m-%dT%H:%M:%SZ')"
|
|
xcrun stapler staple "$app_path"
|
|
xcrun stapler validate "$app_path"
|
|
spctl -a -vv -t execute "$app_path"
|
|
echo "::endgroup::"
|
|
return 0
|
|
fi
|
|
|
|
if [ "$attempt" -eq "$notary_attempts" ]; then
|
|
echo "::endgroup::"
|
|
echo "::error::notarytool failed after ${notary_attempts} attempts"
|
|
return "$status"
|
|
fi
|
|
echo "::warning::notarytool attempt ${attempt}/${notary_attempts} failed with exit code ${status}; retrying after 120 seconds"
|
|
sleep 120
|
|
done
|
|
}
|
|
|
|
set +e
|
|
run_electron_builder_with_retries "$max_attempts" "$build_timeout_seconds" true node ./node_modules/electron-builder/out/cli/cli.js "${builder_args[@]}"
|
|
status=$?
|
|
set -e
|
|
if [ "$status" -ne 0 ]; then
|
|
exit "$status"
|
|
fi
|
|
|
|
if [ "$MACOS_NOTARIZE" != "true" ]; then
|
|
exit 0
|
|
fi
|
|
|
|
app_path="build-artifacts/electron/${{ matrix.app_bundle_dir }}/Claude Code Haha.app"
|
|
set +e
|
|
notarize_app_bundle "$app_path"
|
|
status=$?
|
|
set -e
|
|
if [ "$status" -ne 0 ]; then
|
|
exit "$status"
|
|
fi
|
|
|
|
package_args=( ${{ matrix.builder_args }} --prepackaged "$app_path" --publish never -c.mac.notarize=false )
|
|
find build-artifacts/electron -maxdepth 1 -type f -delete
|
|
set +e
|
|
run_electron_builder_with_retries 1 900 false node ./node_modules/electron-builder/out/cli/cli.js "${package_args[@]}"
|
|
status=$?
|
|
set -e
|
|
if [ "$status" -ne 0 ]; then
|
|
exit "$status"
|
|
fi
|
|
|
|
- name: Build unsigned Electron release artifacts
|
|
if: matrix.smoke_platform == 'linux' || (matrix.smoke_platform == 'macos' && needs.signing-preflight.outputs.macos_signed != 'true') || (matrix.smoke_platform == 'windows' && needs.signing-preflight.outputs.windows_signed != 'true')
|
|
working-directory: desktop
|
|
env:
|
|
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
# Unsigned fallback: do not pass CSC_LINK / APPLE_* / WIN_CSC_* here.
|
|
# Empty secrets are rendered as empty strings, and electron-builder
|
|
# treats an empty CSC_LINK key as an explicit certificate path.
|
|
CSC_IDENTITY_AUTO_DISCOVERY: 'false'
|
|
run: node ./node_modules/electron-builder/out/cli/cli.js ${{ matrix.builder_args }} --publish never
|
|
|
|
# A real NSIS target is intentional here. electron-builder writes
|
|
# resources/app-update.yml only while packaging an updater-capable target;
|
|
# `--win dir` and the later `--prepackaged` pass both skip that hook.
|
|
- name: Build unsigned Windows bootstrap installer for SignPath
|
|
if: matrix.smoke_platform == 'windows' && needs.signing-preflight.outputs.windows_signed == 'true'
|
|
working-directory: desktop
|
|
env:
|
|
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
CSC_IDENTITY_AUTO_DISCOVERY: 'false'
|
|
run: node ./node_modules/electron-builder/out/cli/cli.js --win nsis ${{ matrix.builder_arch_arg }} --publish never
|
|
|
|
- name: Verify Windows updater config before SignPath
|
|
if: matrix.smoke_platform == 'windows' && needs.signing-preflight.outputs.windows_signed == 'true'
|
|
shell: pwsh
|
|
run: |
|
|
$updaterConfig = Join-Path $PWD "desktop/build-artifacts/electron/${{ matrix.unpacked_dir }}/resources/app-update.yml"
|
|
if (-not (Test-Path -LiteralPath $updaterConfig -PathType Leaf)) {
|
|
throw "electron-builder did not create the Windows updater config: $updaterConfig"
|
|
}
|
|
|
|
- name: Stage project-owned Windows application executables
|
|
if: matrix.smoke_platform == 'windows' && needs.signing-preflight.outputs.windows_signed == 'true'
|
|
id: stage-signpath-application
|
|
shell: pwsh
|
|
run: |
|
|
$unpackedDir = Join-Path $PWD "desktop/build-artifacts/electron/${{ matrix.unpacked_dir }}"
|
|
$stageDir = Join-Path $env:RUNNER_TEMP "signpath-application-${{ matrix.arch }}"
|
|
$sidecarName = "claude-sidecar-${{ matrix.target_triple }}.exe"
|
|
$mainExecutable = Join-Path $unpackedDir "Claude Code Haha.exe"
|
|
$sidecarExecutable = Join-Path $unpackedDir "resources/app.asar.unpacked/src-tauri/binaries/$sidecarName"
|
|
New-Item -ItemType Directory -Path $stageDir | Out-Null
|
|
Copy-Item -LiteralPath $mainExecutable -Destination (Join-Path $stageDir "Claude Code Haha.exe")
|
|
Copy-Item -LiteralPath $sidecarExecutable -Destination (Join-Path $stageDir $sidecarName)
|
|
"stage_dir=$stageDir" >> $env:GITHUB_OUTPUT
|
|
"unpacked_dir=$unpackedDir" >> $env:GITHUB_OUTPUT
|
|
"sidecar_name=$sidecarName" >> $env:GITHUB_OUTPUT
|
|
|
|
- name: Upload unsigned Windows application executables
|
|
if: matrix.smoke_platform == 'windows' && needs.signing-preflight.outputs.windows_signed == 'true'
|
|
id: upload-unsigned-signpath-application
|
|
uses: actions/upload-artifact@v4
|
|
with:
|
|
name: signpath-unsigned-application-${{ matrix.arch }}
|
|
path: ${{ steps.stage-signpath-application.outputs.stage_dir }}
|
|
if-no-files-found: error
|
|
|
|
- name: Sign Windows application executables with SignPath
|
|
if: matrix.smoke_platform == 'windows' && needs.signing-preflight.outputs.windows_signed == 'true'
|
|
uses: signpath/github-action-submit-signing-request@v2
|
|
with:
|
|
api-token: ${{ secrets.SIGNPATH_API_TOKEN }}
|
|
organization-id: ${{ vars.SIGNPATH_ORGANIZATION_ID }}
|
|
project-slug: ${{ vars.SIGNPATH_PROJECT_SLUG }}
|
|
signing-policy-slug: ${{ env.SIGNPATH_SIGNING_POLICY_SLUG }}
|
|
artifact-configuration-slug: ${{ vars.SIGNPATH_APPLICATION_ARTIFACT_CONFIGURATION_SLUG }}
|
|
github-artifact-id: ${{ steps.upload-unsigned-signpath-application.outputs.artifact-id }}
|
|
wait-for-completion: true
|
|
wait-for-completion-timeout-in-seconds: '3600'
|
|
output-artifact-directory: ${{ runner.temp }}/signpath-signed-application-${{ matrix.arch }}
|
|
|
|
- name: Restore and verify signed Windows application executables
|
|
if: matrix.smoke_platform == 'windows' && needs.signing-preflight.outputs.windows_signed == 'true'
|
|
shell: pwsh
|
|
env:
|
|
REQUIRE_TRUSTED_WINDOWS_SIGNATURE: ${{ github.event_name != 'workflow_dispatch' || inputs.draft == false }}
|
|
run: |
|
|
$signedDir = Join-Path $env:RUNNER_TEMP "signpath-signed-application-${{ matrix.arch }}"
|
|
$unpackedDir = "${{ steps.stage-signpath-application.outputs.unpacked_dir }}"
|
|
$sidecarName = "${{ steps.stage-signpath-application.outputs.sidecar_name }}"
|
|
$mainExecutable = Join-Path $unpackedDir "Claude Code Haha.exe"
|
|
$sidecarExecutable = Join-Path $unpackedDir "resources/app.asar.unpacked/src-tauri/binaries/$sidecarName"
|
|
Copy-Item -LiteralPath (Join-Path $signedDir "Claude Code Haha.exe") -Destination $mainExecutable -Force
|
|
Copy-Item -LiteralPath (Join-Path $signedDir $sidecarName) -Destination $sidecarExecutable -Force
|
|
|
|
function Assert-SignPathSignature([string] $Path) {
|
|
$signature = Get-AuthenticodeSignature -LiteralPath $Path
|
|
if ($null -eq $signature.SignerCertificate) {
|
|
throw "SignPath did not add an Authenticode signature to $Path"
|
|
}
|
|
if ($signature.Status -notin @('Valid', 'UnknownError')) {
|
|
throw "Authenticode verification failed for $Path with status $($signature.Status): $($signature.StatusMessage)"
|
|
}
|
|
if ($env:REQUIRE_TRUSTED_WINDOWS_SIGNATURE -eq 'true' -and $signature.Status -ne 'Valid') {
|
|
throw "A trusted production signature is required for $Path, but the status is $($signature.Status): $($signature.StatusMessage)"
|
|
}
|
|
Write-Host "Verified Authenticode signature on $Path from $($signature.SignerCertificate.Subject)"
|
|
}
|
|
|
|
Assert-SignPathSignature $mainExecutable
|
|
Assert-SignPathSignature $sidecarExecutable
|
|
|
|
- name: Package NSIS installer from signed Windows application
|
|
if: matrix.smoke_platform == 'windows' && needs.signing-preflight.outputs.windows_signed == 'true'
|
|
working-directory: desktop
|
|
env:
|
|
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
CSC_IDENTITY_AUTO_DISCOVERY: 'false'
|
|
run: node ./node_modules/electron-builder/out/cli/cli.js --win nsis ${{ matrix.builder_arch_arg }} --prepackaged "build-artifacts/electron/${{ matrix.unpacked_dir }}" --publish never
|
|
|
|
- name: Stage unsigned Windows installer
|
|
if: matrix.smoke_platform == 'windows' && needs.signing-preflight.outputs.windows_signed == 'true'
|
|
id: stage-signpath-installer
|
|
shell: pwsh
|
|
run: |
|
|
$installerName = "Claude-Code-Haha-${{ steps.version.outputs.value }}-win-${{ matrix.arch }}.exe"
|
|
$installerPath = Join-Path $PWD "desktop/build-artifacts/electron/$installerName"
|
|
$stageDir = Join-Path $env:RUNNER_TEMP "signpath-installer-${{ matrix.arch }}"
|
|
New-Item -ItemType Directory -Path $stageDir | Out-Null
|
|
Copy-Item -LiteralPath $installerPath -Destination (Join-Path $stageDir $installerName)
|
|
"stage_dir=$stageDir" >> $env:GITHUB_OUTPUT
|
|
"installer_name=$installerName" >> $env:GITHUB_OUTPUT
|
|
"installer_path=$installerPath" >> $env:GITHUB_OUTPUT
|
|
|
|
- name: Upload unsigned Windows installer
|
|
if: matrix.smoke_platform == 'windows' && needs.signing-preflight.outputs.windows_signed == 'true'
|
|
id: upload-unsigned-signpath-installer
|
|
uses: actions/upload-artifact@v4
|
|
with:
|
|
name: signpath-unsigned-installer-${{ matrix.arch }}
|
|
path: ${{ steps.stage-signpath-installer.outputs.stage_dir }}
|
|
if-no-files-found: error
|
|
|
|
- name: Sign Windows installer with SignPath
|
|
if: matrix.smoke_platform == 'windows' && needs.signing-preflight.outputs.windows_signed == 'true'
|
|
uses: signpath/github-action-submit-signing-request@v2
|
|
with:
|
|
api-token: ${{ secrets.SIGNPATH_API_TOKEN }}
|
|
organization-id: ${{ vars.SIGNPATH_ORGANIZATION_ID }}
|
|
project-slug: ${{ vars.SIGNPATH_PROJECT_SLUG }}
|
|
signing-policy-slug: ${{ env.SIGNPATH_SIGNING_POLICY_SLUG }}
|
|
artifact-configuration-slug: ${{ vars.SIGNPATH_INSTALLER_ARTIFACT_CONFIGURATION_SLUG }}
|
|
github-artifact-id: ${{ steps.upload-unsigned-signpath-installer.outputs.artifact-id }}
|
|
wait-for-completion: true
|
|
wait-for-completion-timeout-in-seconds: '3600'
|
|
output-artifact-directory: ${{ runner.temp }}/signpath-signed-installer-${{ matrix.arch }}
|
|
|
|
- name: Restore and verify signed Windows installer
|
|
if: matrix.smoke_platform == 'windows' && needs.signing-preflight.outputs.windows_signed == 'true'
|
|
shell: pwsh
|
|
env:
|
|
REQUIRE_TRUSTED_WINDOWS_SIGNATURE: ${{ github.event_name != 'workflow_dispatch' || inputs.draft == false }}
|
|
run: |
|
|
$signedInstaller = Join-Path $env:RUNNER_TEMP "signpath-signed-installer-${{ matrix.arch }}/${{ steps.stage-signpath-installer.outputs.installer_name }}"
|
|
$installerPath = "${{ steps.stage-signpath-installer.outputs.installer_path }}"
|
|
Copy-Item -LiteralPath $signedInstaller -Destination $installerPath -Force
|
|
$signature = Get-AuthenticodeSignature -LiteralPath $installerPath
|
|
if ($null -eq $signature.SignerCertificate) {
|
|
throw "SignPath did not add an Authenticode signature to $installerPath"
|
|
}
|
|
if ($signature.Status -notin @('Valid', 'UnknownError')) {
|
|
throw "Authenticode verification failed for $installerPath with status $($signature.Status): $($signature.StatusMessage)"
|
|
}
|
|
if ($env:REQUIRE_TRUSTED_WINDOWS_SIGNATURE -eq 'true' -and $signature.Status -ne 'Valid') {
|
|
throw "A trusted production signature is required for $installerPath, but the status is $($signature.Status): $($signature.StatusMessage)"
|
|
}
|
|
Write-Host "Verified Authenticode signature on $installerPath from $($signature.SignerCertificate.Subject)"
|
|
|
|
- name: Refresh signed Windows blockmap and update metadata
|
|
if: matrix.smoke_platform == 'windows' && needs.signing-preflight.outputs.windows_signed == 'true'
|
|
run: bun run scripts/refresh-windows-update-metadata.ts --installer "${{ steps.stage-signpath-installer.outputs.installer_path }}" --metadata desktop/build-artifacts/electron/latest.yml
|
|
|
|
- name: Verify Windows installer execution
|
|
if: matrix.smoke_platform == 'windows' && matrix.arch == 'x64'
|
|
timeout-minutes: 10
|
|
working-directory: desktop
|
|
run: powershell -NoLogo -NoProfile -ExecutionPolicy Bypass -File ./scripts/windows-installer-smoke.ps1 -ArtifactsDir ./build-artifacts/electron -Arch x64
|
|
|
|
- name: Verify packaged app structure
|
|
run: bun run test:package-smoke --platform ${{ matrix.smoke_platform }} --arch ${{ matrix.arch }} --package-kind release --artifacts-dir desktop/build-artifacts/electron
|
|
|
|
- name: Verify macOS launch policy
|
|
if: matrix.smoke_platform == 'macos' && needs.signing-preflight.outputs.macos_signed == 'true' && (github.event_name != 'workflow_dispatch' || inputs.notarize_macos == true)
|
|
run: bun run test:package-smoke --platform macos --arch ${{ matrix.arch }} --package-kind release --artifacts-dir desktop/build-artifacts/electron --require-macos-gatekeeper
|
|
|
|
- name: Warn macOS notarization skipped
|
|
if: matrix.smoke_platform == 'macos' && needs.signing-preflight.outputs.macos_signed == 'true' && github.event_name == 'workflow_dispatch' && inputs.notarize_macos == false
|
|
run: echo "::warning::Skipping macOS Gatekeeper package-smoke because notarization is disabled for this draft. Artifacts are Developer ID signed but not notarized."
|
|
|
|
- name: Warn unsigned macOS launch policy skipped
|
|
if: matrix.smoke_platform == 'macos' && needs.signing-preflight.outputs.macos_signed != 'true'
|
|
run: echo "::warning::Skipping macOS Gatekeeper package-smoke because this release is unsigned. Ship install-macos-unsigned.sh with the DMG."
|
|
|
|
- name: Validate matrix release asset set
|
|
shell: bash
|
|
working-directory: desktop/build-artifacts/electron
|
|
env:
|
|
APP_VERSION: ${{ steps.version.outputs.value }}
|
|
run: |
|
|
case "${{ matrix.label }}" in
|
|
macOS-ARM64)
|
|
expected=(
|
|
"Claude-Code-Haha-${APP_VERSION}-mac-arm64.dmg"
|
|
"Claude-Code-Haha-${APP_VERSION}-mac-arm64.dmg.blockmap"
|
|
"Claude-Code-Haha-${APP_VERSION}-mac-arm64.zip"
|
|
"Claude-Code-Haha-${APP_VERSION}-mac-arm64.zip.blockmap"
|
|
"latest-mac.yml"
|
|
)
|
|
;;
|
|
macOS-x64)
|
|
expected=(
|
|
"Claude-Code-Haha-${APP_VERSION}-mac-x64.dmg"
|
|
"Claude-Code-Haha-${APP_VERSION}-mac-x64.dmg.blockmap"
|
|
"Claude-Code-Haha-${APP_VERSION}-mac-x64.zip"
|
|
"Claude-Code-Haha-${APP_VERSION}-mac-x64.zip.blockmap"
|
|
"latest-mac.yml"
|
|
)
|
|
;;
|
|
Linux-x64)
|
|
expected=(
|
|
"Claude-Code-Haha-${APP_VERSION}-linux-x86_64.AppImage"
|
|
"Claude-Code-Haha-${APP_VERSION}-linux-amd64.deb"
|
|
"Claude-Code-Haha-${APP_VERSION}-linux-x86_64.rpm"
|
|
"latest-linux.yml"
|
|
)
|
|
;;
|
|
Linux-ARM64)
|
|
expected=(
|
|
"Claude-Code-Haha-${APP_VERSION}-linux-arm64.AppImage"
|
|
"Claude-Code-Haha-${APP_VERSION}-linux-arm64.deb"
|
|
"Claude-Code-Haha-${APP_VERSION}-linux-aarch64.rpm"
|
|
"latest-linux-arm64.yml"
|
|
)
|
|
;;
|
|
Windows-x64)
|
|
expected=(
|
|
"Claude-Code-Haha-${APP_VERSION}-win-x64.exe"
|
|
"Claude-Code-Haha-${APP_VERSION}-win-x64.exe.blockmap"
|
|
"latest.yml"
|
|
)
|
|
;;
|
|
Windows-ARM64)
|
|
expected=(
|
|
"Claude-Code-Haha-${APP_VERSION}-win-arm64.exe"
|
|
"Claude-Code-Haha-${APP_VERSION}-win-arm64.exe.blockmap"
|
|
"latest.yml"
|
|
)
|
|
;;
|
|
*)
|
|
echo "::error::No expected asset list for matrix label ${{ matrix.label }}"
|
|
exit 1
|
|
;;
|
|
esac
|
|
|
|
missing=()
|
|
for file in "${expected[@]}"; do
|
|
[ -f "$file" ] || missing+=("$file")
|
|
done
|
|
if [ "${#missing[@]}" -gt 0 ]; then
|
|
printf '::error::Missing release assets for %s: %s\n' "${{ matrix.label }}" "${missing[*]}"
|
|
exit 1
|
|
fi
|
|
|
|
- name: Remove temporary macOS signing keychain
|
|
if: always() && matrix.smoke_platform == 'macos' && needs.signing-preflight.outputs.macos_signed == 'true'
|
|
shell: bash
|
|
run: |
|
|
if [ -n "${CC_HAHA_CI_KEYCHAIN:-}" ]; then
|
|
security delete-keychain "$CC_HAHA_CI_KEYCHAIN" 2>/dev/null || true
|
|
fi
|
|
if [ -n "${CC_HAHA_CI_CERTIFICATE:-}" ]; then
|
|
rm -f "$CC_HAHA_CI_CERTIFICATE"
|
|
fi
|
|
|
|
- name: Namespace update metadata assets
|
|
shell: bash
|
|
working-directory: desktop/build-artifacts/electron
|
|
run: |
|
|
shopt -s nullglob
|
|
for file in latest*.yml; do
|
|
mv "$file" "${file%.yml}-${{ matrix.label }}.yml"
|
|
done
|
|
|
|
- name: Upload update metadata for merge
|
|
uses: actions/upload-artifact@v4
|
|
with:
|
|
name: desktop-update-metadata-${{ matrix.label }}
|
|
path: desktop/build-artifacts/electron/latest*.yml
|
|
if-no-files-found: ignore
|
|
|
|
- name: Upload release artifacts for final publish
|
|
uses: actions/upload-artifact@v4
|
|
with:
|
|
name: desktop-release-artifacts-${{ matrix.label }}
|
|
path: |
|
|
desktop/build-artifacts/electron/*.dmg
|
|
desktop/build-artifacts/electron/*.zip
|
|
desktop/build-artifacts/electron/*.exe
|
|
desktop/build-artifacts/electron/*.AppImage
|
|
desktop/build-artifacts/electron/*.deb
|
|
desktop/build-artifacts/electron/*.rpm
|
|
desktop/build-artifacts/electron/*.blockmap
|
|
desktop/build-artifacts/electron/*.yml
|
|
if-no-files-found: error
|
|
|
|
publish-release:
|
|
if: github.event_name == 'push' || inputs.draft == false || inputs.publish_draft_release == true
|
|
needs: build
|
|
runs-on: ubuntu-latest
|
|
|
|
steps:
|
|
- name: Checkout
|
|
uses: actions/checkout@v4
|
|
|
|
- name: Read version
|
|
id: version
|
|
shell: bash
|
|
run: |
|
|
VERSION=$(node -p "require('./desktop/package.json').version")
|
|
echo "value=$VERSION" >> "$GITHUB_OUTPUT"
|
|
|
|
- name: Validate tag matches version
|
|
if: github.event_name == 'push'
|
|
shell: bash
|
|
run: |
|
|
EXPECTED_TAG="v${{ steps.version.outputs.value }}"
|
|
if [ "${GITHUB_REF_NAME}" != "$EXPECTED_TAG" ]; then
|
|
echo "::error::Tag ${GITHUB_REF_NAME} does not match app version ${EXPECTED_TAG}"
|
|
exit 1
|
|
fi
|
|
|
|
- name: Refuse to overwrite an existing published release
|
|
if: github.event_name == 'workflow_dispatch' && inputs.draft == true
|
|
shell: bash
|
|
env:
|
|
GH_TOKEN: ${{ github.token }}
|
|
run: |
|
|
error_file="${RUNNER_TEMP}/release-view-error.txt"
|
|
set +e
|
|
release_state=$(gh api "repos/${GITHUB_REPOSITORY}/releases/tags/v${{ steps.version.outputs.value }}" --jq '.draft' 2>"$error_file")
|
|
status=$?
|
|
set -e
|
|
|
|
if [ "$status" -ne 0 ]; then
|
|
if grep -q 'HTTP 404' "$error_file"; then
|
|
exit 0
|
|
fi
|
|
cat "$error_file" >&2
|
|
exit "$status"
|
|
fi
|
|
|
|
if [ "$release_state" = "false" ]; then
|
|
echo "::error::Refusing to overwrite published release v${{ steps.version.outputs.value }} with manual draft artifacts."
|
|
exit 1
|
|
fi
|
|
|
|
- name: Load release notes
|
|
id: release_notes
|
|
shell: bash
|
|
run: |
|
|
NOTES_FILE="release-notes/v${{ steps.version.outputs.value }}.md"
|
|
if [ ! -f "$NOTES_FILE" ]; then
|
|
echo "::error::Missing release notes file: $NOTES_FILE"
|
|
exit 1
|
|
fi
|
|
{
|
|
echo 'body<<__RELEASE_NOTES__'
|
|
cat "$NOTES_FILE"
|
|
echo
|
|
echo '__RELEASE_NOTES__'
|
|
} >> "$GITHUB_OUTPUT"
|
|
|
|
- name: Setup Bun
|
|
uses: oven-sh/setup-bun@v2
|
|
with:
|
|
bun-version-file: package.json
|
|
|
|
- name: Install root dependencies
|
|
run: bun install
|
|
|
|
- name: Download release artifacts
|
|
uses: actions/download-artifact@v4
|
|
with:
|
|
pattern: desktop-release-artifacts-*
|
|
path: artifacts/release-assets
|
|
merge-multiple: true
|
|
|
|
- name: Validate complete release asset set
|
|
shell: bash
|
|
env:
|
|
APP_VERSION: ${{ steps.version.outputs.value }}
|
|
run: |
|
|
expected=(
|
|
"Claude-Code-Haha-${APP_VERSION}-mac-arm64.dmg"
|
|
"Claude-Code-Haha-${APP_VERSION}-mac-arm64.dmg.blockmap"
|
|
"Claude-Code-Haha-${APP_VERSION}-mac-arm64.zip"
|
|
"Claude-Code-Haha-${APP_VERSION}-mac-arm64.zip.blockmap"
|
|
"Claude-Code-Haha-${APP_VERSION}-mac-x64.dmg"
|
|
"Claude-Code-Haha-${APP_VERSION}-mac-x64.dmg.blockmap"
|
|
"Claude-Code-Haha-${APP_VERSION}-mac-x64.zip"
|
|
"Claude-Code-Haha-${APP_VERSION}-mac-x64.zip.blockmap"
|
|
"Claude-Code-Haha-${APP_VERSION}-linux-x86_64.AppImage"
|
|
"Claude-Code-Haha-${APP_VERSION}-linux-amd64.deb"
|
|
"Claude-Code-Haha-${APP_VERSION}-linux-x86_64.rpm"
|
|
"Claude-Code-Haha-${APP_VERSION}-linux-arm64.AppImage"
|
|
"Claude-Code-Haha-${APP_VERSION}-linux-arm64.deb"
|
|
"Claude-Code-Haha-${APP_VERSION}-linux-aarch64.rpm"
|
|
"Claude-Code-Haha-${APP_VERSION}-win-x64.exe"
|
|
"Claude-Code-Haha-${APP_VERSION}-win-x64.exe.blockmap"
|
|
"Claude-Code-Haha-${APP_VERSION}-win-arm64.exe"
|
|
"Claude-Code-Haha-${APP_VERSION}-win-arm64.exe.blockmap"
|
|
)
|
|
|
|
missing=()
|
|
for file in "${expected[@]}"; do
|
|
[ -f "artifacts/release-assets/$file" ] || missing+=("$file")
|
|
done
|
|
if [ "${#missing[@]}" -gt 0 ]; then
|
|
printf '::error::Missing complete release assets: %s\n' "${missing[*]}"
|
|
exit 1
|
|
fi
|
|
|
|
- name: Download update metadata artifacts
|
|
uses: actions/download-artifact@v4
|
|
with:
|
|
pattern: desktop-update-metadata-*
|
|
path: artifacts/update-metadata
|
|
merge-multiple: true
|
|
|
|
- name: Merge standard update metadata
|
|
run: bun run scripts/release-update-metadata.ts --metadata-dir artifacts/update-metadata --out-dir artifacts/update-metadata-standard
|
|
|
|
- name: Validate standard update metadata set
|
|
shell: bash
|
|
run: |
|
|
expected=(
|
|
"latest-mac.yml"
|
|
"latest-linux.yml"
|
|
"latest-linux-arm64.yml"
|
|
"latest.yml"
|
|
)
|
|
missing=()
|
|
for file in "${expected[@]}"; do
|
|
[ -f "artifacts/update-metadata-standard/$file" ] || missing+=("$file")
|
|
done
|
|
if [ "${#missing[@]}" -gt 0 ]; then
|
|
printf '::error::Missing standard update metadata: %s\n' "${missing[*]}"
|
|
exit 1
|
|
fi
|
|
|
|
- name: Publish complete GitHub release
|
|
uses: softprops/action-gh-release@v2
|
|
with:
|
|
tag_name: v${{ steps.version.outputs.value }}
|
|
name: Claude Code Haha v${{ steps.version.outputs.value }}
|
|
body: ${{ steps.release_notes.outputs.body }}
|
|
draft: true
|
|
prerelease: false
|
|
fail_on_unmatched_files: true
|
|
files: |
|
|
artifacts/release-assets/**/*.dmg
|
|
artifacts/release-assets/**/*.zip
|
|
artifacts/release-assets/**/*.exe
|
|
artifacts/release-assets/**/*.AppImage
|
|
artifacts/release-assets/**/*.deb
|
|
artifacts/release-assets/**/*.rpm
|
|
artifacts/release-assets/**/*.blockmap
|
|
artifacts/update-metadata-standard/*.yml
|
|
desktop/scripts/install-macos-unsigned.sh
|
|
|
|
- name: Publish GitHub release after complete upload
|
|
if: github.event_name == 'push' || (github.event_name == 'workflow_dispatch' && inputs.draft == false)
|
|
env:
|
|
GH_TOKEN: ${{ github.token }}
|
|
run: gh release edit "v${{ steps.version.outputs.value }}" --draft=false --repo "${{ github.repository }}"
|
|
|
|
- name: Keep workflow-dispatch release as draft
|
|
if: github.event_name == 'workflow_dispatch' && inputs.draft == true
|
|
env:
|
|
GH_TOKEN: ${{ github.token }}
|
|
run: gh release view "v${{ steps.version.outputs.value }}" --json isDraft --jq 'if .isDraft then "release remains draft" else error("workflow-dispatch release was published unexpectedly") end' --repo "${{ github.repository }}"
|