Weekly Development Release #59
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: Nightly Development Release | |
| # Automated nightly builds for development channel testing | |
| # Runs at 2 AM UTC if there are changes since last development release | |
| # Keeps last 7 development releases, deletes older ones | |
| # Uses self-hosted runner like the release workflow | |
| on: | |
| schedule: | |
| # Run every day at 2 AM UTC if there are changes | |
| - cron: '0 2 * * *' | |
| workflow_dispatch: | |
| # Allow manual triggering | |
| env: | |
| XCODE_VERSION: '26.2' | |
| MACOS_VERSION: '14.0' | |
| permissions: | |
| contents: write | |
| jobs: | |
| check-changes: | |
| name: Check for Changes | |
| runs-on: ubuntu-latest | |
| outputs: | |
| should_build: ${{ steps.check.outputs.has_changes }} | |
| steps: | |
| - name: Checkout code | |
| uses: actions/checkout@v4 | |
| with: | |
| fetch-depth: 0 | |
| - name: Check for changes since last dev release | |
| id: check | |
| run: | | |
| # Get the latest development release tag | |
| LATEST_DEV_TAG=$(git tag -l '*-dev.*' --sort=-version:refname | head -1) | |
| if [ -z "$LATEST_DEV_TAG" ]; then | |
| echo "No previous development releases found" | |
| echo "has_changes=true" >> $GITHUB_OUTPUT | |
| exit 0 | |
| fi | |
| # Check if there are meaningful commits since the last dev release | |
| # Ignore appcast files which are auto-updated by the build process | |
| MEANINGFUL_CHANGES=$(git diff --name-only ${LATEST_DEV_TAG}..HEAD | grep -v -E '^appcast-dev' | wc -l) | |
| if [ "$MEANINGFUL_CHANGES" -gt 0 ]; then | |
| echo "Found meaningful changes since $LATEST_DEV_TAG (excluding appcast files)" | |
| git diff --name-only ${LATEST_DEV_TAG}..HEAD | grep -v -E '^appcast-dev' | sed 's/^/ - /' | |
| echo "has_changes=true" >> $GITHUB_OUTPUT | |
| else | |
| echo "No meaningful changes since $LATEST_DEV_TAG (only appcast updates)" | |
| echo "has_changes=false" >> $GITHUB_OUTPUT | |
| fi | |
| build-and-release: | |
| name: Build, Sign, and Release | |
| needs: check-changes | |
| if: needs.check-changes.outputs.should_build == 'true' | |
| runs-on: [self-hosted] | |
| steps: | |
| - name: Checkout code | |
| uses: actions/checkout@v4 | |
| with: | |
| fetch-depth: 0 | |
| submodules: recursive | |
| - name: Set up Xcode | |
| uses: maxim-lobanov/setup-xcode@v1 | |
| with: | |
| xcode-version: ${{ env.XCODE_VERSION }} | |
| - name: Download Metal Toolchain | |
| run: | | |
| echo "Downloading Metal Toolchain for Xcode ${{ env.XCODE_VERSION }}..." | |
| sudo xcodebuild -downloadComponent MetalToolchain || echo "Metal Toolchain already installed or download failed" | |
| - name: Increment development version | |
| run: | | |
| ./scripts/increment-dev-version.sh | |
| DEV_VERSION=$(cat Info.plist | grep -A1 CFBundleShortVersionString | tail -1 | sed 's/.*<string>\(.*\)<\/string>/\1/') | |
| echo "DEV_VERSION=$DEV_VERSION" >> $GITHUB_ENV | |
| echo "Building version: $DEV_VERSION" | |
| - name: Build development release | |
| run: make build-release | |
| - name: Validate Python Bundle | |
| run: ./scripts/validate_python_bundle.sh Release | |
| - name: Sign and notarize | |
| run: | | |
| if [ -f /Users/andrew/.sam_runner_env ]; then | |
| source /Users/andrew/.sam_runner_env | |
| fi | |
| make sign-only-release | |
| - name: Verify distribution files | |
| run: | | |
| if [ ! -f "dist/SAM-${DEV_VERSION}.dmg" ]; then | |
| echo "ERROR: DMG not found" | |
| exit 1 | |
| fi | |
| if [ ! -f "dist/SAM-${DEV_VERSION}.zip" ]; then | |
| echo "ERROR: ZIP not found" | |
| exit 1 | |
| fi | |
| echo "Distribution files ready:" | |
| ls -lh dist/SAM-${DEV_VERSION}.* | |
| - name: Update development appcast | |
| env: | |
| SPARKLE_PRIVATE_KEY: ${{ secrets.SPARKLE_PRIVATE_KEY }} | |
| run: | | |
| # Create temporary file for private key | |
| TEMP_KEY_FILE=$(mktemp) | |
| echo "$SPARKLE_PRIVATE_KEY" > "$TEMP_KEY_FILE" | |
| chmod 600 "$TEMP_KEY_FILE" | |
| # Update appcast-dev-items.xml with new development release | |
| ./scripts/update_dev_appcast.sh "${DEV_VERSION}" "dist/SAM-${DEV_VERSION}.zip" "$TEMP_KEY_FILE" | |
| # Generate appcast-dev.xml from items + stable releases | |
| ./scripts/generate-dev-appcast.sh | |
| # Clean up temporary key file | |
| rm -f "$TEMP_KEY_FILE" | |
| - name: Commit and push appcast changes | |
| run: | | |
| git config user.name "GitHub Actions" | |
| git config user.email "actions@github.com" | |
| git add appcast-dev-items.xml appcast-dev.xml | |
| if git diff --staged --quiet; then | |
| echo "No changes to development appcast files" | |
| else | |
| git commit -m "chore(dev-release): update appcast-dev.xml for v${DEV_VERSION}" | |
| git push origin HEAD:main | |
| fi | |
| - name: Create GitHub pre-release | |
| uses: softprops/action-gh-release@v2 | |
| with: | |
| tag_name: ${{ env.DEV_VERSION }} | |
| name: SAM ${{ env.DEV_VERSION }} (Development) | |
| body: | | |
| ⚠️ **This is a DEVELOPMENT pre-release build** | |
| Development builds are released automatically and may contain: | |
| - Bugs and instability | |
| - Incomplete features | |
| - Breaking changes | |
| **Do not use development builds for production work.** | |
| Enable development updates in SAM → Preferences → General → "Receive development updates" | |
| ### Changes Since Last Development Release | |
| See commit history for details. | |
| draft: false | |
| prerelease: true | |
| files: | | |
| dist/SAM-${{ env.DEV_VERSION }}.zip | |
| dist/SAM-${{ env.DEV_VERSION }}.dmg | |
| cleanup-old-releases: | |
| name: Cleanup Old Development Releases | |
| needs: build-and-release | |
| runs-on: ubuntu-latest | |
| steps: | |
| - name: Delete old development releases | |
| uses: actions/github-script@v7 | |
| with: | |
| script: | | |
| const KEEP_COUNT = 7; // Keep last 7 development releases | |
| // Get all releases | |
| const { data: releases } = await github.rest.repos.listReleases({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| }); | |
| // Filter for development pre-releases | |
| const devReleases = releases.filter(r => | |
| r.prerelease && r.tag_name.includes('-dev.') | |
| ); | |
| // Sort by created date (newest first) | |
| devReleases.sort((a, b) => | |
| new Date(b.created_at) - new Date(a.created_at) | |
| ); | |
| // Delete releases beyond KEEP_COUNT | |
| for (let i = KEEP_COUNT; i < devReleases.length; i++) { | |
| const release = devReleases[i]; | |
| console.log(`Deleting old development release: ${release.tag_name}`); | |
| // Delete the release | |
| await github.rest.repos.deleteRelease({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| release_id: release.id, | |
| }); | |
| // Delete the tag | |
| try { | |
| await github.rest.git.deleteRef({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| ref: `tags/${release.tag_name}`, | |
| }); | |
| } catch (error) { | |
| console.log(`Could not delete tag ${release.tag_name}: ${error.message}`); | |
| } | |
| } | |
| console.log(`Kept ${Math.min(KEEP_COUNT, devReleases.length)} development releases`); |