fix: update README version badge to 1.3.0; protect manual release fro… #45
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: | |
| workflow_dispatch: | |
| push: | |
| branches: | |
| - "main" | |
| tags: | |
| - "v*" | |
| paths: | |
| - "VERSION" | |
| - "README.md" | |
| - "CHANGELOG.md" | |
| - "project.yml" | |
| - ".github/workflows/release.yml" | |
| - "Scripts/build-app.sh" | |
| - "Scripts/build-dmg.sh" | |
| - "Scripts/build-xcode-app.sh" | |
| - "Scripts/release-preflight.sh" | |
| - "Scripts/sign-and-notarize-dmg.sh" | |
| - "Scripts/verify-distribution.sh" | |
| - "Scripts/verify-installed-app.sh" | |
| - "Scripts/verify-privacy.sh" | |
| - "Scripts/bump-version.py" | |
| jobs: | |
| build-release: | |
| runs-on: macos-14 | |
| permissions: | |
| contents: write | |
| steps: | |
| - name: Check out repository | |
| uses: actions/checkout@v5 | |
| with: | |
| fetch-depth: 0 | |
| - name: Resolve release target | |
| id: release_meta | |
| env: | |
| BEFORE_SHA: ${{ github.event.before }} | |
| REF_TYPE: ${{ github.ref_type }} | |
| run: | | |
| VERSION="$(tr -d '[:space:]' < VERSION)" | |
| TAG="v$VERSION" | |
| SHOULD_RELEASE="true" | |
| REASON="manual dispatch or tag push" | |
| if [ "${GITHUB_EVENT_NAME}" = "push" ] && [ "${REF_TYPE}" != "tag" ]; then | |
| REASON="VERSION changed on main" | |
| if [ -n "${BEFORE_SHA:-}" ] && [ "${BEFORE_SHA}" != "0000000000000000000000000000000000000000" ] && git diff --quiet "$BEFORE_SHA" "$GITHUB_SHA" -- VERSION; then | |
| SHOULD_RELEASE="false" | |
| REASON="VERSION unchanged on branch push" | |
| fi | |
| fi | |
| echo "version=$VERSION" >> "$GITHUB_OUTPUT" | |
| echo "tag=$TAG" >> "$GITHUB_OUTPUT" | |
| echo "should_release=$SHOULD_RELEASE" >> "$GITHUB_OUTPUT" | |
| echo "reason=$REASON" >> "$GITHUB_OUTPUT" | |
| echo "Release decision: $SHOULD_RELEASE ($REASON)" | |
| - name: Select latest Xcode | |
| if: steps.release_meta.outputs.should_release == 'true' | |
| run: | | |
| latest_xcode="$(ls -d /Applications/Xcode*.app | sort -V | tail -n 1)" | |
| sudo xcode-select -s "$latest_xcode/Contents/Developer" | |
| xcodebuild -version | |
| - name: Install xcodegen | |
| if: steps.release_meta.outputs.should_release == 'true' | |
| run: brew install xcodegen | |
| - name: Run release preflight | |
| if: steps.release_meta.outputs.should_release == 'true' | |
| env: | |
| MSW_PREFLIGHT_SKIP_BUILDS: "1" | |
| run: | | |
| chmod +x Scripts/*.sh | |
| ./Scripts/release-preflight.sh | |
| - name: Prepare temporary keychain | |
| if: steps.release_meta.outputs.should_release == 'true' | |
| env: | |
| APPLE_CERTIFICATE_P12_BASE64: ${{ secrets.APPLE_CERTIFICATE_P12_BASE64 }} | |
| APPLE_CERTIFICATE_PASSWORD: ${{ secrets.APPLE_CERTIFICATE_PASSWORD }} | |
| run: | | |
| if [ -z "${APPLE_CERTIFICATE_P12_BASE64}" ] || [ -z "${APPLE_CERTIFICATE_PASSWORD}" ]; then | |
| echo "missing signing certificate secrets" >&2 | |
| exit 1 | |
| fi | |
| KEYCHAIN_PASSWORD="$(openssl rand -base64 24)" | |
| echo "KEYCHAIN_PASSWORD=$KEYCHAIN_PASSWORD" >> "$GITHUB_ENV" | |
| echo "KEYCHAIN_PATH=$RUNNER_TEMP/modelswitchboard.keychain-db" >> "$GITHUB_ENV" | |
| security create-keychain -p "$KEYCHAIN_PASSWORD" "$RUNNER_TEMP/modelswitchboard.keychain-db" | |
| security set-keychain-settings -lut 21600 "$RUNNER_TEMP/modelswitchboard.keychain-db" | |
| security unlock-keychain -p "$KEYCHAIN_PASSWORD" "$RUNNER_TEMP/modelswitchboard.keychain-db" | |
| echo "$APPLE_CERTIFICATE_P12_BASE64" | base64 --decode > "$RUNNER_TEMP/developer-id.p12" | |
| security import "$RUNNER_TEMP/developer-id.p12" \ | |
| -k "$RUNNER_TEMP/modelswitchboard.keychain-db" \ | |
| -P "$APPLE_CERTIFICATE_PASSWORD" \ | |
| -T /usr/bin/codesign \ | |
| -T /usr/bin/security | |
| security list-keychains -d user -s "$RUNNER_TEMP/modelswitchboard.keychain-db" login.keychain-db | |
| security set-key-partition-list -S apple-tool:,apple: -k "$KEYCHAIN_PASSWORD" "$RUNNER_TEMP/modelswitchboard.keychain-db" | |
| - name: Write App Store Connect API key | |
| if: steps.release_meta.outputs.should_release == 'true' | |
| env: | |
| APPLE_NOTARY_API_KEY_P8_BASE64: ${{ secrets.APPLE_NOTARY_API_KEY_P8_BASE64 }} | |
| run: | | |
| if [ -z "${APPLE_NOTARY_API_KEY_P8_BASE64}" ]; then | |
| echo "missing notary API key secret" >&2 | |
| exit 1 | |
| fi | |
| API_KEY_PATH="$RUNNER_TEMP/AuthKey.p8" | |
| echo "$APPLE_NOTARY_API_KEY_P8_BASE64" | base64 --decode > "$API_KEY_PATH" | |
| echo "APPLE_NOTARY_API_KEY_PATH=$API_KEY_PATH" >> "$GITHUB_ENV" | |
| - name: Build, sign, and notarize DMGs | |
| if: steps.release_meta.outputs.should_release == 'true' | |
| env: | |
| APPLE_DEVELOPER_IDENTITY: ${{ secrets.APPLE_DEVELOPER_IDENTITY }} | |
| APPLE_NOTARY_API_KEY_PATH: ${{ env.APPLE_NOTARY_API_KEY_PATH }} | |
| APPLE_NOTARY_API_KEY_ID: ${{ secrets.APPLE_NOTARY_API_KEY_ID }} | |
| APPLE_NOTARY_API_ISSUER_ID: ${{ secrets.APPLE_NOTARY_API_ISSUER_ID }} | |
| run: | | |
| chmod +x Scripts/*.sh | |
| for variant in base plus; do | |
| APP_VARIANT="$variant" ./Scripts/sign-and-notarize-dmg.sh | |
| APP_VARIANT="$variant" ./Scripts/verify-distribution.sh | |
| done | |
| shasum -a 256 "dist/Model-Switchboard-$(tr -d '[:space:]' < VERSION).dmg" > "dist/Model-Switchboard-$(tr -d '[:space:]' < VERSION).dmg.sha256" | |
| shasum -a 256 "dist/Model-Switchboard-Plus-$(tr -d '[:space:]' < VERSION).dmg" > "dist/Model-Switchboard-Plus-$(tr -d '[:space:]' < VERSION).dmg.sha256" | |
| - name: Upload release assets | |
| if: steps.release_meta.outputs.should_release == 'true' | |
| env: | |
| GH_TOKEN: ${{ github.token }} | |
| RELEASE_TAG: ${{ steps.release_meta.outputs.tag }} | |
| RELEASE_VERSION: ${{ steps.release_meta.outputs.version }} | |
| REF_TYPE: ${{ github.ref_type }} | |
| run: | | |
| VERSION="$RELEASE_VERSION" | |
| TAG="$RELEASE_TAG" | |
| FILES=( | |
| "dist/Model-Switchboard-$VERSION.dmg" | |
| "dist/Model-Switchboard-Plus-$VERSION.dmg" | |
| "dist/Model-Switchboard-$VERSION.dmg.sha256" | |
| "dist/Model-Switchboard-Plus-$VERSION.dmg.sha256" | |
| ) | |
| for file in "${FILES[@]}"; do | |
| if [ ! -f "$file" ]; then | |
| echo "missing release artifact: $file" >&2 | |
| exit 1 | |
| fi | |
| done | |
| if gh release view "$TAG" >/dev/null 2>&1; then | |
| # A release for this tag already exists. If it already carries DMG | |
| # assets, it was published manually (verified + stapled locally) and | |
| # must not be overwritten by CI-built artifacts. Skip the upload so | |
| # the verified manual release is preserved; only upload assets that | |
| # are genuinely missing. | |
| EXISTING_ASSETS="$(gh release view "$TAG" --json assets --jq '[.assets[].name] | join(" ")')" | |
| UPLOAD_ARGS=() | |
| for file in "${FILES[@]}"; do | |
| base="$(basename "$file")" | |
| case " $EXISTING_ASSETS " in | |
| *" $base "*) echo "skip existing asset: $base" ;; | |
| *) UPLOAD_ARGS+=("$file") ;; | |
| esac | |
| done | |
| if [ "${#UPLOAD_ARGS[@]}" -gt 0 ]; then | |
| gh release upload "$TAG" "${UPLOAD_ARGS[@]}" | |
| else | |
| echo "release $TAG already has all assets; preserving verified manual release (no --clobber)" | |
| fi | |
| else | |
| CREATE_ARGS=("$TAG" "${FILES[@]}" --title "$TAG" --generate-notes) | |
| if [ "$REF_TYPE" != "tag" ]; then | |
| CREATE_ARGS+=(--target "$GITHUB_SHA") | |
| fi | |
| gh release create "${CREATE_ARGS[@]}" | |
| fi |