@@ -3,6 +3,16 @@ name: Build Electron App
33
44on :
55 workflow_dispatch :
6+ inputs :
7+ arch :
8+ description : ' Architecture to build'
9+ required : true
10+ default : ' both'
11+ type : choice
12+ options :
13+ - arm64
14+ - x64
15+ - both
616
717jobs :
818 build-windows :
@@ -36,38 +46,180 @@ jobs:
3646
3747 build-macos :
3848 runs-on : macos-latest
49+ strategy :
50+ matrix :
51+ arch : ${{ github.event.inputs.arch == 'both' && fromJSON('["arm64", "x64"]') || fromJSON(format('["{0}"]', github.event.inputs.arch)) }}
52+
3953 steps :
54+ # ─── Checkout ─────────────────────────────────────────────
4055 - name : Checkout code
41- uses : actions/checkout@v3
56+ uses : actions/checkout@v4
4257
58+ # ─── Setup Node.js ────────────────────────────────────────
4359 - name : Setup Node.js
44- uses : actions/setup-node@v3
60+ uses : actions/setup-node@v4
4561 with :
46- node-version : ' 22'
62+ node-version : 22
63+ cache : npm
4764
65+ # ─── Setup Python (needed by some native deps) ────────────
4866 - name : Setup Python
49- uses : actions/setup-python@v4
67+ uses : actions/setup-python@v5
5068 with :
5169 python-version : ' 3.11'
5270
71+ # ─── Install Dependencies ─────────────────────────────────
5372 - name : Install dependencies
5473 run : npm ci
5574
56- - name : Install app dependencies
57- run : npx electron-builder install-app-deps
58-
59- - name : Build macOS app
60- run : npm run build:mac
75+ # ─── Import Code Signing Certificate ──────────────────────
76+ # This is the KEY step that makes CI signing work.
77+ # We create a temporary keychain, import the .p12 cert into it,
78+ # and set it as the default so codesign can find it.
79+ - name : Import code signing certificate
6180 env :
81+ MAC_CERTIFICATE_P12 : ${{ secrets.MAC_CERTIFICATE_P12 }}
82+ MAC_CERTIFICATE_PASSWORD : ${{ secrets.MAC_CERTIFICATE_PASSWORD }}
83+ run : |
84+ # Create a temporary keychain
85+ KEYCHAIN_PATH=$RUNNER_TEMP/build.keychain-db
86+ KEYCHAIN_PASSWORD=$(openssl rand -base64 32)
87+
88+ # Create and configure keychain
89+ security create-keychain -p "$KEYCHAIN_PASSWORD" "$KEYCHAIN_PATH"
90+ security set-keychain-settings -lut 21600 "$KEYCHAIN_PATH"
91+ security unlock-keychain -p "$KEYCHAIN_PASSWORD" "$KEYCHAIN_PATH"
92+
93+ # Decode and import certificate
94+ echo "$MAC_CERTIFICATE_P12" | base64 --decode > $RUNNER_TEMP/certificate.p12
95+ security import $RUNNER_TEMP/certificate.p12 \
96+ -k "$KEYCHAIN_PATH" \
97+ -P "$MAC_CERTIFICATE_PASSWORD" \
98+ -T /usr/bin/codesign \
99+ -T /usr/bin/security
100+
101+ # Allow codesign to access the keychain without UI prompt
102+ security set-key-partition-list -S apple-tool:,apple: -k "$KEYCHAIN_PASSWORD" "$KEYCHAIN_PATH"
103+
104+ # Add to keychain search path (makes it the default)
105+ security list-keychains -d user -s "$KEYCHAIN_PATH" $(security list-keychains -d user | tr -d '"')
106+
107+ # Verify the identity is available
108+ security find-identity -v -p codesigning "$KEYCHAIN_PATH"
109+
110+ # Clean up the .p12 file
111+ rm -f $RUNNER_TEMP/certificate.p12
112+
113+ # ─── Build Vite + Electron ────────────────────────────────
114+ - name : Build Vite + Electron
115+ run : npx tsc && npx vite build
116+
117+ # ─── Package with electron-builder ────────────────────────
118+ # electron-builder handles deep codesigning the .app bundle
119+ # "notarize: false" in electron-builder.json5 prevents it from
120+ # trying its own notarization flow
121+ - name : Package .app bundle
122+ run : npx electron-builder --mac --${{ matrix.arch }} --dir
123+ env :
124+ CSC_NAME : " Samir Patil (N26FZ4GW28)"
62125 GH_TOKEN : ${{ secrets.GITHUB_TOKEN }}
63126
64- - name : Upload macOS build
127+ # ─── Read version from package.json ───────────────────────
128+ - name : Get version
129+ id : version
130+ run : echo "version=$(node -p 'require(\"./package.json\").version')" >> $GITHUB_OUTPUT
131+
132+ # ─── Locate the .app bundle ───────────────────────────────
133+ - name : Find .app bundle
134+ id : find_app
135+ run : |
136+ VERSION="${{ steps.version.outputs.version }}"
137+ echo "=== Release directory contents ==="
138+ ls -laR "release/${VERSION}/" || echo "release/${VERSION}/ not found"
139+ echo "=== Searching for .app bundle ==="
140+ APP_BUNDLE=$(find "release/${VERSION}" -maxdepth 4 -name "*.app" -type d | head -n1)
141+ if [ -z "$APP_BUNDLE" ]; then
142+ echo "::error::No .app bundle found in release/${VERSION}/"
143+ exit 1
144+ fi
145+ echo "app_bundle=$APP_BUNDLE" >> $GITHUB_OUTPUT
146+ echo "Found: $APP_BUNDLE"
147+
148+ # ─── Verify .app signature ────────────────────────────────
149+ - name : Verify .app code signature
150+ run : codesign --verify --deep --strict "${{ steps.find_app.outputs.app_bundle }}"
151+
152+ # ─── Create DMG ───────────────────────────────────────────
153+ - name : Create DMG
154+ id : dmg
155+ run : |
156+ VERSION="${{ steps.version.outputs.version }}"
157+ ARCH="${{ matrix.arch }}"
158+ DMG_NAME="Openscreen-Mac-${ARCH}-${VERSION}.dmg"
159+ RELEASE_DIR="release/${VERSION}"
160+ DMG_OUTPUT="${RELEASE_DIR}/${DMG_NAME}"
161+ STAGING="${RELEASE_DIR}/dmg-staging"
162+
163+ mkdir -p "$STAGING"
164+ cp -R "${{ steps.find_app.outputs.app_bundle }}" "$STAGING/"
165+ ln -s /Applications "$STAGING/Applications"
166+
167+ hdiutil create \
168+ -srcfolder "$STAGING" \
169+ -volname "Openscreen" \
170+ -fs HFS+ \
171+ -fsargs "-c c=64,a=16,e=16" \
172+ -format UDBZ \
173+ "$DMG_OUTPUT"
174+
175+ rm -rf "$STAGING"
176+
177+ echo "dmg_path=$DMG_OUTPUT" >> $GITHUB_OUTPUT
178+ echo "dmg_name=$DMG_NAME" >> $GITHUB_OUTPUT
179+
180+ # ─── Sign DMG ─────────────────────────────────────────────
181+ - name : Sign DMG
182+ run : |
183+ codesign --force \
184+ --sign "Developer ID Application: Samir Patil (N26FZ4GW28)" \
185+ --timestamp \
186+ "${{ steps.dmg.outputs.dmg_path }}"
187+
188+ # ─── Notarize DMG ────────────────────────────────────────
189+ # On CI we can't use keychain profiles for notarytool, so we
190+ # pass credentials directly via env vars / flags
191+ - name : Notarize DMG
192+ run : |
193+ xcrun notarytool submit "${{ steps.dmg.outputs.dmg_path }}" \
194+ --apple-id "${{ secrets.APPLE_ID }}" \
195+ --team-id "${{ secrets.APPLE_TEAM_ID }}" \
196+ --password "${{ secrets.APPLE_APP_SPECIFIC_PASSWORD }}" \
197+ --wait
198+ timeout-minutes : 15
199+
200+ # ─── Staple ───────────────────────────────────────────────
201+ - name : Staple notarization ticket
202+ run : xcrun stapler staple "${{ steps.dmg.outputs.dmg_path }}"
203+
204+ # ─── Validate ─────────────────────────────────────────────
205+ - name : Validate stapled DMG
206+ run : |
207+ xcrun stapler validate "${{ steps.dmg.outputs.dmg_path }}"
208+ spctl -a -vv -t install "${{ steps.dmg.outputs.dmg_path }}"
209+
210+ # ─── Upload Artifact ──────────────────────────────────────
211+ - name : Upload notarized DMG
65212 uses : actions/upload-artifact@v4
66213 with :
67- name : macos-installer
68- path : release/**/* .dmg
214+ name : openscreen-mac-${{ matrix.arch }}
215+ path : ${{ steps .dmg.outputs.dmg_path }}
69216 retention-days : 30
70217
218+ # ─── Cleanup Keychain ─────────────────────────────────────
219+ - name : Cleanup keychain
220+ if : always()
221+ run : security delete-keychain $RUNNER_TEMP/build.keychain-db || true
222+
71223 build-linux :
72224 runs-on : ubuntu-latest
73225 steps :
97249 path : |
98250 release/**/*.AppImage
99251 release/**/*.zsync
252+ release/**/*.deb
100253 retention-days : 30
0 commit comments