Update package.json #37
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: Release | |
| on: | |
| push: | |
| branches: | |
| - release | |
| workflow_dispatch: | |
| inputs: | |
| dry_run: | |
| description: "Run all build jobs but skip publish (no tag, no GitHub Release)" | |
| type: boolean | |
| default: true | |
| permissions: | |
| contents: write | |
| concurrency: | |
| group: release | |
| cancel-in-progress: true | |
| jobs: | |
| prepare: | |
| name: Prepare Release | |
| runs-on: ubuntu-latest | |
| outputs: | |
| version: ${{ steps.version.outputs.version }} | |
| tag: ${{ steps.version.outputs.tag }} | |
| tag_exists: ${{ steps.check.outputs.exists }} | |
| is_dry_run: ${{ steps.mode.outputs.is_dry_run }} | |
| steps: | |
| - name: Check out repository | |
| uses: actions/checkout@v4 | |
| - name: Read version from package.json | |
| id: version | |
| run: | | |
| VERSION=$(node -p "require('./package.json').version") | |
| echo "version=$VERSION" >> "$GITHUB_OUTPUT" | |
| echo "tag=v$VERSION" >> "$GITHUB_OUTPUT" | |
| echo "Release version: v$VERSION" | |
| - name: Check if tag already exists | |
| id: check | |
| run: | | |
| if git ls-remote --tags origin "refs/tags/v${{ steps.version.outputs.version }}" | grep -q .; then | |
| echo "exists=true" >> "$GITHUB_OUTPUT" | |
| echo "Tag v${{ steps.version.outputs.version }} already exists — skipping." | |
| else | |
| echo "exists=false" >> "$GITHUB_OUTPUT" | |
| fi | |
| - name: Compute dry-run flag | |
| id: mode | |
| run: | | |
| if [ "${{ github.event_name }}" = "workflow_dispatch" ] && [ "${{ inputs.dry_run }}" = "true" ]; then | |
| echo "is_dry_run=true" >> "$GITHUB_OUTPUT" | |
| echo "Dry run: builds will run, publish will be skipped." | |
| else | |
| echo "is_dry_run=false" >> "$GITHUB_OUTPUT" | |
| echo "Real release: publish will run if tag does not exist." | |
| fi | |
| release_mac: | |
| name: Build macOS (${{ matrix.arch }}) | |
| needs: prepare | |
| if: needs.prepare.outputs.tag_exists == 'false' | |
| runs-on: macos-latest | |
| strategy: | |
| matrix: | |
| arch: [x64, arm64] | |
| steps: | |
| - name: Check out repository | |
| uses: actions/checkout@v4 | |
| - name: Set up Node.js | |
| uses: actions/setup-node@v4 | |
| with: | |
| node-version: 22 | |
| cache: npm | |
| - name: Install dependencies | |
| run: npm ci | |
| - name: Rebuild native dependencies for target arch | |
| run: npx electron-builder install-app-deps --arch=${{ matrix.arch }} | |
| - name: Build app | |
| env: | |
| VITE_POSTHOG_KEY: ${{ secrets.VITE_POSTHOG_KEY }} | |
| VITE_POSTHOG_HOST: ${{ secrets.VITE_POSTHOG_HOST }} | |
| run: npm run build | |
| - name: Stage App Store Connect API key | |
| env: | |
| ASC_API_KEY_BASE64: ${{ secrets.ASC_API_KEY }} | |
| ASC_KEY_ID: ${{ secrets.ASC_KEY_ID }} | |
| run: | | |
| set -euo pipefail | |
| if [ -z "${ASC_API_KEY_BASE64:-}" ]; then | |
| echo "ASC_API_KEY secret is missing — notarization will fail." >&2 | |
| exit 1 | |
| fi | |
| KEY_DIR="$RUNNER_TEMP/appstore" | |
| mkdir -p "$KEY_DIR" | |
| KEY_PATH="$KEY_DIR/AuthKey_${ASC_KEY_ID}.p8" | |
| printf '%s' "$ASC_API_KEY_BASE64" | base64 --decode > "$KEY_PATH" | |
| chmod 600 "$KEY_PATH" | |
| echo "APPLE_API_KEY=$KEY_PATH" >> "$GITHUB_ENV" | |
| - name: Package macOS artifacts | |
| env: | |
| CSC_LINK: ${{ secrets.CSC_LINK }} | |
| CSC_KEY_PASSWORD: ${{ secrets.CSC_KEY_PASSWORD }} | |
| APPLE_API_KEY_ID: ${{ secrets.ASC_KEY_ID }} | |
| APPLE_API_ISSUER: ${{ secrets.ASC_ISSUER_ID }} | |
| run: npx electron-builder --mac dmg zip --${{ matrix.arch }} --publish never | |
| - name: Verify native module architecture | |
| run: | | |
| set -euo pipefail | |
| NODE_FILE="dist/mac-${{ matrix.arch }}/Hermes Agent.app/Contents/Resources/app.asar.unpacked/node_modules/better-sqlite3/build/Release/better_sqlite3.node" | |
| if [ ! -f "$NODE_FILE" ]; then | |
| NODE_FILE="dist/mac/Hermes Agent.app/Contents/Resources/app.asar.unpacked/node_modules/better-sqlite3/build/Release/better_sqlite3.node" | |
| fi | |
| file "$NODE_FILE" | |
| case "${{ matrix.arch }}" in | |
| x64) file "$NODE_FILE" | grep -q "x86_64" ;; | |
| arm64) file "$NODE_FILE" | grep -q "arm64" ;; | |
| esac | |
| - name: Upload artifacts | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: mac-${{ matrix.arch }}-artifacts | |
| path: | | |
| dist/*.dmg | |
| dist/*.zip | |
| dist/*.blockmap | |
| release_linux: | |
| name: Build Linux | |
| needs: prepare | |
| if: needs.prepare.outputs.tag_exists == 'false' | |
| runs-on: ubuntu-latest | |
| steps: | |
| - name: Check out repository | |
| uses: actions/checkout@v4 | |
| - name: Set up Node.js | |
| uses: actions/setup-node@v4 | |
| with: | |
| node-version: 22 | |
| cache: npm | |
| - name: Install dependencies | |
| run: npm ci | |
| - name: Build app | |
| env: | |
| VITE_POSTHOG_KEY: ${{ secrets.VITE_POSTHOG_KEY }} | |
| VITE_POSTHOG_HOST: ${{ secrets.VITE_POSTHOG_HOST }} | |
| run: npm run build | |
| - name: Install rpmbuild | |
| run: sudo apt-get update && sudo apt-get install -y rpm | |
| - name: Package Linux artifacts | |
| run: npx electron-builder --linux AppImage deb rpm --publish never | |
| - name: Upload artifacts | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: linux-artifacts | |
| path: | | |
| dist/*.AppImage | |
| dist/*.deb | |
| dist/*.rpm | |
| dist/latest-linux.yml | |
| release_windows: | |
| name: Build Windows | |
| needs: prepare | |
| if: needs.prepare.outputs.tag_exists == 'false' | |
| runs-on: windows-latest | |
| steps: | |
| - name: Check out repository | |
| uses: actions/checkout@v4 | |
| - name: Set up Node.js | |
| uses: actions/setup-node@v4 | |
| with: | |
| node-version: 22 | |
| cache: npm | |
| - name: Install dependencies | |
| run: npm ci | |
| - name: Build app | |
| env: | |
| VITE_POSTHOG_KEY: ${{ secrets.VITE_POSTHOG_KEY }} | |
| VITE_POSTHOG_HOST: ${{ secrets.VITE_POSTHOG_HOST }} | |
| run: npm run build | |
| - name: Package Windows artifacts | |
| run: npx electron-builder --win nsis portable --x64 --publish never | |
| - name: Upload artifacts | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: windows-artifacts | |
| path: | | |
| dist/*.exe | |
| dist/*.exe.blockmap | |
| dist/latest.yml | |
| generate_winget: | |
| name: Generate winget manifests | |
| needs: [prepare, release_windows] | |
| if: needs.prepare.outputs.tag_exists == 'false' | |
| runs-on: ubuntu-latest | |
| steps: | |
| - name: Check out repository | |
| uses: actions/checkout@v4 | |
| - name: Set up Node.js | |
| uses: actions/setup-node@v4 | |
| with: | |
| node-version: 22 | |
| - name: Download Windows installer artifact | |
| uses: actions/download-artifact@v4 | |
| with: | |
| name: windows-artifacts | |
| path: dist/ | |
| - name: Generate winget manifests | |
| env: | |
| VERSION: ${{ needs.prepare.outputs.version }} | |
| PUBLISH_OWNER: fathah | |
| run: node scripts/generate-winget-manifests.mjs | |
| - name: Upload winget manifests artifact | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: winget-manifests-${{ needs.prepare.outputs.version }} | |
| path: dist/winget/ | |
| publish: | |
| name: Publish Release | |
| needs: | |
| [prepare, release_mac, release_linux, release_windows, generate_winget] | |
| if: needs.prepare.outputs.is_dry_run == 'false' && needs.prepare.outputs.tag_exists == 'false' | |
| runs-on: ubuntu-latest | |
| steps: | |
| - name: Check out repository | |
| uses: actions/checkout@v4 | |
| - name: Create tag | |
| run: | | |
| git config user.name "github-actions[bot]" | |
| git config user.email "github-actions[bot]@users.noreply.github.com" | |
| git tag ${{ needs.prepare.outputs.tag }} | |
| git push origin ${{ needs.prepare.outputs.tag }} | |
| - name: Download all artifacts | |
| uses: actions/download-artifact@v4 | |
| with: | |
| path: artifacts | |
| merge-multiple: true | |
| - name: Generate macOS update metadata | |
| env: | |
| VERSION: ${{ needs.prepare.outputs.version }} | |
| run: | | |
| node <<'NODE' | |
| const crypto = require("crypto"); | |
| const fs = require("fs"); | |
| const path = require("path"); | |
| const artifactsDir = path.join(process.cwd(), "artifacts"); | |
| const version = process.env.VERSION; | |
| const zipNames = fs | |
| .readdirSync(artifactsDir) | |
| .filter( | |
| (name) => | |
| name.startsWith(`hermes-desktop-${version}-`) && | |
| name.endsWith("-mac.zip"), | |
| ) | |
| .sort((a, b) => { | |
| if (a.includes("-x64-")) return -1; | |
| if (b.includes("-x64-")) return 1; | |
| return a.localeCompare(b); | |
| }); | |
| if ( | |
| zipNames.length < 2 || | |
| !zipNames.some((name) => name.includes("-x64-")) || | |
| !zipNames.some((name) => name.includes("-arm64-")) | |
| ) { | |
| throw new Error( | |
| `Expected x64 and arm64 macOS zips, found: ${zipNames.join(", ")}`, | |
| ); | |
| } | |
| const files = zipNames.map((name) => { | |
| const filePath = path.join(artifactsDir, name); | |
| return { | |
| url: name, | |
| sha512: crypto | |
| .createHash("sha512") | |
| .update(fs.readFileSync(filePath)) | |
| .digest("base64"), | |
| size: fs.statSync(filePath).size, | |
| }; | |
| }); | |
| const primary = files.find((file) => file.url.includes("-x64-")) || files[0]; | |
| const releaseDate = new Date().toISOString(); | |
| const lines = [ | |
| `version: ${version}`, | |
| "files:", | |
| ...files.flatMap((file) => [ | |
| ` - url: ${file.url}`, | |
| ` sha512: ${file.sha512}`, | |
| ` size: ${file.size}`, | |
| ]), | |
| `path: ${primary.url}`, | |
| `sha512: ${primary.sha512}`, | |
| `releaseDate: '${releaseDate}'`, | |
| "", | |
| ]; | |
| fs.writeFileSync(path.join(artifactsDir, "latest-mac.yml"), lines.join("\n")); | |
| NODE | |
| - name: Publish GitHub release | |
| uses: softprops/action-gh-release@v2 | |
| with: | |
| tag_name: ${{ needs.prepare.outputs.tag }} | |
| name: Hermes Desktop ${{ needs.prepare.outputs.tag }} | |
| generate_release_notes: true | |
| files: | | |
| artifacts/*.dmg | |
| artifacts/*.zip | |
| artifacts/*.AppImage | |
| artifacts/*.deb | |
| artifacts/*.rpm | |
| artifacts/*.exe | |
| artifacts/*.blockmap | |
| artifacts/latest.yml | |
| artifacts/latest-linux.yml | |
| artifacts/latest-mac.yml |