name: Desktop Release on: workflow_dispatch: inputs: tag: description: "Release tag to create or update (defaults to v)" required: false type: string release_name: description: 'Release name (defaults to "CloudCLI Desktop ")' required: false type: string prerelease: description: "Mark the GitHub release as a prerelease" required: true default: false type: boolean jobs: resolve-release: name: Resolve release metadata runs-on: ubuntu-latest permissions: contents: read outputs: tag: ${{ steps.release.outputs.tag }} release_name: ${{ steps.release.outputs.release_name }} server_bundle_tag: ${{ steps.release.outputs.server_bundle_tag }} steps: - name: Checkout uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6 with: persist-credentials: false - name: Resolve release metadata id: release env: TAG_INPUT: ${{ inputs.tag }} RELEASE_NAME_INPUT: ${{ inputs.release_name }} run: | VERSION="$(node -p "require('./package.json').version")" TAG="$TAG_INPUT" if [ -z "$TAG" ]; then TAG="v${VERSION}" fi TAG="$(printf '%s' "$TAG" | tr -d '\r\n' | sed 's/[^A-Za-z0-9._-]/-/g')" if [ -z "$TAG" ]; then echo "Resolved release tag is empty after normalization." >&2 exit 1 fi RELEASE_NAME="$RELEASE_NAME_INPUT" if [ -z "$RELEASE_NAME" ]; then RELEASE_NAME="CloudCLI Desktop ${TAG}" fi RELEASE_NAME_DELIMITER="release_name_${GITHUB_RUN_ID}_${GITHUB_RUN_ATTEMPT}" { echo "tag=$TAG" echo "release_name<<$RELEASE_NAME_DELIMITER" printf '%s\n' "$RELEASE_NAME" echo "$RELEASE_NAME_DELIMITER" echo "server_bundle_tag=cloudcli-local-server-${TAG}" } >> "$GITHUB_OUTPUT" build-macos: name: Build signed macOS desktop app needs: resolve-release runs-on: macos-latest permissions: contents: read steps: - name: Checkout uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6 with: fetch-depth: 0 persist-credentials: false - name: Set up Node.js uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6 with: node-version: 22 cache: npm - name: Install dependencies run: npm ci - name: Typecheck run: npm run typecheck - name: Configure release server bundle source env: SERVER_BUNDLE_TAG: ${{ needs.resolve-release.outputs.server_bundle_tag }} run: printf '{"releaseTag":"%s"}\n' "$SERVER_BUNDLE_TAG" > electron/server-bundle-config.json - name: Verify macOS signing secrets are configured run: | test -n "$CSC_LINK" test -n "$CSC_KEY_PASSWORD" test -n "$APPLE_ID" test -n "$APPLE_APP_SPECIFIC_PASSWORD" test -n "$APPLE_TEAM_ID" env: CSC_LINK: ${{ secrets.CSC_LINK }} CSC_KEY_PASSWORD: ${{ secrets.CSC_KEY_PASSWORD }} APPLE_ID: ${{ secrets.APPLE_ID }} APPLE_APP_SPECIFIC_PASSWORD: ${{ secrets.APPLE_APP_SPECIFIC_PASSWORD }} APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }} - name: Build signed and notarized macOS artifacts run: npm run desktop:dist:mac -- --publish never env: CLOUDCLI_SEMANTICS_BUILD_REQUIRED: "1" CSC_LINK: ${{ secrets.CSC_LINK }} CSC_KEY_PASSWORD: ${{ secrets.CSC_KEY_PASSWORD }} APPLE_ID: ${{ secrets.APPLE_ID }} APPLE_APP_SPECIFIC_PASSWORD: ${{ secrets.APPLE_APP_SPECIFIC_PASSWORD }} APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }} - name: Build macOS local server bundle run: node scripts/release/build-server-bundle.js - name: Stage macOS release assets run: | mkdir -p desktop-release-assets server-release-assets test -n "$(find release/desktop -maxdepth 1 -name '*.dmg' -print -quit)" shasum -a 256 release/desktop/*.dmg > desktop-release-assets/SHASUMS256-macos.txt cp release/desktop/*.dmg desktop-release-assets/ test -n "$(find release/local-server -maxdepth 1 -name 'cloudcli-local-server-*.tar.gz' -print -quit)" test -n "$(find release/local-server -maxdepth 1 -name 'cloudcli-local-server-*.tar.gz.sha256' -print -quit)" cp release/local-server/* server-release-assets/ - name: Upload macOS desktop assets uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6 with: name: desktop-release-macos path: desktop-release-assets/* if-no-files-found: error - name: Upload macOS server assets uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6 with: name: server-release-macos path: server-release-assets/* if-no-files-found: error build-windows: name: Build Windows desktop app needs: resolve-release runs-on: windows-latest permissions: contents: read steps: - name: Checkout uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6 with: fetch-depth: 0 persist-credentials: false - name: Set up Node.js uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6 with: node-version: 22 cache: npm - name: Install dependencies run: npm ci env: GITHUB_TOKEN: ${{ github.token }} - name: Typecheck run: npm run typecheck - name: Configure release server bundle source shell: bash env: SERVER_BUNDLE_TAG: ${{ needs.resolve-release.outputs.server_bundle_tag }} run: printf '{"releaseTag":"%s"}\n' "$SERVER_BUNDLE_TAG" > electron/server-bundle-config.json - name: Check Windows signing secrets id: windows-signing shell: bash env: WINDOWS_CSC_LINK: ${{ secrets.WINDOWS_CSC_LINK }} WINDOWS_CSC_KEY_PASSWORD: ${{ secrets.WINDOWS_CSC_KEY_PASSWORD }} run: | if [ -n "$WINDOWS_CSC_LINK" ] && [ -n "$WINDOWS_CSC_KEY_PASSWORD" ]; then echo "enabled=true" >> "$GITHUB_OUTPUT" else echo "enabled=false" >> "$GITHUB_OUTPUT" fi - name: Build signed Windows artifacts if: steps.windows-signing.outputs.enabled == 'true' run: npm run desktop:dist:win -- --publish never env: CLOUDCLI_SEMANTICS_BUILD_REQUIRED: "1" CSC_LINK: ${{ secrets.WINDOWS_CSC_LINK }} CSC_KEY_PASSWORD: ${{ secrets.WINDOWS_CSC_KEY_PASSWORD }} - name: Build unsigned Windows artifacts if: steps.windows-signing.outputs.enabled != 'true' run: npm run desktop:dist:win -- --publish never env: CLOUDCLI_SEMANTICS_BUILD_REQUIRED: "1" CSC_IDENTITY_AUTO_DISCOVERY: "false" - name: Build Windows local server bundle run: node scripts/release/build-server-bundle.js - name: Stage Windows release assets shell: bash run: | mkdir -p desktop-release-assets server-release-assets test -n "$(find release/desktop -maxdepth 1 -name '*.exe' -print -quit)" sha256sum release/desktop/*.exe > desktop-release-assets/SHASUMS256-windows.txt cp release/desktop/*.exe desktop-release-assets/ test -n "$(find release/local-server -maxdepth 1 -name 'cloudcli-local-server-*.tar.gz' -print -quit)" test -n "$(find release/local-server -maxdepth 1 -name 'cloudcli-local-server-*.tar.gz.sha256' -print -quit)" cp release/local-server/* server-release-assets/ - name: Upload Windows desktop assets uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6 with: name: desktop-release-windows path: desktop-release-assets/* if-no-files-found: error - name: Upload Windows server assets uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6 with: name: server-release-windows path: server-release-assets/* if-no-files-found: error publish: name: Publish desktop release needs: - resolve-release - build-macos - build-windows runs-on: ubuntu-latest permissions: contents: write steps: - name: Download desktop assets uses: actions/download-artifact@018cc2cf5baa6db3ef3c5f8a56943fffe632ef53 # v6 with: pattern: desktop-release-* path: release/desktop merge-multiple: true - name: Download server assets uses: actions/download-artifact@018cc2cf5baa6db3ef3c5f8a56943fffe632ef53 # v6 with: pattern: server-release-* path: release/local-server merge-multiple: true - name: Verify release assets run: | test -n "$(find release/desktop -maxdepth 1 -name '*.dmg' -print -quit)" test -n "$(find release/desktop -maxdepth 1 -name '*.exe' -print -quit)" test -f release/desktop/SHASUMS256-macos.txt test -f release/desktop/SHASUMS256-windows.txt test -n "$(find release/local-server -maxdepth 1 -name 'cloudcli-local-server-*.tar.gz' -print -quit)" test -n "$(find release/local-server -maxdepth 1 -name 'cloudcli-local-server-*.tar.gz.sha256' -print -quit)" find release -maxdepth 2 -type f -print | sort - name: Publish local server runtime assets uses: softprops/action-gh-release@b4309332981a82ec1c5618f44dd2e27cc8bfbfda # v3.0.0 with: tag_name: ${{ needs.resolve-release.outputs.server_bundle_tag }} target_commitish: ${{ github.sha }} name: CloudCLI Local Server Runtime (${{ needs.resolve-release.outputs.tag }}) body: | This prerelease contains the Local mode runtime for CloudCLI Desktop. Download CloudCLI Desktop from the main ${{ needs.resolve-release.outputs.tag }} release. When you open Local CloudCLI, the desktop app automatically downloads the matching runtime from this prerelease. You do not need to download these runtime files manually. prerelease: true fail_on_unmatched_files: false overwrite_files: true files: | release/local-server/* - name: Publish GitHub release assets uses: softprops/action-gh-release@b4309332981a82ec1c5618f44dd2e27cc8bfbfda # v3.0.0 with: tag_name: ${{ needs.resolve-release.outputs.tag }} target_commitish: ${{ github.sha }} name: ${{ needs.resolve-release.outputs.release_name }} body: | Download the CloudCLI Desktop installer for your platform. The local server runtime used by local mode is installed automatically by the desktop app. You do not need to download any server bundle manually. prerelease: ${{ inputs.prerelease }} fail_on_unmatched_files: false overwrite_files: true files: | release/desktop/*