diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..1900983 --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,122 @@ +name: Release + +on: + push: + tags: + - "v*" + +permissions: + contents: write + +jobs: + release: + runs-on: macos-14 + timeout-minutes: 60 + + steps: + - uses: actions/checkout@v4 + + - name: Resolve release metadata + id: meta + env: + MACOS_CERTIFICATE_P12: ${{ secrets.MACOS_CERTIFICATE_P12 }} + MACOS_CERTIFICATE_PASSWORD: ${{ secrets.MACOS_CERTIFICATE_PASSWORD }} + MACOS_SIGNING_IDENTITY: ${{ secrets.MACOS_SIGNING_IDENTITY }} + APPLE_ID: ${{ secrets.APPLE_ID }} + APPLE_APP_SPECIFIC_PASSWORD: ${{ secrets.APPLE_APP_SPECIFIC_PASSWORD }} + APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }} + run: | + VERSION="${GITHUB_REF_NAME#v}" + if [ -z "$VERSION" ]; then + echo "Unable to derive version from tag: $GITHUB_REF_NAME" >&2 + exit 1 + fi + + HAS_SIGNING="false" + HAS_NOTARIZATION="false" + + if [ -n "$MACOS_CERTIFICATE_P12" ] && [ -n "$MACOS_CERTIFICATE_PASSWORD" ] && [ -n "$MACOS_SIGNING_IDENTITY" ]; then + HAS_SIGNING="true" + fi + + if [ -n "$APPLE_ID" ] && [ -n "$APPLE_APP_SPECIFIC_PASSWORD" ] && [ -n "$APPLE_TEAM_ID" ]; then + HAS_NOTARIZATION="true" + fi + + echo "app_version=$VERSION" >> "$GITHUB_OUTPUT" + echo "archive_name=VoiceMemo-$VERSION-macos.zip" >> "$GITHUB_OUTPUT" + echo "has_signing=$HAS_SIGNING" >> "$GITHUB_OUTPUT" + echo "has_notarization=$HAS_NOTARIZATION" >> "$GITHUB_OUTPUT" + + - name: Prepare signing keychain + if: steps.meta.outputs.has_signing == 'true' + env: + MACOS_CERTIFICATE_P12: ${{ secrets.MACOS_CERTIFICATE_P12 }} + MACOS_CERTIFICATE_PASSWORD: ${{ secrets.MACOS_CERTIFICATE_PASSWORD }} + MACOS_KEYCHAIN_PASSWORD: ${{ secrets.MACOS_KEYCHAIN_PASSWORD }} + run: | + CERT_PATH="$RUNNER_TEMP/signing_certificate.p12" + KEYCHAIN_PATH="$RUNNER_TEMP/voicememo-signing.keychain-db" + KEYCHAIN_PASSWORD="${MACOS_KEYCHAIN_PASSWORD:-$MACOS_CERTIFICATE_PASSWORD}" + + printf '%s' "$MACOS_CERTIFICATE_P12" | base64 -D -o "$CERT_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 "$CERT_PATH" -k "$KEYCHAIN_PATH" -P "$MACOS_CERTIFICATE_PASSWORD" -T /usr/bin/codesign -T /usr/bin/security -T /usr/bin/xcrun + security list-keychains -d user -s "$KEYCHAIN_PATH" + security default-keychain -d user -s "$KEYCHAIN_PATH" + security set-key-partition-list -S apple-tool:,apple:,codesign: -s -k "$KEYCHAIN_PASSWORD" "$KEYCHAIN_PATH" + + - name: Run tests + run: swift test -v + + - name: Package release archive + env: + APP_VERSION: ${{ steps.meta.outputs.app_version }} + BUILD_NUMBER: ${{ github.run_number }} + ARCHIVE_NAME: ${{ steps.meta.outputs.archive_name }} + HAS_SIGNING: ${{ steps.meta.outputs.has_signing }} + HAS_NOTARIZATION: ${{ steps.meta.outputs.has_notarization }} + MACOS_SIGNING_IDENTITY: ${{ secrets.MACOS_SIGNING_IDENTITY }} + APPLE_ID: ${{ secrets.APPLE_ID }} + APPLE_APP_SPECIFIC_PASSWORD: ${{ secrets.APPLE_APP_SPECIFIC_PASSWORD }} + APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }} + run: | + SIGN_IDENTITY="-" + NOTARIZE="false" + + if [ "$HAS_SIGNING" = "true" ]; then + SIGN_IDENTITY="$MACOS_SIGNING_IDENTITY" + fi + + if [ "$HAS_SIGNING" = "true" ] && [ "$HAS_NOTARIZATION" = "true" ]; then + NOTARIZE="true" + fi + + APP_VERSION="$APP_VERSION" \ + BUILD_NUMBER="$BUILD_NUMBER" \ + ARCHIVE_NAME="$ARCHIVE_NAME" \ + SIGN_IDENTITY="$SIGN_IDENTITY" \ + NOTARIZE="$NOTARIZE" \ + APPLE_ID="$APPLE_ID" \ + APPLE_APP_SPECIFIC_PASSWORD="$APPLE_APP_SPECIFIC_PASSWORD" \ + APPLE_TEAM_ID="$APPLE_TEAM_ID" \ + bash ./package_app.sh + + - name: Upload workflow artifact + uses: actions/upload-artifact@v4 + with: + name: VoiceMemo-${{ steps.meta.outputs.app_version }} + path: | + dist/${{ steps.meta.outputs.archive_name }} + dist/${{ steps.meta.outputs.archive_name }}.sha256 + + - name: Publish GitHub release + uses: softprops/action-gh-release@v2 + with: + generate_release_notes: true + files: | + dist/${{ steps.meta.outputs.archive_name }} + dist/${{ steps.meta.outputs.archive_name }}.sha256 diff --git a/.gitignore b/.gitignore index 8dcc25b..e835ce5 100644 --- a/.gitignore +++ b/.gitignore @@ -10,6 +10,7 @@ Packages.resolved .DS_Store *.app/ *.dSYM/ +dist/ .DocumentRevisions-V100 .fseventsd .Spotlight-V100 diff --git a/README.md b/README.md index 293c730..a1156ac 100644 --- a/README.md +++ b/README.md @@ -88,7 +88,7 @@ flowchart LR - `Sources/`: Core Swift implementation. - `Package.swift`: Swift Package Manager configuration. -- `package_app.sh`: Automated build and ad-hoc signing script. +- `package_app.sh`: Build, sign, notarize (optional), and archive the macOS app bundle. - `Info.plist`: Application configuration and permission strings. ## Getting Started @@ -100,9 +100,15 @@ Due to macOS security requirements (ScreenCaptureKit needs specific entitlements ```bash chmod +x package_app.sh ./package_app.sh -open VoiceMemo.app +open dist/VoiceMemo.app ``` +Artifacts are written to `dist/` by default: + +- `dist/VoiceMemo.app` +- `dist/VoiceMemo--macos.zip` +- `dist/VoiceMemo--macos.zip.sha256` + ### 2. Permissions When you first start recording, macOS will request the following permissions: @@ -171,6 +177,26 @@ xed VoiceMemo.xcodeproj Ensure you configure **Signing & Capabilities** with your Development Team to run the app with full permissions. +## Release + +Tag-driven releases are handled by [`.github/workflows/release.yml`](.github/workflows/release.yml). Pushing a tag like `v1.2.3` will: + +- run `swift test` +- build a versioned macOS app archive +- create a GitHub Release and upload the zip plus SHA-256 checksum + +If the following GitHub Actions secrets are configured, the workflow signs with Developer ID and notarizes the archive. Otherwise it falls back to ad-hoc signing for internal/testing distribution: + +- `MACOS_CERTIFICATE_P12` +- `MACOS_CERTIFICATE_PASSWORD` +- `MACOS_SIGNING_IDENTITY` +- `MACOS_KEYCHAIN_PASSWORD` (optional, defaults to the certificate password) +- `APPLE_ID` +- `APPLE_APP_SPECIFIC_PASSWORD` +- `APPLE_TEAM_ID` + +For signing and notarization details, see [Permissions and Signing](doc/05-permissions-and-signing.md). + ## Roadmap ### Completed diff --git a/README_CN.md b/README_CN.md index a38286c..2614c6b 100644 --- a/README_CN.md +++ b/README_CN.md @@ -88,7 +88,7 @@ flowchart LR - `Sources/`:核心 Swift 代码实现。 - `Package.swift`:Swift Package Manager 项目配置。 -- `package_app.sh`:自动化编译及 Ad-hoc 签名脚本。 +- `package_app.sh`:用于构建、签名、可选公证并归档 macOS 应用包。 - `Info.plist`:应用配置及权限描述。 ## 快速开始 @@ -100,9 +100,15 @@ flowchart LR ```bash chmod +x package_app.sh ./package_app.sh -open VoiceMemo.app +open dist/VoiceMemo.app ``` +默认产物会输出到 `dist/`: + +- `dist/VoiceMemo.app` +- `dist/VoiceMemo--macos.zip` +- `dist/VoiceMemo--macos.zip.sha256` + ### 2. 权限说明 首次开始录制时,macOS 会请求以下权限: @@ -170,6 +176,26 @@ xed VoiceMemo.xcodeproj 请务必在 **Signing & Capabilities** 中配置您的开发团队(Team),以便应用能以完整权限运行。 +## 发布 + +基于 tag 的自动发布由 [`.github/workflows/release.yml`](.github/workflows/release.yml) 负责。推送 `v1.2.3` 这类 tag 时,会自动: + +- 执行 `swift test` +- 构建带版本号的 macOS 应用归档 +- 创建 GitHub Release 并上传 zip 与 SHA-256 校验文件 + +如果配置了以下 GitHub Actions secrets,workflow 会使用 Developer ID 签名并执行 notarization;如果没有配置,则回退到 ad-hoc 签名,适合内部或测试分发: + +- `MACOS_CERTIFICATE_P12` +- `MACOS_CERTIFICATE_PASSWORD` +- `MACOS_SIGNING_IDENTITY` +- `MACOS_KEYCHAIN_PASSWORD`(可选,默认使用证书密码) +- `APPLE_ID` +- `APPLE_APP_SPECIFIC_PASSWORD` +- `APPLE_TEAM_ID` + +签名和公证的细节请参阅 [权限与签名说明](doc/05-permissions-and-signing.zh-CN.md)。 + ## 开发路线图 ### 已完成 diff --git a/Sources/VoiceMemo/Info.plist b/Sources/VoiceMemo/Info.plist index 297cc01..f4e6501 100644 --- a/Sources/VoiceMemo/Info.plist +++ b/Sources/VoiceMemo/Info.plist @@ -16,6 +16,8 @@ APPL CFBundleShortVersionString 1.0 + CFBundleVersion + 1 LSMinimumSystemVersion 13.0 NSHighResolutionCapable diff --git a/Tests/VoiceMemoTests/OSSUploadTests.swift b/Tests/VoiceMemoTests/OSSUploadTests.swift index 2ce3093..7575c5e 100644 --- a/Tests/VoiceMemoTests/OSSUploadTests.swift +++ b/Tests/VoiceMemoTests/OSSUploadTests.swift @@ -20,6 +20,7 @@ final class OSSUploadTests: XCTestCase { } let settings = SettingsStore() + settings.useKeychain = false settings.ossRegion = ossRegion settings.ossEndpoint = ossEndpoint settings.ossBucket = ossBucket diff --git a/Tests/VoiceMemoTests/TingwuCreateTaskTests.swift b/Tests/VoiceMemoTests/TingwuCreateTaskTests.swift index fba9750..4f532db 100644 --- a/Tests/VoiceMemoTests/TingwuCreateTaskTests.swift +++ b/Tests/VoiceMemoTests/TingwuCreateTaskTests.swift @@ -16,6 +16,7 @@ final class TingwuCreateTaskTests: XCTestCase { } let settings = SettingsStore() + settings.useKeychain = false settings.tingwuAppKey = appKey settings.saveTingwuAccessKeyId(accessKeyId) settings.saveTingwuAccessKeySecret(accessKeySecret) @@ -35,6 +36,7 @@ final class TingwuCreateTaskTests: XCTestCase { // specifically checking that Summarization and MeetingAssistance parameters are present. let settings = SettingsStore() + settings.useKeychain = false settings.tingwuAppKey = "TEST_APP_KEY" settings.saveTingwuAccessKeyId("TEST_AK_ID") settings.saveTingwuAccessKeySecret("TEST_AK_SECRET") diff --git a/Tests/VoiceMemoTests/VolcengineTests.swift b/Tests/VoiceMemoTests/VolcengineTests.swift index 578ffaf..ae50829 100644 --- a/Tests/VoiceMemoTests/VolcengineTests.swift +++ b/Tests/VoiceMemoTests/VolcengineTests.swift @@ -19,6 +19,7 @@ final class VolcengineTests: XCTestCase { func testCreateTaskRequestConstruction() throws { let settings = SettingsStore() + settings.useKeychain = false settings.volcAppId = "TEST_APP_ID" settings.volcResourceId = "volc.auc.common" settings.enableRoleSplit = false // Ensure it works even if setting is false @@ -62,6 +63,7 @@ final class VolcengineTests: XCTestCase { func testQueryRequestConstruction() throws { let settings = SettingsStore() + settings.useKeychain = false settings.volcAppId = "TEST_APP_ID" settings.volcResourceId = "volc.auc.common" settings.saveVolcAccessToken("TEST_TOKEN") diff --git a/doc/05-permissions-and-signing.md b/doc/05-permissions-and-signing.md index e0cf4ce..d591900 100644 --- a/doc/05-permissions-and-signing.md +++ b/doc/05-permissions-and-signing.md @@ -60,10 +60,45 @@ For stable behavior during development: Packaging script note: -- `package_app.sh` uses ad-hoc signing (`codesign --sign -`). +- `package_app.sh` always builds a versioned `.app` plus a `.zip` archive in `dist/`. +- The script uses ad-hoc signing by default (`codesign --sign -`) and can switch to Developer ID signing when `SIGN_IDENTITY` is provided. +- If `VoiceMemo.entitlements` exists, the script signs with that entitlements file to match the Xcode target more closely. - Ad-hoc signatures are not stable identities for repeated permission grants; repeated prompts are expected when running differently signed builds. +## GitHub Tag Releases + +Workflow file: `.github/workflows/release.yml` + +When you push a tag such as `v1.2.3`, the release workflow: + +- runs `swift test` +- builds `dist/VoiceMemo.app` +- injects `CFBundleShortVersionString=1.2.3` +- injects `CFBundleVersion=` +- creates `dist/VoiceMemo-1.2.3-macos.zip` +- uploads the zip and `.sha256` file to GitHub Releases + +### Release Secrets + +For ad-hoc archive publishing, no extra secrets are required. + +For Developer ID signing: + +- `MACOS_CERTIFICATE_P12` +- `MACOS_CERTIFICATE_PASSWORD` +- `MACOS_SIGNING_IDENTITY` +- `MACOS_KEYCHAIN_PASSWORD` (optional) + +For notarization: + +- `APPLE_ID` +- `APPLE_APP_SPECIFIC_PASSWORD` +- `APPLE_TEAM_ID` + +If the signing or notarization secrets are missing, the workflow still publishes an ad-hoc signed archive. That is useful for internal distribution, but external users may still hit Gatekeeper/quarantine warnings. + ## Practical Recommendation - Debug via Xcode with automatic signing and a fixed team. -- Use the packaging script only for quick local distribution/testing; expect permission prompts unless you switch to a stable signing identity. +- Use GitHub tag releases with Developer ID signing + notarization for public binary distribution. +- Use ad-hoc packaging only for quick local distribution/testing; expect permission prompts unless you switch to a stable signing identity. diff --git a/doc/05-permissions-and-signing.zh-CN.md b/doc/05-permissions-and-signing.zh-CN.md index 240ea57..630cb13 100644 --- a/doc/05-permissions-and-signing.zh-CN.md +++ b/doc/05-permissions-and-signing.zh-CN.md @@ -60,10 +60,45 @@ macOS 隐私权限授权与“应用身份 + 签名”绑定。 打包脚本说明: -- `package_app.sh` 使用 ad-hoc 签名(`codesign --sign -`) +- `package_app.sh` 会在 `dist/` 下输出带版本号的 `.app` 和 `.zip` 包 +- 脚本默认使用 ad-hoc 签名(`codesign --sign -`),如果提供 `SIGN_IDENTITY` 则会切换为 Developer ID 签名 +- 如果仓库中存在 `VoiceMemo.entitlements`,脚本会在签名时显式带上它,尽量与 Xcode target 的行为保持一致 - ad-hoc 签名不具备稳定身份,换一种方式运行/签名后重复弹窗是预期现象 +## GitHub Tag 发布 + +工作流文件:`.github/workflows/release.yml` + +当你推送 `v1.2.3` 这样的 tag 时,发布 workflow 会自动: + +- 执行 `swift test` +- 构建 `dist/VoiceMemo.app` +- 注入 `CFBundleShortVersionString=1.2.3` +- 注入 `CFBundleVersion=` +- 生成 `dist/VoiceMemo-1.2.3-macos.zip` +- 将 zip 和 `.sha256` 文件上传到 GitHub Release + +### 发布所需 Secrets + +如果只是发布 ad-hoc 归档,不需要额外 secrets。 + +如果要做 Developer ID 签名,需要: + +- `MACOS_CERTIFICATE_P12` +- `MACOS_CERTIFICATE_PASSWORD` +- `MACOS_SIGNING_IDENTITY` +- `MACOS_KEYCHAIN_PASSWORD`(可选) + +如果要做 notarization,还需要: + +- `APPLE_ID` +- `APPLE_APP_SPECIFIC_PASSWORD` +- `APPLE_TEAM_ID` + +如果签名或公证所需 secrets 缺失,workflow 仍然会发布 ad-hoc 签名的归档。这适合内部测试,但对外分发时用户仍可能遇到 Gatekeeper 或 quarantine 提示。 + ## 实践建议 - 调试:优先使用 Xcode + 自动签名 + 固定 Team。 -- 打包脚本:适合快速本地运行/分发测试;若要减少弹窗,需要改为稳定签名策略。 +- 对外二进制发布:优先使用 GitHub tag release + Developer ID 签名 + notarization。 +- ad-hoc 打包:适合快速本地运行/分发测试;若要减少弹窗,需要改为稳定签名策略。 diff --git a/package_app.sh b/package_app.sh index ea84950..8248b74 100755 --- a/package_app.sh +++ b/package_app.sh @@ -1,36 +1,167 @@ #!/bin/bash -# 设置错误即停止 -set -e +set -euo pipefail -APP_NAME="VoiceMemo" -BUILD_DIR=".build/release" -APP_BUNDLE="$APP_NAME.app" +APP_NAME="${APP_NAME:-VoiceMemo}" +CONFIGURATION="${CONFIGURATION:-release}" +BUILD_DIR="${BUILD_DIR:-.build/$CONFIGURATION}" +SOURCE_INFO_PLIST="${SOURCE_INFO_PLIST:-Sources/VoiceMemo/Info.plist}" +ENTITLEMENTS_PATH="${ENTITLEMENTS_PATH:-VoiceMemo.entitlements}" +OUTPUT_DIR="${OUTPUT_DIR:-dist}" +SIGN_IDENTITY="${SIGN_IDENTITY:--}" +NOTARIZE="${NOTARIZE:-false}" +SWIFT_BUILD_EXTRA_ARGS="${SWIFT_BUILD_EXTRA_ARGS:-}" +APPLE_ID="${APPLE_ID:-}" +APPLE_APP_SPECIFIC_PASSWORD="${APPLE_APP_SPECIFIC_PASSWORD:-}" +APPLE_TEAM_ID="${APPLE_TEAM_ID:-}" + +APP_BUNDLE_NAME="$APP_NAME.app" +APP_BUNDLE="$OUTPUT_DIR/$APP_BUNDLE_NAME" CONTENTS_DIR="$APP_BUNDLE/Contents" MACOS_DIR="$CONTENTS_DIR/MacOS" RESOURCES_DIR="$CONTENTS_DIR/Resources" +INFO_PLIST_PATH="$CONTENTS_DIR/Info.plist" +PLIST_BUDDY="/usr/libexec/PlistBuddy" + +is_true() { + case "$1" in + 1|true|TRUE|yes|YES|on|ON) + return 0 + ;; + *) + return 1 + ;; + esac +} + +require_command() { + if ! command -v "$1" >/dev/null 2>&1; then + echo "Missing required command: $1" >&2 + exit 1 + fi +} + +plist_get() { + local plist_path="$1" + local key="$2" + "$PLIST_BUDDY" -c "Print :$key" "$plist_path" 2>/dev/null || true +} + +plist_set() { + local plist_path="$1" + local key="$2" + local value_type="$3" + local value="$4" + + if "$PLIST_BUDDY" -c "Print :$key" "$plist_path" >/dev/null 2>&1; then + "$PLIST_BUDDY" -c "Set :$key $value" "$plist_path" + else + "$PLIST_BUDDY" -c "Add :$key $value_type $value" "$plist_path" + fi +} + +create_zip() { + local source_path="$1" + local output_path="$2" + + rm -f "$output_path" + ditto -c -k --sequesterRsrc --keepParent "$source_path" "$output_path" +} + +require_command swift +require_command codesign +require_command ditto +require_command shasum +require_command "$PLIST_BUDDY" + +DEFAULT_VERSION="$(plist_get "$SOURCE_INFO_PLIST" CFBundleShortVersionString)" +APP_VERSION="${APP_VERSION:-${DEFAULT_VERSION:-1.0.0}}" +BUILD_NUMBER="${BUILD_NUMBER:-1}" +ARCHIVE_NAME="${ARCHIVE_NAME:-$APP_NAME-$APP_VERSION-macos.zip}" +ARCHIVE_PATH="$OUTPUT_DIR/$ARCHIVE_NAME" +CHECKSUM_PATH="$ARCHIVE_PATH.sha256" +NOTARY_ARCHIVE_PATH="$OUTPUT_DIR/$APP_NAME-$APP_VERSION-notary.zip" +SWIFT_BUILD_ARGS=(-c "$CONFIGURATION") + +if [ -n "$SWIFT_BUILD_EXTRA_ARGS" ]; then + # Split additional flags from the environment for CI/local overrides. + # shellcheck disable=SC2206 + EXTRA_BUILD_ARGS=($SWIFT_BUILD_EXTRA_ARGS) + SWIFT_BUILD_ARGS+=("${EXTRA_BUILD_ARGS[@]}") +fi -echo "Step 1: Building project in release mode..." -swift build -c release +echo "Step 1: Building project in $CONFIGURATION mode..." +swift build "${SWIFT_BUILD_ARGS[@]}" -echo "Step 2: Creating .app bundle structure..." +echo "Step 2: Preparing app bundle..." rm -rf "$APP_BUNDLE" -mkdir -p "$MACOS_DIR" -mkdir -p "$RESOURCES_DIR" +mkdir -p "$MACOS_DIR" "$RESOURCES_DIR" -echo "Step 3: Copying binary, Info.plist and AppIcon..." -cp ".build/release/$APP_NAME" "$MACOS_DIR/" -cp "Sources/VoiceMemo/Info.plist" "$CONTENTS_DIR/" +echo "Step 3: Copying binary, Info.plist and resources..." +cp "$BUILD_DIR/$APP_NAME" "$MACOS_DIR/" +cp "$SOURCE_INFO_PLIST" "$INFO_PLIST_PATH" if [ -f "Sources/VoiceMemo/Resources/AppIcon.icns" ]; then cp "Sources/VoiceMemo/Resources/AppIcon.icns" "$RESOURCES_DIR/" fi -echo "Step 4: Signing the application (ad-hoc)..." -# 使用 ad-hoc 签名,以便在本地运行并请求权限 -codesign --force --deep --sign - "$APP_BUNDLE" +echo "Step 4: Injecting bundle version metadata..." +plist_set "$INFO_PLIST_PATH" CFBundleShortVersionString string "$APP_VERSION" +plist_set "$INFO_PLIST_PATH" CFBundleVersion string "$BUILD_NUMBER" + +echo "Step 5: Signing application bundle..." +CODE_SIGN_ARGS=(--force --sign "$SIGN_IDENTITY") +if [ -f "$ENTITLEMENTS_PATH" ]; then + CODE_SIGN_ARGS+=(--entitlements "$ENTITLEMENTS_PATH") +fi +if [ "$SIGN_IDENTITY" != "-" ]; then + CODE_SIGN_ARGS+=(--timestamp --options runtime) +fi +codesign "${CODE_SIGN_ARGS[@]}" "$APP_BUNDLE" +codesign --verify --deep --strict --verbose=2 "$APP_BUNDLE" + +if is_true "$NOTARIZE"; then + if [ "$SIGN_IDENTITY" = "-" ]; then + echo "Notarization requires a non ad-hoc signing identity." >&2 + exit 1 + fi + if [ -z "$APPLE_ID" ] || [ -z "$APPLE_APP_SPECIFIC_PASSWORD" ] || [ -z "$APPLE_TEAM_ID" ]; then + echo "Notarization requires APPLE_ID, APPLE_APP_SPECIFIC_PASSWORD and APPLE_TEAM_ID." >&2 + exit 1 + fi + + require_command xcrun + + echo "Step 6: Creating notarization archive..." + create_zip "$APP_BUNDLE" "$NOTARY_ARCHIVE_PATH" + + echo "Step 7: Submitting archive for notarization..." + xcrun notarytool submit "$NOTARY_ARCHIVE_PATH" \ + --apple-id "$APPLE_ID" \ + --password "$APPLE_APP_SPECIFIC_PASSWORD" \ + --team-id "$APPLE_TEAM_ID" \ + --wait + + echo "Step 8: Stapling notarization ticket..." + xcrun stapler staple "$APP_BUNDLE" + spctl -a -vvv --type exec "$APP_BUNDLE" + + rm -f "$NOTARY_ARCHIVE_PATH" +fi + +echo "Step 9: Creating distributable archive..." +mkdir -p "$OUTPUT_DIR" +create_zip "$APP_BUNDLE" "$ARCHIVE_PATH" +shasum -a 256 "$ARCHIVE_PATH" > "$CHECKSUM_PATH" echo "------------------------------------------------" -echo "✅ 打包完成: $APP_BUNDLE" -echo "现在你可以通过以下方式运行:" -echo "open $APP_BUNDLE" +echo "App bundle: $APP_BUNDLE" +echo "Archive: $ARCHIVE_PATH" +echo "SHA256: $CHECKSUM_PATH" +echo "Version: $APP_VERSION ($BUILD_NUMBER)" +echo "Signing: $SIGN_IDENTITY" +if is_true "$NOTARIZE"; then + echo "Notarized: yes" +else + echo "Notarized: no" +fi echo "------------------------------------------------"