fix(chat): restore workspace choices for gated goals #623
Workflow file for this run
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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 |