This repository was archived by the owner on Jan 13, 2026. It is now read-only.
v0.16.0-dev #129
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 Release | |
| on: | |
| release: | |
| types: [published] | |
| permissions: | |
| contents: write | |
| id-token: write # required for OIDC role assumption | |
| jobs: | |
| build-mac: | |
| runs-on: macos-latest | |
| environment: ${{ github.event.release.target_commitish == 'main' && 'production' || 'develop' }} | |
| steps: | |
| - name: Checkout repository | |
| uses: actions/checkout@v4 | |
| - name: Set up Bun | |
| uses: oven-sh/setup-bun@v1 | |
| - name: Install dependencies | |
| run: bun install | |
| - name: Set up Rust | |
| uses: dtolnay/rust-toolchain@stable | |
| with: | |
| toolchain: stable | |
| targets: 'x86_64-apple-darwin,aarch64-apple-darwin' | |
| - name: Set up environment | |
| run: | | |
| echo "VITE_AUTH0_DOMAIN=\"${{ secrets.VITE_AUTH0_DOMAIN }}\"" >> .env | |
| echo "VITE_AUTH0_CLIENT_ID=\"${{ secrets.VITE_AUTH0_CLIENT_ID }}\"" >> .env | |
| echo "VITE_AUTH0_AUDIENCE=\"${{ secrets.VITE_AUTH0_AUDIENCE }}\"" >> .env | |
| echo "VITE_POSTHOG_API_KEY=\"${{ secrets.VITE_POSTHOG_API_KEY }}\"" >> .env | |
| echo "VITE_POSTHOG_HOST=\"${{ secrets.VITE_POSTHOG_HOST }}\"" >> .env | |
| echo "VITE_GRPC_BASE_URL=\"${{ vars.VITE_GRPC_BASE_URL }}\"" >> .env | |
| echo "VITE_UPDATER_BUCKET=\"${{ vars.VITE_UPDATER_BUCKET }}\"" >> .env | |
| echo "VITE_SENTRY_DSN=\"${{ vars.VITE_SENTRY_DSN }}\"" >> .env | |
| echo "VITE_SENTRY_ENV=\"${{ vars.VITE_SENTRY_ENV }}\"" >> .env | |
| echo "VITE_SENTRY_TRACES_SAMPLE_RATE=\"${{ vars.VITE_SENTRY_TRACES_SAMPLE_RATE }}\"" >> .env | |
| echo "VITE_SENTRY_PROFILES_SAMPLE_RATE=\"${{ vars.VITE_SENTRY_PROFILES_SAMPLE_RATE }}\"" >> .env | |
| echo "VITE_ITO_VERSION=\"${GITHUB_REF#refs/tags/v}\"" >> .env | |
| echo "ITO_ENV=\"${{ github.event.release.target_commitish == 'main' && 'prod' || 'dev' }}\"" >> .env | |
| echo "VITE_ITO_ENV=\"${{ github.event.release.target_commitish == 'main' && 'prod' || 'dev' }}\"" >> .env | |
| echo "APPLE_ID=\"${{ secrets.APPLE_ID }}\"" >> .env | |
| echo "APPLE_TEAM_ID=\"${{ secrets.APPLE_TEAM_ID }}\"" >> .env | |
| echo "APPLE_APP_SPECIFIC_PASSWORD=\"${{ secrets.APPLE_APP_SPECIFIC_PASSWORD }}\"" >> .env | |
| echo "CSC_LINK=\"release.p12\"" >> .env | |
| echo "CSC_KEY_PASSWORD=\"${{ secrets.MACOS_CERT_PASSWORD }}\"" >> .env | |
| echo "GH_TOKEN=\"${{ secrets.GITHUB_TOKEN }}\"" >> .env | |
| echo "GRPC_BASE_URL=\"${{ vars.GRPC_BASE_URL }}\"" >> .env | |
| echo "Created .env file:" | |
| cat .env | |
| - name: Decode and install certificate | |
| run: | | |
| echo "${{ secrets.MACOS_CERT_BASE64 }}" | base64 --decode > release.p12 | |
| - name: Build and package macOS application | |
| env: | |
| SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }} | |
| run: ./build-app.sh mac | |
| - name: Upload Mac Installer DMG | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: Ito-Mac-Installer | |
| path: dist/Ito-${{ github.event.release.target_commitish == 'main' && 'Installer' || 'dev-Installer' }}.dmg | |
| - name: Upload Mac Build Artifacts | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: Mac-Build-Artifacts | |
| path: | | |
| dist/*.yml | |
| dist/*universal-mac.zip | |
| dist/*universal-mac.zip.blockmap | |
| dist/*.dmg | |
| dist/*.dmg.blockmap | |
| build-windows-rust: | |
| runs-on: windows-latest | |
| environment: ${{ github.event.release.target_commitish == 'main' && 'production' || 'develop' }} | |
| steps: | |
| - name: Checkout repository | |
| uses: actions/checkout@v4 | |
| - name: Set up Rust with MSVC toolchain | |
| uses: dtolnay/rust-toolchain@stable | |
| with: | |
| toolchain: stable-x86_64-pc-windows-msvc | |
| targets: 'x86_64-pc-windows-msvc' | |
| - name: Build Rust native modules with MSVC | |
| shell: pwsh | |
| run: | | |
| # Find Visual Studio installation (GitHub Actions uses Enterprise) | |
| $vsPath = if (Test-Path "C:\Program Files\Microsoft Visual Studio\2022\Enterprise") { | |
| "C:\Program Files\Microsoft Visual Studio\2022\Enterprise" | |
| } elseif (Test-Path "C:\Program Files\Microsoft Visual Studio\2022\Community") { | |
| "C:\Program Files\Microsoft Visual Studio\2022\Community" | |
| } else { | |
| throw "Visual Studio 2022 not found" | |
| } | |
| Write-Host "Using Visual Studio at: $vsPath" | |
| # Launch VS Developer PowerShell and build | |
| & "$vsPath\Common7\Tools\Launch-VsDevShell.ps1" -Arch amd64 | |
| # Build each native module | |
| $modules = @("global-key-listener", "audio-recorder", "text-writer", "active-application", "selected-text-reader") | |
| foreach ($module in $modules) { | |
| Write-Host "Building $module with MSVC..." | |
| Set-Location "native\$module" | |
| cargo build --release --target x86_64-pc-windows-msvc | |
| Set-Location ..\.. | |
| } | |
| - name: Prepare artifacts directory | |
| shell: pwsh | |
| run: | | |
| New-Item -ItemType Directory -Force -Path rust-binaries | |
| $modules = @("global-key-listener", "audio-recorder", "text-writer", "active-application", "selected-text-reader") | |
| foreach ($module in $modules) { | |
| $sourcePath = "native\target\x86_64-pc-windows-msvc\release\$module.exe" | |
| $destPath = "rust-binaries\$module.exe" | |
| if (Test-Path $sourcePath) { | |
| Copy-Item $sourcePath $destPath | |
| Write-Host "Copied $module.exe" | |
| } else { | |
| Write-Error "$sourcePath not found!" | |
| exit 1 | |
| } | |
| } | |
| - name: Upload Rust binaries | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: Windows-Rust-Binaries | |
| path: rust-binaries/*.exe | |
| build-windows: | |
| runs-on: ubuntu-latest | |
| needs: build-windows-rust | |
| environment: ${{ github.event.release.target_commitish == 'main' && 'production' || 'develop' }} | |
| steps: | |
| - name: Checkout repository | |
| uses: actions/checkout@v4 | |
| - name: Download Rust binaries built with MSVC | |
| uses: actions/download-artifact@v4 | |
| with: | |
| name: Windows-Rust-Binaries | |
| path: rust-binaries-msvc | |
| - name: Place Rust binaries in expected locations | |
| run: | | |
| # Place binaries where electron-builder expects them | |
| for module in global-key-listener audio-recorder text-writer active-application selected-text-reader; do | |
| mkdir -p "native/target/x86_64-pc-windows-msvc/release" | |
| cp "rust-binaries-msvc/$module.exe" "native/target/x86_64-pc-windows-msvc/release/" | |
| echo "Placed $module.exe" | |
| done | |
| - name: Set up Bun | |
| uses: oven-sh/setup-bun@v1 | |
| - name: Install dependencies | |
| run: bun install | |
| - name: Set up environment | |
| run: | | |
| echo "VITE_AUTH0_DOMAIN=\"${{ secrets.VITE_AUTH0_DOMAIN }}\"" >> .env | |
| echo "VITE_AUTH0_CLIENT_ID=\"${{ secrets.VITE_AUTH0_CLIENT_ID }}\"" >> .env | |
| echo "VITE_AUTH0_AUDIENCE=\"${{ secrets.VITE_AUTH0_AUDIENCE }}\"" >> .env | |
| echo "VITE_POSTHOG_API_KEY=\"${{ secrets.VITE_POSTHOG_API_KEY }}\"" >> .env | |
| echo "VITE_POSTHOG_HOST=\"${{ secrets.VITE_POSTHOG_HOST }}\"" >> .env | |
| echo "VITE_GRPC_BASE_URL=\"${{ vars.VITE_GRPC_BASE_URL }}\"" >> .env | |
| echo "VITE_UPDATER_BUCKET=\"${{ vars.VITE_UPDATER_BUCKET }}\"" >> .env | |
| echo "VITE_SENTRY_DSN=\"${{ vars.VITE_SENTRY_DSN }}\"" >> .env | |
| echo "VITE_SENTRY_ENV=\"${{ vars.VITE_SENTRY_ENV }}\"" >> .env | |
| echo "VITE_SENTRY_TRACES_SAMPLE_RATE=\"${{ vars.VITE_SENTRY_TRACES_SAMPLE_RATE }}\"" >> .env | |
| echo "VITE_SENTRY_PROFILES_SAMPLE_RATE=\"${{ vars.VITE_SENTRY_PROFILES_SAMPLE_RATE }}\"" >> .env | |
| echo "VITE_ITO_VERSION=\"${GITHUB_REF#refs/tags/v}\"" >> .env | |
| echo "ITO_ENV=\"${{ github.event.release.target_commitish == 'main' && 'prod' || 'dev' }}\"" >> .env | |
| echo "VITE_ITO_ENV=\"${{ github.event.release.target_commitish == 'main' && 'prod' || 'dev' }}\"" >> .env | |
| echo "GH_TOKEN=\"${{ secrets.GITHUB_TOKEN }}\"" >> .env | |
| echo "GRPC_BASE_URL=\"${{ vars.GRPC_BASE_URL }}\"" >> .env | |
| echo "Created .env file:" | |
| cat .env | |
| - name: Build and package Windows application (unsigned) | |
| env: | |
| SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }} | |
| run: ./build-app.sh windows --skip-binaries | |
| - name: Upload unsigned Windows artifacts for signing | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: Windows-Unsigned-Artifacts | |
| path: | | |
| dist/*.exe | |
| dist/*.yml | |
| dist/*.nsis.7z | |
| dist/*.zip | |
| dist/*.blockmap | |
| sign-windows: | |
| runs-on: windows-latest | |
| needs: build-windows | |
| environment: ${{ github.event.release.target_commitish == 'main' && 'production' || 'develop' }} | |
| steps: | |
| - name: Download unsigned Windows artifacts | |
| uses: actions/download-artifact@v4 | |
| with: | |
| name: Windows-Unsigned-Artifacts | |
| path: dist | |
| - name: Sign Windows executable with Azure Trusted Signing | |
| uses: Azure/trusted-signing-action@v0 | |
| with: | |
| azure-tenant-id: ${{ secrets.AZURE_TENANT_ID }} | |
| azure-client-id: ${{ secrets.AZURE_CLIENT_ID }} | |
| azure-client-secret: ${{ secrets.AZURE_CLIENT_SECRET }} | |
| endpoint: https://eus.codesigning.azure.net/ | |
| trusted-signing-account-name: ${{ secrets.AZURE_CODE_SIGNING_ACCOUNT_NAME }} | |
| certificate-profile-name: ${{ secrets.AZURE_CODE_SIGNING_CERTIFICATE_PROFILE_NAME }} | |
| files-folder: dist | |
| files-folder-filter: exe | |
| file-digest: SHA256 | |
| timestamp-rfc3161: http://timestamp.acs.microsoft.com | |
| timestamp-digest: SHA256 | |
| - name: Regenerate metadata after signing | |
| shell: bash | |
| run: | | |
| # Find the signed executable file in the dist directory | |
| # This searches for any file matching the pattern "Ito-*.exe" | |
| SIGNED_EXE=$(find dist -name "Ito-*.exe" | head -1) | |
| if [ -z "$SIGNED_EXE" ]; then | |
| echo "Error: No signed executable found" | |
| exit 1 | |
| fi | |
| echo "Found signed executable: $SIGNED_EXE" | |
| # Calculate the SHA512 checksum of the signed file | |
| # cut -d' ' -f1 extracts just the hash part (before the filename) | |
| NEW_CHECKSUM=$(sha512sum "$SIGNED_EXE" | cut -d' ' -f1) | |
| # Convert hex checksum to base64 format (required by electron-updater) | |
| # xxd -r -p converts hex string to raw bytes | |
| # base64 -w 0 encodes to base64 without line wrapping | |
| NEW_CHECKSUM_B64=$(echo -n "$NEW_CHECKSUM" | xxd -r -p | base64 -w 0) | |
| # Get the file size in bytes using stat | |
| FILE_SIZE=$(stat -c%s "$SIGNED_EXE") | |
| # Extract just the filename from the full path | |
| FILENAME=$(basename "$SIGNED_EXE") | |
| echo "New checksum (hex): $NEW_CHECKSUM" | |
| echo "New checksum (base64): $NEW_CHECKSUM_B64" | |
| echo "File size: $FILE_SIZE" | |
| echo "Filename: $FILENAME" | |
| # Update the auto-updater metadata file with the signed file's information | |
| # This is critical because signing changes the file's checksum | |
| if [ -f "dist/latest.yml" ]; then | |
| echo "Updating latest.yml with new checksum after signing" | |
| # Update each field in latest.yml using sed (stream editor) | |
| # The | delimiter is used instead of / to avoid conflicts with filenames | |
| sed -i "s|url: .*|url: $FILENAME|" dist/latest.yml # Update download URL | |
| sed -i "s|sha512: .*|sha512: $NEW_CHECKSUM_B64|" dist/latest.yml # Update checksum | |
| sed -i "s|size: .*|size: $FILE_SIZE|" dist/latest.yml # Update file size | |
| sed -i "s|path: .*|path: $FILENAME|" dist/latest.yml # Update path | |
| echo "Updated latest.yml contents:" | |
| cat dist/latest.yml | |
| else | |
| echo "Warning: latest.yml not found in dist directory" | |
| ls -la dist/ | |
| fi | |
| - name: Upload signed Windows artifacts | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: Windows-Build-Artifacts | |
| path: | | |
| dist/*.yml | |
| dist/*.exe | |
| dist/*.nsis.7z | |
| dist/*.zip | |
| dist/*.blockmap | |
| upload-to-s3: | |
| runs-on: ubuntu-latest | |
| needs: [build-mac, sign-windows] | |
| environment: ${{ github.event.release.target_commitish == 'main' && 'production' || 'develop' }} | |
| steps: | |
| - name: Download Mac Build Artifacts | |
| uses: actions/download-artifact@v4 | |
| with: | |
| name: Mac-Build-Artifacts | |
| path: mac-dist | |
| - name: Download Windows Build Artifacts | |
| uses: actions/download-artifact@v4 | |
| with: | |
| name: Windows-Build-Artifacts | |
| path: windows-dist | |
| - name: Configure AWS credentials (OIDC) | |
| uses: aws-actions/configure-aws-credentials@v2 | |
| with: | |
| role-to-assume: arn:aws:iam::287641434880:role/ItoGitHubCiCdRole | |
| aws-region: us-west-2 | |
| - name: Upload Build Output to S3 | |
| run: | | |
| echo "Deploying version ${{ github.ref_name }} to S3" | |
| BUCKET=s3://${{ vars.AWS_STAGE }}-ito-releases/releases | |
| echo "Listing existing files in root of releases/" | |
| aws s3 ls "$BUCKET/" | grep -vE '/$' | awk '{print $4}' > existing_files.txt | |
| echo "Combining Mac and Windows artifacts" | |
| mkdir -p combined-dist | |
| # Copy Mac artifacts | |
| if [ -d "mac-dist" ]; then | |
| cp -r mac-dist/* combined-dist/ | |
| fi | |
| # Copy Windows artifacts | |
| if [ -d "windows-dist" ]; then | |
| cp -r windows-dist/* combined-dist/ | |
| fi | |
| echo "Files to upload:" | |
| ls -la combined-dist/ | |
| echo "Identifying which files to delete post-upload" | |
| find combined-dist -maxdepth 1 \( \ | |
| -name '*.yml' -o \ | |
| -name '*universal-mac.zip' -o \ | |
| -name '*universal-mac.zip.blockmap' -o \ | |
| -name '*.dmg' -o \ | |
| -name '*.dmg.blockmap' -o \ | |
| -name '*.exe' -o \ | |
| -name '*.nsis.7z' -o \ | |
| -name '*.zip' -o \ | |
| -name '*.blockmap' \ | |
| \) | xargs -I{} basename {} > uploaded_root_files.txt | |
| echo "Uploading full combined dist to versioned folder: $BUCKET/${{ github.ref_name }}/" | |
| aws s3 cp combined-dist $BUCKET/${{ github.ref_name }}/ --recursive | |
| echo "Uploading selected files to root of releases/" | |
| for FILE in \ | |
| $(find combined-dist -maxdepth 1 -name '*.yml') \ | |
| $(find combined-dist -maxdepth 1 -name '*universal-mac.zip') \ | |
| $(find combined-dist -maxdepth 1 -name '*universal-mac.zip.blockmap') \ | |
| $(find combined-dist -maxdepth 1 -name '*.dmg') \ | |
| $(find combined-dist -maxdepth 1 -name '*.dmg.blockmap') \ | |
| $(find combined-dist -maxdepth 1 -name '*.exe') \ | |
| $(find combined-dist -maxdepth 1 -name '*.nsis.7z') \ | |
| $(find combined-dist -maxdepth 1 -name '*.zip') \ | |
| $(find combined-dist -maxdepth 1 -name '*.blockmap') | |
| do | |
| if [ -f "$FILE" ]; then | |
| aws s3 cp "$FILE" $BUCKET/ | |
| fi | |
| done | |
| # Compare and find stale files (exist in bucket but not in upload list) | |
| # Exclude .blockmap files from deletion to preserve differential update capability | |
| echo "Existing files in bucket root:" | |
| cat existing_files.txt | |
| echo "" | |
| echo "Files uploaded to bucket root:" | |
| cat uploaded_root_files.txt | |
| echo "" | |
| echo "Computing stale files (existing - uploaded)..." | |
| comm -23 <(sort existing_files.txt) <(sort uploaded_root_files.txt) > all_stale_files.txt | |
| echo "All stale files before filtering:" | |
| cat all_stale_files.txt || echo "(none)" | |
| echo "" | |
| echo "Filtering out .blockmap files..." | |
| grep -v '\.blockmap$' all_stale_files.txt > stale_files.txt || echo "(no non-blockmap stale files found)" | |
| echo "Stale files identified for deletion (excluding .blockmap files for differential updates):" | |
| cat stale_files.txt || echo "(none)" | |
| # Delete each stale file (but preserve blockmaps) | |
| while IFS= read -r file; do | |
| echo "Deleting stale file: $file" | |
| aws s3 rm "$BUCKET/$file" | |
| done < stale_files.txt | |
| - name: Invalidate CDN Cache for Release Files | |
| run: | | |
| echo "Invalidating cache for release files on distribution: ${{ vars.CLOUDFRONT_DISTRIBUTION_ID }}. Updated files should be available within 15 minutes." | |
| aws cloudfront create-invalidation \ | |
| --distribution-id "${{ vars.CLOUDFRONT_DISTRIBUTION_ID }}" \ | |
| --paths "/*.yml" "/*.dmg" "/*.exe" "/*.zip" "/*.blockmap" | |
| - name: Upload installers to GitHub Release | |
| uses: softprops/action-gh-release@v1 | |
| with: | |
| tag_name: ${{ github.ref_name }} | |
| files: | | |
| mac-dist/*.dmg | |
| windows-dist/*.exe |