Skip to content

Version 0.16.6

Version 0.16.6 #158

Workflow file for this run

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 }}