Version 0.16.6 #158
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: Release | |
| on: | |
| push: | |
| tags: | |
| - 'v*' # Triggers on version tags like v1.0.0 or v1.0.0-canary.1 | |
| jobs: | |
| release: | |
| runs-on: ${{ matrix.os }} | |
| strategy: | |
| matrix: | |
| os: [macos-latest, windows-latest, ubuntu-latest] | |
| steps: | |
| - name: Checkout code | |
| uses: actions/checkout@v4 | |
| - name: Determine release train | |
| id: train | |
| shell: bash | |
| run: | | |
| TAG_NAME="${GITHUB_REF#refs/tags/}" | |
| if [[ "$TAG_NAME" == *"-canary."* ]]; then | |
| echo "train=canary" >> $GITHUB_OUTPUT | |
| echo "bucket=${{ secrets.R2_CANARY_BUCKET_NAME }}" >> $GITHUB_OUTPUT | |
| echo "update_url=https://canary.flintnote.com" >> $GITHUB_OUTPUT | |
| else | |
| echo "train=production" >> $GITHUB_OUTPUT | |
| echo "bucket=${{ secrets.R2_PRODUCTION_BUCKET_NAME }}" >> $GITHUB_OUTPUT | |
| echo "update_url=https://updates.flintnote.com" >> $GITHUB_OUTPUT | |
| fi | |
| echo "Release train: $(cat $GITHUB_OUTPUT)" | |
| - name: Setup Node.js | |
| uses: actions/setup-node@v4 | |
| with: | |
| node-version: '20' | |
| cache: 'npm' | |
| - name: Install dependencies | |
| run: npm ci | |
| - name: Build application | |
| run: npm run build | |
| - name: Import Code Signing Certificate (macOS) | |
| if: matrix.os == 'macos-latest' | |
| env: | |
| BUILD_CERTIFICATE_BASE64: ${{ secrets.CSC_LINK }} | |
| P12_PASSWORD: ${{ secrets.CSC_KEY_PASSWORD }} | |
| KEYCHAIN_PASSWORD: ${{ secrets.KEYCHAIN_PASSWORD || 'temporary_password' }} | |
| run: | | |
| # Create variables | |
| CERTIFICATE_PATH=$RUNNER_TEMP/build_certificate.p12 | |
| KEYCHAIN_PATH=$RUNNER_TEMP/app-signing.keychain-db | |
| # Decode certificate | |
| echo -n "$BUILD_CERTIFICATE_BASE64" | base64 --decode -o $CERTIFICATE_PATH | |
| # Create temporary keychain | |
| security create-keychain -p "$KEYCHAIN_PASSWORD" $KEYCHAIN_PATH | |
| security set-keychain-settings -lut 21600 $KEYCHAIN_PATH | |
| security unlock-keychain -p "$KEYCHAIN_PASSWORD" $KEYCHAIN_PATH | |
| # Import certificate to temporary keychain | |
| security import $CERTIFICATE_PATH -P "$P12_PASSWORD" -A -t cert -f pkcs12 -k $KEYCHAIN_PATH | |
| security list-keychain -d user -s $KEYCHAIN_PATH | |
| # Set partition list | |
| security set-key-partition-list -S apple-tool:,apple:,codesign: -s -k "$KEYCHAIN_PASSWORD" $KEYCHAIN_PATH | |
| # Clean up certificate file | |
| rm $CERTIFICATE_PATH | |
| - name: Build Electron app (macOS) | |
| if: matrix.os == 'macos-latest' | |
| run: | | |
| if [ "${{ steps.train.outputs.train }}" == "canary" ]; then | |
| npm run build:mac:canary | |
| else | |
| npm run build:mac:production | |
| fi | |
| env: | |
| CSC_IDENTITY_AUTO_DISCOVERY: true | |
| - name: Notarize DMG (macOS) | |
| if: matrix.os == 'macos-latest' | |
| env: | |
| APPLE_ID: ${{ secrets.APPLE_ID }} | |
| APPLE_APP_SPECIFIC_PASSWORD: ${{ secrets.APPLE_ID_PASSWORD }} | |
| APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }} | |
| run: | | |
| # Find the DMG file | |
| DMG_FILE=$(find dist -name "*.dmg" -type f | head -n 1) | |
| if [ -z "$DMG_FILE" ]; then | |
| echo "No DMG file found" | |
| exit 1 | |
| fi | |
| echo "Found DMG: $DMG_FILE" | |
| echo "Submitting for notarization..." | |
| # Submit for notarization | |
| xcrun notarytool submit "$DMG_FILE" \ | |
| --apple-id "$APPLE_ID" \ | |
| --password "$APPLE_APP_SPECIFIC_PASSWORD" \ | |
| --team-id "$APPLE_TEAM_ID" \ | |
| --wait | |
| # Staple the notarization ticket | |
| echo "Stapling notarization ticket..." | |
| xcrun stapler staple "$DMG_FILE" | |
| # Verify stapling | |
| echo "Verifying staple..." | |
| xcrun stapler validate "$DMG_FILE" | |
| echo "Notarization complete!" | |
| - name: Build Electron app (Windows) | |
| if: matrix.os == 'windows-latest' | |
| shell: bash | |
| run: | | |
| if [ "${{ steps.train.outputs.train }}" == "canary" ]; then | |
| npm run build:win:canary | |
| else | |
| npm run build:win:production | |
| fi | |
| env: | |
| # Azure Trusted Signing credentials for build-time signing | |
| AZURE_TENANT_ID: ${{ secrets.AZURE_TENANT_ID }} | |
| AZURE_CLIENT_ID: ${{ secrets.AZURE_CLIENT_ID }} | |
| AZURE_CLIENT_SECRET: ${{ secrets.AZURE_CLIENT_SECRET }} | |
| AZURE_SIGNING_ENDPOINT: ${{ secrets.AZURE_SIGNING_ENDPOINT }} | |
| AZURE_SIGNING_ACCOUNT_NAME: ${{ secrets.AZURE_SIGNING_ACCOUNT_NAME }} | |
| AZURE_CERTIFICATE_PROFILE: ${{ secrets.AZURE_CERTIFICATE_PROFILE }} | |
| - name: Build Electron app (Linux) | |
| if: matrix.os == 'ubuntu-latest' | |
| run: | | |
| if [ "${{ steps.train.outputs.train }}" == "canary" ]; then | |
| npm run build:linux:canary | |
| else | |
| npm run build:linux:production | |
| fi | |
| - name: Configure R2 credentials | |
| shell: bash | |
| run: | | |
| mkdir -p ~/.aws | |
| cat > ~/.aws/credentials << EOF | |
| [r2] | |
| aws_access_key_id = ${{ secrets.R2_ACCESS_KEY_ID }} | |
| aws_secret_access_key = ${{ secrets.R2_SECRET_ACCESS_KEY }} | |
| EOF | |
| cat > ~/.aws/config << EOF | |
| [profile r2] | |
| region = auto | |
| endpoint_url = https://${{ secrets.CLOUDFLARE_ACCOUNT_ID }}.r2.cloudflarestorage.com | |
| EOF | |
| - name: Deploy to R2 | |
| shell: bash | |
| env: | |
| BUCKET_NAME: ${{ steps.train.outputs.bucket }} | |
| CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }} | |
| UPDATE_URL: ${{ steps.train.outputs.update_url }} | |
| RELEASE_TRAIN: ${{ steps.train.outputs.train }} | |
| run: | | |
| echo "Deploying $RELEASE_TRAIN release to R2 bucket: $BUCKET_NAME" | |
| echo "Update URL: $UPDATE_URL" | |
| # Upload installer files (DMG, ZIP, EXE, AppImage, blockmap files) | |
| for ext in dmg zip exe AppImage blockmap; do | |
| find dist -maxdepth 1 -name "*.$ext" -type f 2>/dev/null | while read -r file; do | |
| if [ -f "$file" ]; then | |
| filename=$(basename "$file") | |
| echo "Uploading $filename..." | |
| aws s3 cp "$file" "s3://$BUCKET_NAME/$filename" \ | |
| --profile r2 \ | |
| --endpoint-url "https://$CLOUDFLARE_ACCOUNT_ID.r2.cloudflarestorage.com" \ | |
| --acl public-read | |
| fi | |
| done | |
| done | |
| # Upload update metadata files with correct content type | |
| echo "Uploading update metadata files..." | |
| find dist -maxdepth 1 -name "*.yml" -type f 2>/dev/null | while read -r file; do | |
| if [ -f "$file" ]; then | |
| filename=$(basename "$file") | |
| echo "Uploading $filename with correct content type..." | |
| aws s3 cp "$file" "s3://$BUCKET_NAME/$filename" \ | |
| --profile r2 \ | |
| --endpoint-url "https://$CLOUDFLARE_ACCOUNT_ID.r2.cloudflarestorage.com" \ | |
| --content-type "text/yaml" \ | |
| --acl public-read | |
| fi | |
| done | |
| # Create "latest" copies for easy linking | |
| if [ "$RELEASE_TRAIN" == "canary" ]; then | |
| echo "Creating canary latest links..." | |
| # Find and copy DMG as latest | |
| DMG_FILE=$(find dist -maxdepth 1 -name "*.dmg" -type f | head -n 1) | |
| if [ -f "$DMG_FILE" ]; then | |
| echo "Copying DMG to flint-canary-latest-universal.dmg" | |
| aws s3 cp "$DMG_FILE" "s3://$BUCKET_NAME/flint-canary-latest-universal.dmg" \ | |
| --profile r2 \ | |
| --endpoint-url "https://$CLOUDFLARE_ACCOUNT_ID.r2.cloudflarestorage.com" \ | |
| --cache-control "no-cache, no-store, must-revalidate" \ | |
| --acl public-read | |
| fi | |
| # Find and copy EXE as latest | |
| EXE_FILE=$(find dist -maxdepth 1 -name "*.exe" -type f | head -n 1) | |
| if [ -f "$EXE_FILE" ]; then | |
| echo "Copying EXE to flint-canary-latest.exe" | |
| aws s3 cp "$EXE_FILE" "s3://$BUCKET_NAME/flint-canary-latest.exe" \ | |
| --profile r2 \ | |
| --endpoint-url "https://$CLOUDFLARE_ACCOUNT_ID.r2.cloudflarestorage.com" \ | |
| --cache-control "no-cache, no-store, must-revalidate" \ | |
| --acl public-read | |
| fi | |
| # Find and copy AppImage as latest | |
| APPIMAGE_FILE=$(find dist -maxdepth 1 -name "*.AppImage" -type f | head -n 1) | |
| if [ -f "$APPIMAGE_FILE" ]; then | |
| echo "Copying AppImage to flint-canary-latest.AppImage" | |
| aws s3 cp "$APPIMAGE_FILE" "s3://$BUCKET_NAME/flint-canary-latest.AppImage" \ | |
| --profile r2 \ | |
| --endpoint-url "https://$CLOUDFLARE_ACCOUNT_ID.r2.cloudflarestorage.com" \ | |
| --cache-control "no-cache, no-store, must-revalidate" \ | |
| --acl public-read | |
| fi | |
| else | |
| echo "Creating production latest links..." | |
| # Find and copy DMG as latest | |
| DMG_FILE=$(find dist -maxdepth 1 -name "*.dmg" -type f | head -n 1) | |
| if [ -f "$DMG_FILE" ]; then | |
| echo "Copying DMG to Flint-latest-universal.dmg" | |
| aws s3 cp "$DMG_FILE" "s3://$BUCKET_NAME/Flint-latest-universal.dmg" \ | |
| --profile r2 \ | |
| --endpoint-url "https://$CLOUDFLARE_ACCOUNT_ID.r2.cloudflarestorage.com" \ | |
| --cache-control "no-cache, no-store, must-revalidate" \ | |
| --acl public-read | |
| fi | |
| # Find and copy EXE as latest | |
| EXE_FILE=$(find dist -maxdepth 1 -name "*.exe" -type f | head -n 1) | |
| if [ -f "$EXE_FILE" ]; then | |
| echo "Copying EXE to Flint-latest.exe" | |
| aws s3 cp "$EXE_FILE" "s3://$BUCKET_NAME/Flint-latest.exe" \ | |
| --profile r2 \ | |
| --endpoint-url "https://$CLOUDFLARE_ACCOUNT_ID.r2.cloudflarestorage.com" \ | |
| --cache-control "no-cache, no-store, must-revalidate" \ | |
| --acl public-read | |
| fi | |
| # Find and copy AppImage as latest | |
| APPIMAGE_FILE=$(find dist -maxdepth 1 -name "*.AppImage" -type f | head -n 1) | |
| if [ -f "$APPIMAGE_FILE" ]; then | |
| echo "Copying AppImage to Flint-latest.AppImage" | |
| aws s3 cp "$APPIMAGE_FILE" "s3://$BUCKET_NAME/Flint-latest.AppImage" \ | |
| --profile r2 \ | |
| --endpoint-url "https://$CLOUDFLARE_ACCOUNT_ID.r2.cloudflarestorage.com" \ | |
| --cache-control "no-cache, no-store, must-revalidate" \ | |
| --acl public-read | |
| fi | |
| fi | |
| echo "✅ Deployment to $RELEASE_TRAIN R2 complete!" | |
| echo "Update files available at: $UPDATE_URL" | |
| create-release: | |
| needs: release | |
| runs-on: ubuntu-latest | |
| permissions: | |
| contents: write | |
| steps: | |
| - name: Checkout code | |
| uses: actions/checkout@v4 | |
| - name: Determine release info | |
| id: release_info | |
| shell: bash | |
| run: | | |
| TAG_NAME="${GITHUB_REF#refs/tags/}" | |
| if [[ "$TAG_NAME" == *"-canary."* ]]; then | |
| echo "update_url=https://canary.flintnote.com" >> $GITHUB_OUTPUT | |
| echo "train=canary" >> $GITHUB_OUTPUT | |
| else | |
| echo "update_url=https://updates.flintnote.com" >> $GITHUB_OUTPUT | |
| echo "train=production" >> $GITHUB_OUTPUT | |
| fi | |
| - name: Create GitHub Release | |
| uses: softprops/action-gh-release@v2 | |
| with: | |
| generate_release_notes: true | |
| draft: false | |
| prerelease: ${{ contains(github.ref, '-canary.') }} | |
| body: | | |
| ## Release Train: ${{ steps.release_info.outputs.train }} | |
| Download installers from: ${{ steps.release_info.outputs.update_url }} | |
| Auto-update URL: ${{ steps.release_info.outputs.update_url }} | |
| env: | |
| GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} |