| name: Release desktop |
| |
| on: |
| workflow_dispatch: |
| |
| permissions: |
| contents: write |
| |
| concurrency: |
| group: release-desktop |
| cancel-in-progress: false |
| |
| jobs: |
| build: |
| if: github.ref == 'refs/heads/main' |
| strategy: |
| # Both platforms ship from one dispatch, so a failure on one still leaves |
| # the other artifact available for diagnosis. |
| fail-fast: false |
| matrix: |
| include: |
| - platform: macos |
| runner: macos-15 |
| - platform: windows |
| runner: windows-2025 |
| runs-on: ${{ matrix.runner }} |
| environment: release |
| timeout-minutes: 45 |
| defaults: |
| run: |
| # Windows runners default to pwsh; the release steps are written once, |
| # in bash, for both platforms. |
| shell: bash |
| |
| steps: |
| - name: Check out the dispatched commit |
| uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 |
| with: |
| ref: ${{ github.sha }} |
| fetch-depth: 0 |
| persist-credentials: false |
| |
| - name: Set up Node.js |
| uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0 |
| with: |
| node-version: '24' |
| cache: npm |
| |
| - name: Install dependencies |
| run: npm ci |
| |
| - name: Audit production dependencies |
| run: npm audit --omit=dev --audit-level=moderate |
| |
| - name: Write App Store Connect API key |
| if: matrix.platform == 'macos' |
| env: |
| APPLE_API_KEY_CONTENT: ${{ secrets.APPLE_API_KEY }} |
| APPLE_API_KEY_PATH: ${{ runner.temp }}/AuthKey_Maka.p8 |
| run: | |
| umask 077 |
| printf '%s' "$APPLE_API_KEY_CONTENT" > "$APPLE_API_KEY_PATH" |
| |
| - name: Resolve and reserve release identity |
| id: release |
| env: |
| GH_TOKEN: ${{ github.token }} |
| run: | |
| version="$(node -p "require('./apps/desktop/package.json').version")" |
| tag="v${version}" |
| dmg="apps/desktop/release/Maka-${version}-mac-arm64.dmg" |
| exe="apps/desktop/release/Maka-${version}-win-x64.exe" |
| |
| if git ls-remote --exit-code --tags origin "refs/tags/${tag}" >/dev/null 2>&1; then |
| echo "Tag ${tag} already exists." >&2 |
| exit 1 |
| fi |
| if gh release view "$tag" >/dev/null 2>&1; then |
| echo "Release ${tag} already exists." >&2 |
| exit 1 |
| fi |
| |
| { |
| echo "version=${version}" |
| echo "tag=${tag}" |
| echo "dmg=${dmg}" |
| echo "exe=${exe}" |
| } >> "$GITHUB_OUTPUT" |
| |
| - name: Package notarized app and signed DMG |
| if: matrix.platform == 'macos' |
| env: |
| CSC_LINK: ${{ secrets.CSC_LINK }} |
| CSC_KEY_PASSWORD: ${{ secrets.CSC_KEY_PASSWORD }} |
| APPLE_API_KEY: ${{ runner.temp }}/AuthKey_Maka.p8 |
| APPLE_API_KEY_ID: ${{ secrets.APPLE_API_KEY_ID }} |
| APPLE_API_ISSUER: ${{ secrets.APPLE_API_ISSUER }} |
| run: npm run package:macos-arm64 |
| |
| - name: Notarize and staple the signed final DMG |
| if: matrix.platform == 'macos' |
| env: |
| APPLE_API_KEY: ${{ runner.temp }}/AuthKey_Maka.p8 |
| APPLE_API_KEY_ID: ${{ secrets.APPLE_API_KEY_ID }} |
| APPLE_API_ISSUER: ${{ secrets.APPLE_API_ISSUER }} |
| DMG_PATH: ${{ steps.release.outputs.dmg }} |
| run: | |
| codesign --verify --verbose=4 "$DMG_PATH" |
| xcrun notarytool submit "$DMG_PATH" \ |
| --key "$APPLE_API_KEY" \ |
| --key-id "$APPLE_API_KEY_ID" \ |
| --issuer "$APPLE_API_ISSUER" \ |
| --wait |
| xcrun stapler staple "$DMG_PATH" |
| |
| codesign --verify --verbose=4 "$DMG_PATH" |
| xcrun stapler validate "$DMG_PATH" |
| spctl --assess \ |
| --type open \ |
| --context context:primary-signature \ |
| --verbose=4 \ |
| "$DMG_PATH" |
| |
| - name: Verify the final DMG |
| if: matrix.platform == 'macos' |
| run: npm run verify:macos-arm64 -- "${{ steps.release.outputs.dmg }}" |
| |
| # Windows has no Authenticode certificate yet, so this build is unsigned |
| # and there is nothing to notarize between packaging and verification. |
| - name: Package the Windows installer and ZIP |
| if: matrix.platform == 'windows' |
| run: npm run package:windows-x64 |
| |
| - name: Verify the Windows release |
| if: matrix.platform == 'windows' |
| run: npm run verify:windows-x64 -- "${{ steps.release.outputs.exe }}" |
| |
| - name: Upload the verified release assets |
| uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 |
| with: |
| name: release-${{ matrix.platform }} |
| path: | |
| apps/desktop/release/Maka-* |
| apps/desktop/release/latest*.yml |
| if-no-files-found: error |
| retention-days: 7 |
| |
| - name: Remove temporary release credentials |
| if: always() && matrix.platform == 'macos' |
| run: rm -f "${{ runner.temp }}/AuthKey_Maka.p8" |
| |
| source: |
| if: github.ref == 'refs/heads/main' |
| runs-on: ubuntu-latest |
| timeout-minutes: 20 |
| defaults: |
| run: |
| shell: bash |
| |
| steps: |
| - name: Check out the dispatched commit |
| uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 |
| with: |
| ref: ${{ github.sha }} |
| persist-credentials: false |
| |
| - name: Set up Node.js |
| uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0 |
| with: |
| node-version: '24' |
| cache: npm |
| |
| - name: Install pinned dependency metadata |
| run: npm ci --ignore-scripts |
| |
| - name: Materialize bundled Git source materials |
| run: npm run prepare:bundled-git-source |
| |
| - name: Archive and hash source materials |
| run: | |
| version="$(node -p "require('./apps/desktop/package.json').version")" |
| archive_dir="apps/desktop/release-sources" |
| archive_name="Maka-${version}-bundled-git-source.tar.gz" |
| archive="${archive_dir}/${archive_name}" |
| tar -C apps/desktop/release-sources/bundled-git -czf "$archive" . |
| (cd "$archive_dir" && sha256sum "$archive_name" > "${archive_name}.sha256") |
| |
| - name: Upload verified source assets |
| uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 |
| with: |
| name: release-source |
| path: | |
| apps/desktop/release-sources/Maka-*-bundled-git-source.tar.gz |
| apps/desktop/release-sources/Maka-*-bundled-git-source.tar.gz.sha256 |
| if-no-files-found: error |
| retention-days: 7 |
| |
| publish: |
| # One draft release carries both platforms, so it is created once, after |
| # every platform has been packaged and verified. |
| needs: [build, source] |
| runs-on: ubuntu-latest |
| timeout-minutes: 15 |
| defaults: |
| run: |
| shell: bash |
| |
| steps: |
| - name: Check out the dispatched commit |
| uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 |
| with: |
| ref: ${{ github.sha }} |
| persist-credentials: false |
| |
| - name: Download the verified release assets |
| uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 |
| with: |
| pattern: release-* |
| path: release-assets |
| merge-multiple: true |
| |
| - name: Create draft GitHub Release |
| env: |
| GH_TOKEN: ${{ github.token }} |
| run: | |
| version="$(node -p "require('./apps/desktop/package.json').version")" |
| tag="v${version}" |
| |
| notes="Apple Silicon macOS and Windows x64 release built from commit ${GITHUB_SHA}. |
| |
| Before publishing, download these draft assets on another Apple Silicon Mac and on a Windows x64 machine, and complete .github/RELEASE_CHECKLIST.md. |
| |
| The Windows build is unsigned: SmartScreen warns on first launch, and the download has to be checked against its .sha256 file. |
| |
| Bundled Git source materials and their checksum are attached to this draft. The packaged applications also carry the GPLv2 license and written source offer. |
| |
| Known limitation: Computer Use is not included in this release." |
| |
| gh release create "$tag" release-assets/* \ |
| --draft \ |
| --target "$GITHUB_SHA" \ |
| --title "Maka ${version}" \ |
| --notes "$notes" |
| |
| echo "Draft release ${tag} created from ${GITHUB_SHA}." >> "$GITHUB_STEP_SUMMARY" |