Bump version to 0.9.8 #38
Workflow file for this run
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: Build and Deploy Executables | |
on: | |
push: | |
branches: [ prod ] | |
tags: [ 'v*' ] | |
workflow_dispatch: | |
permissions: | |
contents: write | |
packages: write | |
jobs: | |
build: | |
strategy: | |
matrix: | |
os: [ubuntu-latest, windows-latest, macos-latest] | |
include: | |
- os: ubuntu-latest | |
output_name: todoforai-edge-linux | |
- os: windows-latest | |
output_name: todoforai-edge.exe | |
- os: macos-latest | |
output_name: todoforai-edge-mac | |
runs-on: ${{ matrix.os }} | |
steps: | |
- uses: actions/checkout@v4 | |
- name: Set up Python | |
uses: actions/setup-python@v5 | |
with: | |
python-version: '3.10' | |
- name: Install dependencies | |
run: | | |
python -m pip install --upgrade pip | |
pip install -e . | |
pip install pyinstaller | |
- name: Build executable | |
run: python build_executable.py | |
# ─── Linux / macOS rename ────────────────────────────────────────────── | |
- name: Rename executable (Linux/Mac) | |
if: matrix.os != 'windows-latest' | |
run: mv dist/todoforai-edge dist/${{ matrix.output_name }} | |
shell: bash | |
# ─── 🍏 1. Import the certificate ─────────────────────────────────────── | |
- name: Import Code‑Signing Certificates (macOS) | |
C449 | if: matrix.os == 'macos-latest' |
uses: apple-actions/import-codesign-certs@v2 | |
with: | |
p12-file-base64: ${{ secrets.APPLE_CERTIFICATE }} | |
p12-password: ${{ secrets.APPLE_CERTIFICATE_PASSWORD }} | |
# ─── 🍏 2. Diagnostic step to show available identities ───────────────── | |
- name: Show available code‑signing identities (macOS) | |
if: matrix.os == 'macos-latest' | |
run: | | |
echo "::group::security find-identity" | |
security find-identity -v -p codesigning | |
echo "::endgroup::" | |
# ─── 🍏 3. Capture the identity that's really in the keychain ─────────── | |
- name: Detect first codesign identity (macOS) | |
id: detect-identity | |
if: matrix.os == 'macos-latest' | |
run: | | |
ID=$(security find-identity -v -p codesigning | awk 'NR==1{print $2}') | |
echo "IDENTITY_SHA=$ID" >> "$GITHUB_OUTPUT" | |
echo "Found identity SHA: $ID" | |
# ─── 🍏 4. Sign all dylibs first ───────────────────────────────────────── | |
- name: Sign dylibs in macOS executable | |
if: matrix.os == 'macos-latest' && steps.detect-identity.outputs.IDENTITY_SHA != '' | |
continue-on-error: true | |
run: | | |
# Find and sign all dylibs | |
find dist -name "*.dylib" -o -name "*.so" | while read -r file; do | |
echo "Signing $file" | |
/usr/bin/codesign --force --timestamp --options runtime \ | |
--sign "${{ steps.detect-identity.outputs.IDENTITY_SHA }}" \ | |
--entitlements mac.entitlements \ | |
"$file" -v | |
done | |
# ─── 🍏 5. Sign the main executable with entitlements ─────────────────── | |
- name: Sign macOS executable | |
if: matrix.os == 'macos-latest' && steps.detect-identity.outputs.IDENTITY_SHA != '' | |
continue-on-error: true | |
run: | | |
/usr/bin/codesign --force --timestamp --options runtime \ | |
--sign "${{ steps.detect-identity.outputs.IDENTITY_SHA }}" \ | |
--entitlements mac.entitlements \ | |
dist/${{ matrix.output_name }} -v | |
# ─── 🍏 6. Notarize & capture submission ID ─────────────────────────── | |
- name: Notarize macOS executable | |
id: notarize | |
if: matrix.os == 'macos-latest' && steps.detect-identity.outputs.IDENTITY_SHA != '' | |
continue-on-error: true | |
run: | | |
ditto -c -k --keepParent dist/${{ matrix.output_name }} dist/todoforai-edge-mac.zip | |
RESULT_JSON=$(xcrun notarytool submit dist/todoforai-edge-mac.zip \ | |
--apple-id "${{ secrets.APPLE_ID }}" \ | |
--password "${{ secrets.APPLE_PASSWORD }}" \ | |
--team-id "${{ secrets.APPLE_TEAM_ID }}" \ | |
--wait \ | |
--output-format json) | |
echo "$RESULT_JSON" > notarization-result.json | |
echo "NOTARIZATION_ID=$(echo "$RESULT_JSON" | jq -r '.id')" >> "$GITHUB_OUTPUT" | |
echo "NOTARIZATION_STATUS=$(echo "$RESULT_JSON" | jq -r '.status')" >> "$GITHUB_OUTPUT" | |
cat notarization-result.json | |
# ─── 🍏 7. Show notarization log ───────────────────────────────────── | |
- name: Show notarization log (macOS) | |
if: matrix.os == 'macos-latest' && steps.notarize.outputs.NOTARIZATION_ID != '' | |
run: | | |
# Install jq if needed | |
brew install jq || true | |
# Fetch the JSON log | |
xcrun notarytool log "${{ steps.notarize.outputs.NOTARIZATION_ID }}" \ | |
--apple-id "${{ secrets.APPLE_ID }}" \ | |
--password "${{ secrets.APPLE_PASSWORD }}" \ | |
--team-id "${{ secrets.APPLE_TEAM_ID }}" \ | |
--output-format json > notarization-log.json | |
echo "::group::Notarization Log" | |
# Pretty‑print the important bits for the workflow log | |
jq -r '.issues[] | "• \(.severity): \(.message) (\(.path))"' \ | |
notarization-log.json || echo "No issues key – probably rejected at upload time" | |
echo "::endgroup::" | |
# ─── 🍏 8. Staple if notarization succeeded ─────────────────────────── | |
- name: Staple notarization ticket (macOS) | |
if: matrix.os == 'macos-latest' && steps.notarize.outputs.NOTARIZATION_STATUS == 'Accepted' | |
run: | | |
xcrun stapler staple dist/${{ matrix.output_name }} | |
# Windows already produces the correct file name | |
- name: Upload executable as artifact | |
uses: actions/upload-artifact@v4 | |
with: | |
name: ${{ matrix.output_name }} | |
path: dist/${{ matrix.output_name }} | |
# ─────────────────────────── Deployment (unchanged) ─────────────────────── | |
deploy: | |
needs: build | |
runs-on: ubuntu-latest | |
if: startsWith(github.ref, 'refs/tags/v') || github.ref == 'refs/heads/prod' | |
steps: | |
- name: Download all artifacts | |
uses: actions/download-artifact@v4 | |
- name: Debug environment | |
run: | | |
# Check if secrets are available (without revealing them) | |
if [ -n "${{ secrets.R2_BUCKET_NAME }}" ]; then | |
echo "R2_BUCKET_NAME is set" | |
else | |
echo "R2_BUCKET_NAME is NOT set" | |
fi | |
if [ -n "${{ secrets.CLOUDFLARE_API_TOKEN }}" ]; then | |
echo "CLOUDFLARE_API_TOKEN is set" | |
else | |
echo "CLOUDFLARE_API_TOKEN is NOT set" | |
fi | |
if [ -n "${{ secrets.CLOUDFLARE_ACCOUNT_ID }}" ]; then | |
echo "CLOUDFLARE_ACCOUNT_ID is set" | |
else | |
echo "CLOUDFLARE_ACCOUNT_ID is NOT set" | |
fi | |
echo "::group::Artifacts" | |
ls -R | |
echo "::endgroup::" | |
- name: Install Cloudflare Wrangler | |
run: npm install -g wrangler | |
- name: Upload to Cloudflare R2 using Wrangler | |
env: | |
CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }} | |
CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }} | |
R2_BUCKET_NAME: ${{ secrets.R2_BUCKET_NAME }} | |
run: | | |
if [[ "$GITHUB_REF" == refs/tags/v* ]]; then | |
VERSION=${GITHUB_REF#refs/tags/v}; FOLDER="$VERSION" | |
else | |
FOLDER="latest" | |
fi | |
echo "Uploading to folder: $FOLDER" | |
for FILE in todoforai-edge-linux todoforai-edge.exe todoforai-edge-mac; do | |
wrangler r2 object put "$R2_BUCKET_NAME/edge/$FOLDER/$FILE" \ | |
--file "$FILE/$FILE" \ | |
--content-type application/octet-stream \ | |
--remote | |
done | |
if [[ "$GITHUB_REF" == refs/tags/v* ]]; then | |
for FILE in todoforai-edge-linux todoforai-edge.exe todoforai-edge-mac; do | |
wrangler r2 object put "$R2_BUCKET_NAME/edge/latest/$FILE" \ | |
--file "$FILE/$FILE" \ | |
--content-type application/octet-stream \ | |
--remote | |
done | |
fi | |
- name: Create GitHub Release | |
uses: softprops/action-gh-release@v2 | |
if: startsWith(github.ref, 'refs/tags/v') | |
with: | |
files: | | |
todoforai-edge-linux/todoforai-edge-linux | |
todoforai-edge.exe/todoforai-edge.exe | |
todoforai-edge-mac/todoforai-edge-mac |