name: Build release artifacts # Builds each platform natively on a v* tag and publishes one Gitea release. # # Runners required. None are registered yet, and a job with no matching runner # queues silently rather than failing, so register all three before tagging: # ubuntu-latest Linux AppImage # windows-latest Windows NSIS installer + portable zip # macos-latest macOS dmg + zip, both architectures (M-series builds both) # # Nothing cross-builds any more: dmg requires macOS, and the Windows exe needs # rcedit on Windows to get its icon and version resources. # # A RELEASE_TOKEN secret is optional; GITEA_TOKEN is used by default. # # All output is unsigned. Once certificates exist: for macOS drop mac.identity # from package.json and remove CSC_IDENTITY_AUTO_DISCOVERY; for Windows add the # cert secrets. Until then users hit Gatekeeper and SmartScreen warnings. on: push: tags: - 'v*' workflow_dispatch: jobs: linux: runs-on: ubuntu-latest defaults: run: working-directory: desktop-client steps: - uses: actions/checkout@v4 - uses: actions/setup-node@v4 with: node-version: '22' # Runs only here rather than in all three jobs: it is a repo-wide check, # and this job uses bash, which is not guaranteed on the Windows runner. - name: Verify tag matches package.json version if: startsWith(github.ref, 'refs/tags/v') run: | pkg_version=$(node -p "require('./package.json').version") tag_version="${GITHUB_REF_NAME#v}" if [ "$pkg_version" != "$tag_version" ]; then echo "Tag $GITHUB_REF_NAME does not match package.json version $pkg_version." >&2 echo "Bump package.json before tagging, or artifacts will be misnamed." >&2 exit 1 fi - name: Install dependencies run: npm ci - name: Run tests run: npm test - name: Build AppImage run: npx electron-builder --linux AppImage --publish never - uses: actions/upload-artifact@v3 with: name: linux path: desktop-client/dist/*.AppImage if-no-files-found: error windows: runs-on: windows-latest defaults: run: working-directory: desktop-client steps: - uses: actions/checkout@v4 - uses: actions/setup-node@v4 with: node-version: '22' - name: Install dependencies run: npm ci - name: Run tests run: npm test - name: Build installer and portable zip run: npx electron-builder --win nsis zip --publish never - uses: actions/upload-artifact@v3 with: name: windows path: | desktop-client/dist/*.exe desktop-client/dist/*-win-*.zip if-no-files-found: error macos: runs-on: macos-latest defaults: run: working-directory: desktop-client env: # No Developer ID yet: stop electron-builder auto-discovering an identity, # which otherwise fails the build on a real Mac rather than building unsigned. CSC_IDENTITY_AUTO_DISCOVERY: 'false' steps: - uses: actions/checkout@v4 - uses: actions/setup-node@v4 with: node-version: '22' - name: Install dependencies run: npm ci - name: Run tests run: npm test - name: Build dmg and zip (both architectures) run: npx electron-builder --mac --x64 --arm64 --publish never - uses: actions/upload-artifact@v3 with: name: macos path: | desktop-client/dist/*.dmg desktop-client/dist/*-mac-*.zip if-no-files-found: error publish: # Separate job so only one process touches the release: parallel build jobs # would race to create it and one would fail on the duplicate tag. needs: [linux, windows, macos] if: startsWith(github.ref, 'refs/tags/v') runs-on: ubuntu-latest steps: # Checkout first: it cleans the workspace, which would delete downloaded artifacts. - uses: actions/checkout@v4 - uses: actions/download-artifact@v3 with: path: artifacts - name: Create release and attach artifacts env: # Gitea injects GITEA_TOKEN automatically, repo-scoped and short-lived. # RELEASE_TOKEN is an optional override if wider rights are ever needed. TOKEN: ${{ secrets.RELEASE_TOKEN || secrets.GITEA_TOKEN }} API: ${{ github.server_url }}/api/v1/repos/${{ github.repository }} run: | set -euo pipefail if [ -z "${TOKEN:-}" ]; then echo "No token available (GITEA_TOKEN missing and RELEASE_TOKEN unset)." >&2 exit 1 fi # Report the HTTP status and body on failure. A bare `curl -sf | JSON.parse` # dies with "Unexpected end of JSON input" and hides the real cause, which is # how the v1.0.0 run failed on a missing token. api() { local method="$1" path="$2"; shift 2 local out code out=$(curl -s -w '\n%{http_code}' -X "$method" \ -H "Authorization: token $TOKEN" "$API$path" "$@") code=$(printf '%s' "$out" | tail -n1) body=$(printf '%s' "$out" | sed '$d') if [ "$code" -ge 400 ]; then echo "$method $path -> HTTP $code" >&2 echo "$body" >&2 return 1 fi printf '%s' "$body" } # Reuse the release if the tag already has one, otherwise create it. release_id=$(api GET "/releases/tags/$GITHUB_REF_NAME" 2>/dev/null \ | node -pe "JSON.parse(require('fs').readFileSync(0,'utf8')).id" 2>/dev/null || true) if [ -z "${release_id:-}" ]; then echo "No release for $GITHUB_REF_NAME yet; creating it." release_id=$(api POST "/releases" \ -H "Content-Type: application/json" \ -d "{\"tag_name\":\"$GITHUB_REF_NAME\",\"name\":\"$GITHUB_REF_NAME\"}" \ | node -pe "JSON.parse(require('fs').readFileSync(0,'utf8')).id") fi if [ -z "${release_id:-}" ]; then echo "Could not determine a release id." >&2 exit 1 fi echo "Publishing to release $release_id" # while-read rather than word-splitting a find, so spaces in names are safe. found=0 while IFS= read -r f; do name=$(basename "$f") echo "Attaching $name ($(du -h "$f" | cut -f1))" api POST "/releases/$release_id/assets?name=$name" -F "attachment=@$f" >/dev/null found=$((found + 1)) done < <(find artifacts -type f \( -name '*.AppImage' -o -name '*.zip' \ -o -name '*.dmg' -o -name '*.exe' \) | sort) if [ "$found" -eq 0 ]; then echo "No artifacts found to attach; build jobs produced nothing." >&2 find artifacts -type f | head -20 >&2 exit 1 fi echo "Attached $found artifacts to release $release_id" # Constant-URL copy of the AppImage for updaters that poll a static URL # (e.g. Shelly). Runs after the versioned release exists, so a failure here # cannot leave a tag without its assets. - name: Update rolling "latest" AppImage env: TOKEN: ${{ secrets.RELEASE_TOKEN || secrets.GITEA_TOKEN }} API: ${{ github.server_url }}/api/v1/repos/${{ github.repository }} run: bash .gitea/scripts/update-latest.sh