Repository navigation
Prepare 2.1.4 update #8
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*" | |
| workflow_dispatch: | |
| inputs: | |
| tag: | |
| description: Existing v* tag | |
| required: true | |
| type: string | |
| notarize: | |
| description: Submit the package to Apple notarization | |
| required: true | |
| default: true | |
| type: boolean | |
| permissions: | |
| contents: write | |
| concurrency: | |
| group: release-${{ inputs.tag || github.ref_name }} | |
| cancel-in-progress: false | |
| jobs: | |
| release: | |
| runs-on: macos-15 | |
| timeout-minutes: 360 | |
| steps: | |
| - uses: actions/checkout@v6 | |
| with: | |
| ref: ${{ inputs.tag || github.ref }} | |
| - name: Select Xcode | |
| run: sudo xcode-select -s /Applications/Xcode_26.3.app/Contents/Developer | |
| - name: Install XcodeGen | |
| run: brew install xcodegen | |
| - name: Check signing inputs | |
| env: | |
| MACOS_DEVELOPER_ID_P12_BASE64: ${{ secrets.MACOS_DEVELOPER_ID_P12_BASE64 }} | |
| MACOS_DEVELOPER_ID_P12_PASSWORD: ${{ secrets.MACOS_DEVELOPER_ID_P12_PASSWORD }} | |
| APP_STORE_CONNECT_KEY_ID: ${{ secrets.APP_STORE_CONNECT_KEY_ID }} | |
| APP_STORE_CONNECT_ISSUER_ID: ${{ secrets.APP_STORE_CONNECT_ISSUER_ID }} | |
| APP_STORE_CONNECT_PRIVATE_KEY_BASE64: ${{ secrets.APP_STORE_CONNECT_PRIVATE_KEY_BASE64 }} | |
| SPARKLE_PRIVATE_KEY: ${{ secrets.SPARKLE_PRIVATE_KEY }} | |
| run: | | |
| for name in \ | |
| MACOS_DEVELOPER_ID_P12_BASE64 \ | |
| MACOS_DEVELOPER_ID_P12_PASSWORD \ | |
| APP_STORE_CONNECT_KEY_ID \ | |
| APP_STORE_CONNECT_ISSUER_ID \ | |
| APP_STORE_CONNECT_PRIVATE_KEY_BASE64 \ | |
| SPARKLE_PRIVATE_KEY | |
| do | |
| if [[ -z "${!name}" ]]; then | |
| echo "Missing signing input: $name" >&2 | |
| exit 2 | |
| fi | |
| done | |
| - name: Import Developer ID certificate | |
| env: | |
| CERTIFICATE: ${{ secrets.MACOS_DEVELOPER_ID_P12_BASE64 }} | |
| CERTIFICATE_PASSWORD: ${{ secrets.MACOS_DEVELOPER_ID_P12_PASSWORD }} | |
| run: | | |
| CERTIFICATE_PATH="$RUNNER_TEMP/developer-id.p12" | |
| KEYCHAIN_PATH="$RUNNER_TEMP/signing.keychain-db" | |
| KEYCHAIN_PASSWORD="$(openssl rand -base64 32)" | |
| printf '%s' "$CERTIFICATE" | base64 --decode -o "$CERTIFICATE_PATH" | |
| security create-keychain -p "$KEYCHAIN_PASSWORD" "$KEYCHAIN_PATH" | |
| security set-keychain-settings -lut 21600 "$KEYCHAIN_PATH" | |
| security unlock-keychain -p "$KEYCHAIN_PASSWORD" "$KEYCHAIN_PATH" | |
| security import "$CERTIFICATE_PATH" \ | |
| -P "$CERTIFICATE_PASSWORD" \ | |
| -A -t cert -f pkcs12 \ | |
| -k "$KEYCHAIN_PATH" | |
| security set-key-partition-list \ | |
| -S apple-tool:,apple: \ | |
| -k "$KEYCHAIN_PASSWORD" \ | |
| "$KEYCHAIN_PATH" | |
| security list-keychain -d user -s "$KEYCHAIN_PATH" | |
| identity="$( | |
| security find-identity -v -p codesigning "$KEYCHAIN_PATH" \ | |
| | sed -n 's/.*"\(Developer ID Application:[^"]*\)".*/\1/p' | |
| )" | |
| identity_count="$(printf '%s\n' "$identity" | awk 'NF { count++ } END { print count + 0 }')" | |
| if [[ "$identity_count" -ne 1 ]]; then | |
| echo "Expected exactly one Developer ID Application identity." >&2 | |
| exit 2 | |
| fi | |
| echo "CODESIGN_IDENTITY=$identity" >> "$GITHUB_ENV" | |
| - name: Test | |
| run: | | |
| xcodegen generate | |
| xcodebuild test \ | |
| -project CodexTokenLedger.xcodeproj \ | |
| -scheme CodexTokenLedger \ | |
| -configuration Debug \ | |
| -destination 'platform=macOS' | |
| - name: Build and sign | |
| run: | | |
| ./scripts/build_app.sh | |
| ./scripts/package_release.sh | |
| codesign --verify --deep --strict --verbose=2 dist/CodexTokenLedger.app | |
| REQUIRE_DEVELOPER_ID=1 ./scripts/package_dmg.sh | |
| - name: Notarize and staple | |
| if: ${{ github.event_name == 'workflow_dispatch' && inputs.notarize }} | |
| env: | |
| KEY_ID: ${{ secrets.APP_STORE_CONNECT_KEY_ID }} | |
| ISSUER_ID: ${{ secrets.APP_STORE_CONNECT_ISSUER_ID }} | |
| PRIVATE_KEY: ${{ secrets.APP_STORE_CONNECT_PRIVATE_KEY_BASE64 }} | |
| run: | | |
| APP="dist/CodexTokenLedger.app" | |
| ZIP="dist/CodexTokenLedger-menu-bar-macOS.zip" | |
| DMG="dist/CodexTokenLedger-menu-bar-macOS.dmg" | |
| KEY_PATH="$RUNNER_TEMP/AuthKey_${KEY_ID}.p8" | |
| printf '%s' "$PRIVATE_KEY" | base64 --decode -o "$KEY_PATH" | |
| submission="$(xcrun notarytool submit "$DMG" \ | |
| --key "$KEY_PATH" \ | |
| --key-id "$KEY_ID" \ | |
| --issuer "$ISSUER_ID" \ | |
| --output-format json)" | |
| submission_id="$( | |
| printf '%s' "$submission" \ | |
| | /usr/bin/python3 -c 'import json,sys; print(json.load(sys.stdin)["id"])' | |
| )" | |
| echo "Notary submission: $submission_id" | |
| deadline=$((SECONDS + 18000)) | |
| while (( SECONDS < deadline )); do | |
| if info="$(xcrun notarytool info "$submission_id" \ | |
| --key "$KEY_PATH" \ | |
| --key-id "$KEY_ID" \ | |
| --issuer "$ISSUER_ID" \ | |
| --output-format json)" | |
| then | |
| status="$( | |
| printf '%s' "$info" \ | |
| | /usr/bin/python3 -c 'import json,sys; print(json.load(sys.stdin)["status"])' | |
| )" | |
| echo "Notary status: $status" | |
| case "$status" in | |
| Accepted) | |
| break | |
| ;; | |
| Invalid|Rejected) | |
| xcrun notarytool log "$submission_id" \ | |
| --key "$KEY_PATH" \ | |
| --key-id "$KEY_ID" \ | |
| --issuer "$ISSUER_ID" | |
| exit 1 | |
| ;; | |
| esac | |
| else | |
| echo "Notary status request failed; retrying." | |
| fi | |
| sleep 30 | |
| done | |
| if [[ "${status:-}" != "Accepted" ]]; then | |
| echo "Notarization did not finish before the release timeout." >&2 | |
| exit 1 | |
| fi | |
| xcrun stapler staple "$DMG" | |
| xcrun stapler validate "$DMG" | |
| xcrun stapler staple "$APP" | |
| xcrun stapler validate "$APP" | |
| spctl --assess --type execute --verbose=4 "$APP" | |
| rm "$ZIP" | |
| ditto -c -k --sequesterRsrc --keepParent "$APP" "$ZIP" | |
| shasum -a 256 "$DMG" "$ZIP" | sed 's#dist/##' > dist/SHA256SUMS.txt | |
| hdiutil verify "$DMG" | |
| unzip -t "$ZIP" >/dev/null | |
| - name: Validate Developer ID package | |
| if: ${{ github.event_name != 'workflow_dispatch' || !inputs.notarize }} | |
| run: | | |
| APP="dist/CodexTokenLedger.app" | |
| ZIP="dist/CodexTokenLedger-menu-bar-macOS.zip" | |
| DMG="dist/CodexTokenLedger-menu-bar-macOS.dmg" | |
| codesign --verify --deep --strict --verbose=2 "$APP" | |
| codesign --verify --verbose=2 "$DMG" | |
| hdiutil verify "$DMG" | |
| unzip -t "$ZIP" >/dev/null | |
| shasum -a 256 "$DMG" "$ZIP" | sed 's#dist/##' > dist/SHA256SUMS.txt | |
| - name: Prepare release notes | |
| env: | |
| TAG: ${{ inputs.tag || github.ref_name }} | |
| UNNOTARIZED: ${{ github.event_name != 'workflow_dispatch' || !inputs.notarize }} | |
| run: | | |
| SOURCE="release-notes/${TAG#v}.md" | |
| OUTPUT="$RUNNER_TEMP/release-notes.md" | |
| test -s "$SOURCE" | |
| if [[ "$UNNOTARIZED" == "true" ]]; then | |
| head -n 1 "$SOURCE" > "$OUTPUT" | |
| cat >> "$OUTPUT" <<'EOF' | |
| > 此安装包已使用 Developer ID 签名,尚未通过 Apple 公证。首次打开时 macOS 可能要求确认。 | |
| EOF | |
| tail -n +2 "$SOURCE" >> "$OUTPUT" | |
| else | |
| cp "$SOURCE" "$OUTPUT" | |
| fi | |
| echo "RELEASE_NOTES_PATH=$OUTPUT" >> "$GITHUB_ENV" | |
| - name: Generate update feed | |
| env: | |
| SPARKLE_PRIVATE_KEY: ${{ secrets.SPARKLE_PRIVATE_KEY }} | |
| TAG: ${{ inputs.tag || github.ref_name }} | |
| run: | | |
| APPCAST_DIR="$RUNNER_TEMP/appcast" | |
| ZIP="dist/CodexTokenLedger-menu-bar-macOS.zip" | |
| NOTES="$RELEASE_NOTES_PATH" | |
| mkdir -p "$APPCAST_DIR" | |
| test -s "$NOTES" | |
| cp "$ZIP" "$APPCAST_DIR/" | |
| cp "$NOTES" "$APPCAST_DIR/CodexTokenLedger-menu-bar-macOS.md" | |
| GENERATE_APPCAST="$( | |
| find build/DerivedData/SourcePackages/artifacts/sparkle \ | |
| -type f -name generate_appcast -perm -111 -print -quit | |
| )" | |
| if [[ -z "$GENERATE_APPCAST" ]]; then | |
| echo "Sparkle generate_appcast was not found." >&2 | |
| exit 2 | |
| fi | |
| printf '%s' "$SPARKLE_PRIVATE_KEY" | "$GENERATE_APPCAST" \ | |
| --ed-key-file - \ | |
| --download-url-prefix "https://github.com/Lincb522/CodexTokenLedger/releases/download/$TAG/" \ | |
| --embed-release-notes \ | |
| --maximum-versions 3 \ | |
| --maximum-deltas 0 \ | |
| -o "$GITHUB_WORKSPACE/dist/appcast.xml" \ | |
| "$APPCAST_DIR" | |
| test -s dist/appcast.xml | |
| - name: Publish release assets | |
| env: | |
| GH_TOKEN: ${{ github.token }} | |
| TAG: ${{ inputs.tag || github.ref_name }} | |
| run: | | |
| NOTES="$RELEASE_NOTES_PATH" | |
| test -s "$NOTES" | |
| if gh release view "$TAG" >/dev/null 2>&1; then | |
| gh release edit "$TAG" --notes-file "$NOTES" | |
| gh release upload "$TAG" \ | |
| dist/CodexTokenLedger-menu-bar-macOS.dmg \ | |
| dist/CodexTokenLedger-menu-bar-macOS.zip \ | |
| dist/appcast.xml \ | |
| dist/SHA256SUMS.txt \ | |
| --clobber | |
| else | |
| gh release create "$TAG" \ | |
| dist/CodexTokenLedger-menu-bar-macOS.dmg \ | |
| dist/CodexTokenLedger-menu-bar-macOS.zip \ | |
| dist/appcast.xml \ | |
| dist/SHA256SUMS.txt \ | |
| --verify-tag \ | |
| --notes-file "$NOTES" \ | |
| --title "Codex Token Ledger ${TAG#v}" | |
| fi |