Skip to content

fix(chat): restore workspace choices for gated goals #623

fix(chat): restore workspace choices for gated goals

fix(chat): restore workspace choices for gated goals #623

name: Desktop Release Artifacts
on:
pull_request:
paths:
- ".github/workflows/desktop-release-artifacts.yml"
- "apps/desktop/loopx-control-plane/**"
- "apps/presentation/dashboard/**"
- "examples/desktop-icon-alpha-smoke.py"
- "examples/desktop-release-workflow-smoke.py"
- "scripts/desktop_release_artifacts.py"
release:
types: [published]
workflow_dispatch:
inputs:
tag:
description: "Existing GitHub Release tag to attach desktop artifacts to"
required: false
type: string
permissions:
contents: read
concurrency:
group: desktop-release-artifacts-${{ github.event.release.tag_name || inputs.tag || github.ref }}
cancel-in-progress: false
jobs:
build:
if: github.repository == 'loopx-project/loopx'
name: ${{ matrix.name }}
runs-on: ${{ matrix.runner }}
timeout-minutes: 30
permissions:
contents: read
id-token: write
attestations: write
strategy:
fail-fast: false
matrix:
include:
- name: macOS desktop
runner: macos-15
bundles: dmg app
artifact-name: loopx-macos
artifact-path: dist/desktop/*
- name: Windows desktop
runner: windows-latest
bundles: msi nsis
artifact-name: loopx-windows
artifact-path: dist/desktop/*
steps:
- name: Check out release source
uses: actions/checkout@v7
with:
ref: ${{ github.event.release.tag_name || inputs.tag || github.sha }}
- name: Resolve release identity
id: identity
shell: bash
env:
EVENT_RELEASE_TAG: ${{ github.event.release.tag_name }}
INPUT_RELEASE_TAG: ${{ inputs.tag }}
run: |
set -euo pipefail
release_tag="${EVENT_RELEASE_TAG:-${INPUT_RELEASE_TAG:-}}"
if [[ -z "${release_tag}" ]]; then
release_tag="$(git describe --tags --exact-match 2>/dev/null || echo "preview-${GITHUB_SHA::12}")"
fi
echo "release-tag=${release_tag}" >> "${GITHUB_OUTPUT}"
- name: Set up Node
uses: actions/setup-node@v6
with:
node-version: "24"
cache: npm
cache-dependency-path: |
apps/desktop/loopx-control-plane/package-lock.json
apps/presentation/dashboard/package-lock.json
- name: Set up Rust
uses: dtolnay/rust-toolchain@stable
- name: Cache Rust build
uses: Swatinem/rust-cache@v2
with:
workspaces: apps/desktop/loopx-control-plane/src-tauri -> target
key: desktop-${{ matrix.runner }}
- name: Install desktop dependencies
working-directory: apps/desktop/loopx-control-plane
run: npm ci
- name: Validate desktop icon background
run: python examples/desktop-icon-alpha-smoke.py
- name: Validate desktop release workflow
if: github.event_name == 'pull_request'
run: python examples/desktop-release-workflow-smoke.py
- name: Build unsigned desktop preview bundles
if: github.event_name == 'pull_request'
working-directory: apps/desktop/loopx-control-plane
run: npm run build -- --bundles ${{ matrix.bundles }} --no-sign
- name: Build integrity-signed macOS release bundles
if: github.event_name != 'pull_request' && runner.os == 'macOS'
working-directory: apps/desktop/loopx-control-plane
env:
RELEASE_TAG: ${{ steps.identity.outputs.release-tag }}
run: |
set -euo pipefail
release_version="${RELEASE_TAG#v}"
if [[ ! "${release_version}" =~ ^[0-9]+\.[0-9]+\.[0-9]+([+-][0-9A-Za-z.-]+)?$ ]]; then
echo "::error title=Invalid desktop release version::${RELEASE_TAG} is not a supported release tag"
exit 1
fi
npm run build -- --bundles ${{ matrix.bundles }} \
--config "{\"version\":\"${release_version}\",\"bundle\":{\"macOS\":{\"signingIdentity\":\"-\"}}}"
- name: Build Windows release bundles
if: github.event_name != 'pull_request' && runner.os == 'Windows'
working-directory: apps/desktop/loopx-control-plane
shell: bash
env:
RELEASE_TAG: ${{ steps.identity.outputs.release-tag }}
run: |
set -euo pipefail
release_version="${RELEASE_TAG#v}"
if [[ ! "${release_version}" =~ ^[0-9]+\.[0-9]+\.[0-9]+([+-][0-9A-Za-z.-]+)?$ ]]; then
echo "::error title=Invalid desktop release version::${RELEASE_TAG} is not a supported release tag"
exit 1
fi
npm run build -- --bundles ${{ matrix.bundles }} --no-sign \
--config "{\"version\":\"${release_version}\"}"
- name: Verify macOS desktop bundles
if: github.event_name != 'pull_request' && runner.os == 'macOS'
shell: bash
run: |
set -euo pipefail
app_path="apps/desktop/loopx-control-plane/src-tauri/target/release/bundle/macos/LoopX.app"
dmg_path="$(find apps/desktop/loopx-control-plane/src-tauri/target/release/bundle/dmg -maxdepth 1 -name '*.dmg' -print -quit)"
test -n "${dmg_path}"
codesign --verify --deep --strict --verbose=2 "${app_path}"
codesign --display --verbose=4 "${app_path}" 2>&1 \
| grep -q '^Signature=adhoc$'
hdiutil verify "${dmg_path}"
- name: Collect macOS desktop bundles
if: runner.os == 'macOS'
shell: bash
run: |
set -euo pipefail
mkdir -p dist/desktop
cp apps/desktop/loopx-control-plane/src-tauri/target/release/bundle/dmg/*.dmg dist/desktop/
ditto -c -k --keepParent \
"apps/desktop/loopx-control-plane/src-tauri/target/release/bundle/macos/LoopX.app" \
"dist/desktop/LoopX.app.zip"
- name: Collect Windows desktop bundles
if: runner.os == 'Windows'
shell: pwsh
run: |
New-Item -ItemType Directory -Force -Path dist/desktop | Out-Null
Copy-Item apps/desktop/loopx-control-plane/src-tauri/target/release/bundle/msi/*.msi dist/desktop/
Copy-Item apps/desktop/loopx-control-plane/src-tauri/target/release/bundle/nsis/*.exe dist/desktop/
- name: Attest desktop artifacts
if: github.event_name != 'pull_request'
uses: actions/attest@v4
with:
subject-path: ${{ matrix.artifact-path }}
- name: Upload desktop artifact
uses: actions/upload-artifact@v7
with:
name: ${{ matrix.artifact-name }}-${{ steps.identity.outputs.release-tag }}
path: ${{ matrix.artifact-path }}
if-no-files-found: error
retention-days: 30
upload-release:
if: github.event_name != 'pull_request'
needs: build
runs-on: ubuntu-latest
timeout-minutes: 10
permissions:
contents: write
steps:
- name: Check out release workflow source
uses: actions/checkout@v7
- name: Download desktop artifacts
uses: actions/download-artifact@v7
with:
pattern: loopx-*
path: dist/desktop
merge-multiple: true
- name: Resolve release identity
id: identity
env:
EVENT_RELEASE_TAG: ${{ github.event.release.tag_name }}
INPUT_RELEASE_TAG: ${{ inputs.tag }}
run: |
set -euo pipefail
release_tag="${EVENT_RELEASE_TAG:-${INPUT_RELEASE_TAG:-}}"
if [[ -z "${release_tag}" ]]; then
echo "::error title=Missing release tag::release uploads require a release event or workflow_dispatch tag"
exit 1
fi
echo "release-tag=${release_tag}" >> "${GITHUB_OUTPUT}"
echo "release-version=${release_tag#v}" >> "${GITHUB_OUTPUT}"
- name: Validate target release
env:
GH_TOKEN: ${{ github.token }}
RELEASE_TAG: ${{ steps.identity.outputs.release-tag }}
run: gh release view "${RELEASE_TAG}" --repo "${GITHUB_REPOSITORY}" >/dev/null
- name: Back up existing desktop release assets
if: github.event_name == 'workflow_dispatch'
env:
GH_TOKEN: ${{ github.token }}
RELEASE_TAG: ${{ steps.identity.outputs.release-tag }}
run: |
set -euo pipefail
mkdir -p dist/previous-desktop
mapfile -t assets < <(
gh release view "${RELEASE_TAG}" \
--repo "${GITHUB_REPOSITORY}" \
--json assets \
--jq '.assets[].name | select(. == "DESKTOP-SHA256SUMS" or (startswith("LoopX") and (endswith(".dmg") or endswith(".zip") or endswith(".exe") or endswith(".msi"))))'
)
for asset in "${assets[@]}"; do
gh release download "${RELEASE_TAG}" \
--repo "${GITHUB_REPOSITORY}" \
--pattern "${asset}" \
--dir dist/previous-desktop
done
{
echo "release_tag=${RELEASE_TAG}"
echo "asset_count=${#assets[@]}"
printf '%s\n' "${assets[@]}"
} > dist/previous-desktop/ASSET-INVENTORY.txt
cd dist/previous-desktop
find . -maxdepth 1 -type f \
! -name PREVIOUS-DESKTOP-SHA256SUMS \
-printf '%f\n' \
| LC_ALL=C sort \
| while IFS= read -r artifact; do
sha256sum "${artifact}"
done \
> PREVIOUS-DESKTOP-SHA256SUMS
- name: Preserve previous desktop release assets
if: github.event_name == 'workflow_dispatch'
uses: actions/upload-artifact@v7
with:
name: previous-desktop-${{ steps.identity.outputs.release-tag }}-${{ github.run_id }}
path: dist/previous-desktop
if-no-files-found: error
retention-days: 30
- name: Validate complete rebuild and generate desktop checksums
env:
RELEASE_VERSION: ${{ steps.identity.outputs.release-version }}
run: |
set -euo pipefail
python scripts/desktop_release_artifacts.py write-checksums \
--dist-dir dist/desktop \
--version "${RELEASE_VERSION}" \
--output dist/desktop/DESKTOP-SHA256SUMS
python scripts/desktop_release_artifacts.py verify-checksums \
--dist-dir dist/desktop \
--version "${RELEASE_VERSION}" \
--checksum-file dist/desktop/DESKTOP-SHA256SUMS
cd dist/desktop
sha256sum --check DESKTOP-SHA256SUMS
- name: Replace complete desktop binary set
env:
GH_TOKEN: ${{ github.token }}
RELEASE_TAG: ${{ steps.identity.outputs.release-tag }}
run: |
set -euo pipefail
if gh release view "${RELEASE_TAG}" \
--repo "${GITHUB_REPOSITORY}" \
--json assets \
--jq 'any(.assets[]; .name == "DESKTOP-SHA256SUMS")' \
| grep -qx true
then
gh release delete-asset "${RELEASE_TAG}" DESKTOP-SHA256SUMS \
--repo "${GITHUB_REPOSITORY}" \
--yes
fi
gh release upload "${RELEASE_TAG}" \
dist/desktop/*.dmg \
dist/desktop/*.zip \
dist/desktop/*.exe \
dist/desktop/*.msi \
--repo "${GITHUB_REPOSITORY}" \
--clobber
- name: Replace desktop checksum manifest last
env:
GH_TOKEN: ${{ github.token }}
RELEASE_TAG: ${{ steps.identity.outputs.release-tag }}
run: |
gh release upload "${RELEASE_TAG}" \
dist/desktop/DESKTOP-SHA256SUMS \
--repo "${GITHUB_REPOSITORY}" \
--clobber
- name: Verify published desktop release
env:
GH_TOKEN: ${{ github.token }}
RELEASE_TAG: ${{ steps.identity.outputs.release-tag }}
RELEASE_VERSION: ${{ steps.identity.outputs.release-version }}
run: |
set -euo pipefail
mkdir -p dist/published-desktop
for asset in \
LoopX.app.zip \
"LoopX_${RELEASE_VERSION}_aarch64.dmg" \
"LoopX_${RELEASE_VERSION}_x64-setup.exe" \
"LoopX_${RELEASE_VERSION}_x64_en-US.msi" \
DESKTOP-SHA256SUMS
do
gh release download "${RELEASE_TAG}" \
--repo "${GITHUB_REPOSITORY}" \
--pattern "${asset}" \
--dir dist/published-desktop
done
python scripts/desktop_release_artifacts.py verify-checksums \
--dist-dir dist/published-desktop \
--version "${RELEASE_VERSION}" \
--checksum-file dist/published-desktop/DESKTOP-SHA256SUMS