commit
248182c1f6
174
.github/workflows/build-mac-catalyst.yml
vendored
Normal file
174
.github/workflows/build-mac-catalyst.yml
vendored
Normal file
@ -0,0 +1,174 @@
|
||||
name: Build Mac Catalyst
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
pull_request:
|
||||
branches:
|
||||
- master
|
||||
types: [labeled, synchronize]
|
||||
|
||||
concurrency:
|
||||
group: catalyst-build-${{ github.event.pull_request.number || github.run_id }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
build:
|
||||
if: >
|
||||
github.event_name == 'workflow_dispatch' ||
|
||||
(github.event.action == 'labeled' && (github.event.label.name == 'mac-dmg' || github.event.label.name == 'testflight')) ||
|
||||
github.event.action == 'synchronize'
|
||||
runs-on: macos-15
|
||||
timeout-minutes: 120
|
||||
|
||||
steps:
|
||||
- name: Check PR labels
|
||||
if: github.event_name == 'pull_request'
|
||||
id: labels
|
||||
env:
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
run: |
|
||||
LABELS=$(gh api "repos/${{ github.repository }}/issues/${{ github.event.pull_request.number }}/labels" --jq '.[].name' | tr '\n' ',')
|
||||
echo "all=${LABELS}" >> $GITHUB_OUTPUT
|
||||
if [[ "$LABELS" == *"mac-dmg"* ]]; then
|
||||
echo "has_mac_dmg=true" >> $GITHUB_OUTPUT
|
||||
else
|
||||
echo "has_mac_dmg=false" >> $GITHUB_OUTPUT
|
||||
fi
|
||||
if [[ "$LABELS" == *"testflight"* ]] && [[ "$LABELS" == *"mac-dmg"* ]]; then
|
||||
echo "upload_testflight=true" >> $GITHUB_OUTPUT
|
||||
else
|
||||
echo "upload_testflight=false" >> $GITHUB_OUTPUT
|
||||
fi
|
||||
echo "Labels on PR: ${LABELS}"
|
||||
|
||||
- name: Skip if mac-dmg label not present
|
||||
if: github.event_name == 'pull_request' && steps.labels.outputs.has_mac_dmg != 'true'
|
||||
run: |
|
||||
echo "mac-dmg label not found on PR — skipping build."
|
||||
exit 0
|
||||
|
||||
- name: Checkout project
|
||||
if: github.event_name == 'workflow_dispatch' || steps.labels.outputs.has_mac_dmg == 'true'
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Setup Node.js
|
||||
if: github.event_name == 'workflow_dispatch' || steps.labels.outputs.has_mac_dmg == 'true'
|
||||
uses: actions/setup-node@v6
|
||||
with:
|
||||
node-version: 24
|
||||
cache: npm
|
||||
cache-dependency-path: package-lock.json
|
||||
|
||||
- name: Setup Xcode
|
||||
if: github.event_name == 'workflow_dispatch' || steps.labels.outputs.has_mac_dmg == 'true'
|
||||
uses: maxim-lobanov/setup-xcode@v1
|
||||
with:
|
||||
xcode-version: latest
|
||||
|
||||
- name: Set up Ruby
|
||||
if: github.event_name == 'workflow_dispatch' || steps.labels.outputs.has_mac_dmg == 'true'
|
||||
uses: ruby/setup-ruby@v1
|
||||
with:
|
||||
ruby-version: 3.1.6
|
||||
bundler-cache: true
|
||||
|
||||
- name: Install Node modules
|
||||
if: github.event_name == 'workflow_dispatch' || steps.labels.outputs.has_mac_dmg == 'true'
|
||||
run: npm ci
|
||||
|
||||
- name: Install CocoaPods dependencies
|
||||
if: github.event_name == 'workflow_dispatch' || steps.labels.outputs.has_mac_dmg == 'true'
|
||||
run: bundle exec fastlane ios install_pods
|
||||
env:
|
||||
SKIP_APP_STORE_CONNECT_AUTH: '1'
|
||||
|
||||
- name: Create temporary keychain for signing
|
||||
if: (github.event_name == 'workflow_dispatch' || steps.labels.outputs.has_mac_dmg == 'true') && steps.labels.outputs.upload_testflight == 'true'
|
||||
run: |
|
||||
security create-keychain -p "${{ secrets.KEYCHAIN_PASSWORD }}" build.keychain
|
||||
security default-keychain -s build.keychain
|
||||
security unlock-keychain -p "${{ secrets.KEYCHAIN_PASSWORD }}" build.keychain
|
||||
security set-keychain-settings -t 3600 -u build.keychain
|
||||
|
||||
- name: Build Mac Catalyst app with Fastlane
|
||||
if: github.event_name == 'workflow_dispatch' || steps.labels.outputs.has_mac_dmg == 'true'
|
||||
id: build_catalyst
|
||||
run: bundle exec fastlane ios build_catalyst_app_lane
|
||||
env:
|
||||
SKIP_APP_STORE_CONNECT_AUTH: '1'
|
||||
SKIP_CLEAR_DERIVED_DATA: '1'
|
||||
CATALYST_SIGNING_IDENTITY: ${{ steps.labels.outputs.upload_testflight == 'true' && secrets.CATALYST_SIGNING_IDENTITY || '' }}
|
||||
CATALYST_TEAM_ID: ${{ steps.labels.outputs.upload_testflight == 'true' && secrets.CATALYST_TEAM_ID || '' }}
|
||||
GIT_URL: ${{ steps.labels.outputs.upload_testflight == 'true' && secrets.GIT_URL || '' }}
|
||||
GIT_ACCESS_TOKEN: ${{ steps.labels.outputs.upload_testflight == 'true' && secrets.GIT_ACCESS_TOKEN || '' }}
|
||||
MATCH_READONLY: ${{ steps.labels.outputs.upload_testflight == 'true' && 'false' || 'true' }}
|
||||
KEYCHAIN_NAME: ${{ steps.labels.outputs.upload_testflight == 'true' && 'build' || '' }}
|
||||
KEYCHAIN_PASSWORD: ${{ secrets.KEYCHAIN_PASSWORD }}
|
||||
|
||||
- name: Upload Mac Catalyst DMG
|
||||
id: upload_dmg
|
||||
if: success() && (github.event_name == 'workflow_dispatch' || steps.labels.outputs.has_mac_dmg == 'true')
|
||||
uses: actions/upload-artifact@v6
|
||||
with:
|
||||
name: BlueWallet-Mac-Catalyst
|
||||
path: ${{ steps.build_catalyst.outputs.catalyst_dmg_path }}
|
||||
if-no-files-found: warn
|
||||
|
||||
- name: Create App Store Connect API Key JSON
|
||||
if: success() && steps.labels.outputs.upload_testflight == 'true'
|
||||
run: echo '${{ secrets.APPLE_API_KEY_CONTENT }}' > ./appstore_api_key.json
|
||||
|
||||
- name: Upload to TestFlight
|
||||
if: success() && steps.labels.outputs.upload_testflight == 'true'
|
||||
run: bundle exec fastlane ios upload_catalyst_to_testflight
|
||||
env:
|
||||
APPLE_API_KEY_ID: ${{ secrets.APPLE_API_KEY_ID }}
|
||||
APPLE_API_ISSUER_ID: ${{ secrets.APPLE_API_ISSUER_ID }}
|
||||
CATALYST_TEAM_ID: ${{ secrets.CATALYST_TEAM_ID }}
|
||||
TEAM_ID: ${{ secrets.TEAM_ID }}
|
||||
BRANCH_NAME: ${{ github.head_ref || github.ref_name }}
|
||||
LATEST_COMMIT_MESSAGE: ${{ github.event.pull_request.title || 'Manual build' }}
|
||||
|
||||
- name: Cleanup App Store Connect API Key JSON
|
||||
if: always() && steps.labels.outputs.upload_testflight == 'true'
|
||||
run: rm -f ./appstore_api_key.json
|
||||
|
||||
- name: Cleanup temporary keychain
|
||||
if: always() && steps.labels.outputs.upload_testflight == 'true'
|
||||
run: security delete-keychain build.keychain || true
|
||||
|
||||
- name: Comment on PR with DMG link
|
||||
if: success() && github.event_name == 'pull_request' && steps.labels.outputs.has_mac_dmg == 'true'
|
||||
env:
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
UPLOADED_TO_TF: ${{ steps.labels.outputs.upload_testflight }}
|
||||
run: |
|
||||
ARTIFACT_URL="https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}/artifacts/${{ steps.upload_dmg.outputs.artifact-id }}"
|
||||
COMMENT_TAG="<!-- catalyst-dmg-link -->"
|
||||
|
||||
TF_LINE=""
|
||||
if [[ "$UPLOADED_TO_TF" == "true" ]]; then
|
||||
TF_LINE=$'\n\n**Also uploaded to TestFlight.** Check [App Store Connect](https://appstoreconnect.apple.com) for the build.'
|
||||
fi
|
||||
|
||||
COMMENT_FILE="$(mktemp)"
|
||||
{
|
||||
printf '%s\n' "${COMMENT_TAG}"
|
||||
printf '### Mac Catalyst Build\n\n'
|
||||
printf 'The Mac Catalyst DMG is ready for download:\n\n'
|
||||
printf '[Download BlueWallet-Mac-Catalyst.dmg](%s)\n' "${ARTIFACT_URL}"
|
||||
if [[ -n "$TF_LINE" ]]; then
|
||||
printf '%s\n' "${TF_LINE}"
|
||||
fi
|
||||
printf '<sub>Built from `%s`"
|
||||
} >"${COMMENT_FILE}"
|
||||
|
||||
gh api "repos/${{ github.repository }}/issues/${{ github.event.pull_request.number }}/comments" \
|
||||
--paginate --jq '.[] | select(.body | contains("<!-- catalyst-dmg-link -->")) | .id' | \
|
||||
while read -r comment_id; do
|
||||
gh api -X DELETE "repos/${{ github.repository }}/issues/comments/${comment_id}" || true
|
||||
done
|
||||
|
||||
gh pr comment "${{ github.event.pull_request.number }}" --body-file "${COMMENT_FILE}"
|
||||
@ -82,10 +82,17 @@ def cached_app_store_connect_login
|
||||
end
|
||||
|
||||
before_all do |lane, options|
|
||||
skip_auth_lanes = ['register_devices_from_txt']
|
||||
if ENV['SKIP_APP_STORE_CONNECT_AUTH'] == '1'
|
||||
UI.message('Skipping App Store Connect authentication (SKIP_APP_STORE_CONNECT_AUTH=1)')
|
||||
next
|
||||
end
|
||||
|
||||
skip_auth_lanes = ['register_devices_from_txt', 'build_catalyst_app_lane', 'install_pods', 'clear_derived_data_lane']
|
||||
lane_name = lane.to_s
|
||||
should_skip_auth = skip_auth_lanes.any? { |skip_lane| lane_name == skip_lane || lane_name.end_with?(" #{skip_lane}") }
|
||||
|
||||
# Check if we need App Store Connect for this lane
|
||||
unless skip_auth_lanes.include?(lane)
|
||||
unless should_skip_auth
|
||||
begin
|
||||
# Try to authenticate once at the beginning
|
||||
require 'spaceship'
|
||||
@ -386,12 +393,20 @@ platform :ios do
|
||||
log_success("All provisioning profiles set up")
|
||||
end
|
||||
|
||||
# Only these targets support Mac Catalyst (watch/stickers do not)
|
||||
def catalyst_app_identifiers
|
||||
[
|
||||
"io.bluewallet.bluewallet",
|
||||
"io.bluewallet.bluewallet.MarketWidget"
|
||||
]
|
||||
end
|
||||
|
||||
desc "Fetch development certificates and provisioning profiles for Mac Catalyst"
|
||||
lane :fetch_dev_profiles_catalyst do
|
||||
match(
|
||||
type: "development",
|
||||
platform: "catalyst",
|
||||
app_identifier: app_identifiers,
|
||||
app_identifier: catalyst_app_identifiers,
|
||||
readonly: true,
|
||||
clone_branch_directly: true
|
||||
)
|
||||
@ -402,15 +417,15 @@ platform :ios do
|
||||
match(
|
||||
type: "appstore",
|
||||
platform: "catalyst",
|
||||
app_identifier: app_identifiers,
|
||||
app_identifier: catalyst_app_identifiers,
|
||||
readonly: true,
|
||||
clone_branch_directly: true
|
||||
)
|
||||
end
|
||||
|
||||
desc "Setup provisioning profiles for Mac Catalyst"
|
||||
desc "Create provisioning profiles for Mac Catalyst (first-time setup)"
|
||||
lane :setup_catalyst_provisioning_profiles do
|
||||
app_identifiers.each do |app_identifier|
|
||||
catalyst_app_identifiers.each do |app_identifier|
|
||||
match(
|
||||
type: "development",
|
||||
platform: "catalyst",
|
||||
@ -459,6 +474,338 @@ platform :ios do
|
||||
clean_install: true)
|
||||
end
|
||||
|
||||
desc "Build Mac Catalyst app, handle code signing, create DMG with multilingual README, and set GitHub outputs"
|
||||
lane :build_catalyst_app_lane do
|
||||
Dir.chdir(project_root) do
|
||||
UI.message("Building Mac Catalyst application from: #{Dir.pwd}")
|
||||
|
||||
workspace_path = File.join(project_root, "ios", "BlueWallet.xcworkspace")
|
||||
derived_data_path = File.join(project_root, "ios", "build", "catalyst-derived-data")
|
||||
output_dir = File.join(project_root, "ios", "build", "catalyst-output")
|
||||
|
||||
if ENV['SKIP_CLEAR_DERIVED_DATA'] == '1'
|
||||
UI.message('Skipping clear_derived_data_lane (SKIP_CLEAR_DERIVED_DATA=1)')
|
||||
else
|
||||
clear_derived_data_lane
|
||||
end
|
||||
FileUtils.mkdir_p(derived_data_path)
|
||||
FileUtils.mkdir_p(output_dir)
|
||||
|
||||
# Only these targets support Mac Catalyst
|
||||
catalyst_identifiers = [
|
||||
"io.bluewallet.bluewallet",
|
||||
"io.bluewallet.bluewallet.MarketWidget"
|
||||
]
|
||||
|
||||
has_signing_creds = ENV['CATALYST_SIGNING_IDENTITY'] && !ENV['CATALYST_SIGNING_IDENTITY'].empty? &&
|
||||
ENV['CATALYST_TEAM_ID'] && !ENV['CATALYST_TEAM_ID'].empty?
|
||||
has_match_creds = ENV['GIT_URL'] && !ENV['GIT_URL'].empty? &&
|
||||
ENV['GIT_ACCESS_TOKEN'] && !ENV['GIT_ACCESS_TOKEN'].empty?
|
||||
should_sign = has_signing_creds && has_match_creds && ENV['CATALYST_SKIP_CODESIGNING'] != '1'
|
||||
signing_identity = ENV['CATALYST_SIGNING_IDENTITY'] || "Apple Distribution"
|
||||
|
||||
xcargs_str = "ARCHS=arm64 ONLY_ACTIVE_ARCH=YES"
|
||||
if should_sign
|
||||
UI.message("Setting up Mac Catalyst provisioning profiles via match...")
|
||||
team_id = ENV['CATALYST_TEAM_ID']
|
||||
match_readonly = ENV['MATCH_READONLY'] != 'false'
|
||||
|
||||
# Create/fetch provisioning profiles for catalyst targets
|
||||
catalyst_identifiers.each do |app_id|
|
||||
match(
|
||||
type: "appstore",
|
||||
platform: "catalyst",
|
||||
app_identifier: app_id,
|
||||
team_id: team_id,
|
||||
git_url: ENV['GIT_URL'],
|
||||
git_basic_authorization: ENV['GIT_ACCESS_TOKEN'],
|
||||
readonly: match_readonly,
|
||||
clone_branch_directly: true,
|
||||
keychain_name: ENV['KEYCHAIN_NAME'] || "login",
|
||||
keychain_password: ENV['KEYCHAIN_PASSWORD'] || ""
|
||||
)
|
||||
end
|
||||
|
||||
xcargs_str += " DEVELOPMENT_TEAM=#{team_id}"
|
||||
xcargs_str += " CODE_SIGN_IDENTITY=\"#{signing_identity}\""
|
||||
xcargs_str += " CODE_SIGN_STYLE=Manual"
|
||||
|
||||
# Set provisioning profile specifiers per catalyst target
|
||||
catalyst_identifiers.each do |app_id|
|
||||
profile_name = "match AppStore #{app_id} catalyst"
|
||||
# Convert bundle ID to xcodebuild target setting key
|
||||
# e.g., io.bluewallet.bluewallet -> PROVISIONING_PROFILE_SPECIFIER for that target
|
||||
xcargs_str += " PROVISIONING_PROFILE_SPECIFIER_#{app_id.gsub('.', '_')}=\"#{profile_name}\""
|
||||
end
|
||||
|
||||
UI.success("Provisioning profiles configured for Mac Catalyst")
|
||||
else
|
||||
# Disable code signing entirely so xcodebuild doesn't look for provisioning profiles
|
||||
xcargs_str += " CODE_SIGN_IDENTITY=- CODE_SIGNING_REQUIRED=NO CODE_SIGNING_ALLOWED=NO"
|
||||
UI.message("No signing credentials provided — building without code signing")
|
||||
end
|
||||
|
||||
build_app(
|
||||
scheme: "BlueWallet",
|
||||
workspace: workspace_path,
|
||||
configuration: "Release",
|
||||
destination: "generic/platform=macOS,variant=Mac Catalyst",
|
||||
xcargs: xcargs_str,
|
||||
clean: true,
|
||||
skip_codesigning: !should_sign,
|
||||
skip_package_ipa: true,
|
||||
derived_data_path: derived_data_path,
|
||||
buildlog_path: File.join(project_root, "ios", "build_logs")
|
||||
)
|
||||
|
||||
archive_path = lane_context[SharedValues::XCODEBUILD_ARCHIVE]
|
||||
if archive_path.nil? || archive_path.empty?
|
||||
archive_path = Dir.glob(File.join(Dir.home, "Library/Developer/Xcode/Archives/**/*.xcarchive")).max_by { |path| File.mtime(path) }
|
||||
end
|
||||
|
||||
candidate_paths = []
|
||||
candidate_paths << File.join(archive_path, "Products/Applications/BlueWallet.app") if archive_path
|
||||
candidate_paths << Dir.glob(File.join(derived_data_path, "Build/Products/Release-maccatalyst/*.app")).first
|
||||
candidate_paths << Dir.glob(File.join(derived_data_path, "Build/Intermediates.noindex/ArchiveIntermediates/BlueWallet/BuildProductsPath/Release-maccatalyst/*.app")).first
|
||||
|
||||
catalyst_app_path = candidate_paths.compact.find { |path| File.exist?(path) }
|
||||
UI.user_error!("Mac Catalyst app was not found after build") if catalyst_app_path.nil?
|
||||
UI.success("Mac Catalyst app found at: #{catalyst_app_path}")
|
||||
|
||||
if should_sign && ENV['CATALYST_SIGNING_IDENTITY'] && !ENV['CATALYST_SIGNING_IDENTITY'].empty?
|
||||
UI.message("Re-signing app with: #{signing_identity}")
|
||||
sh("codesign",
|
||||
"--deep",
|
||||
"--force",
|
||||
"--options", "runtime",
|
||||
"--sign", signing_identity,
|
||||
catalyst_app_path)
|
||||
sh("codesign",
|
||||
"--verify",
|
||||
"--deep",
|
||||
"--strict",
|
||||
catalyst_app_path)
|
||||
UI.success("App signed and verified")
|
||||
else
|
||||
# Ad-hoc sign so macOS doesn't treat the app as "damaged"
|
||||
UI.message("Ad-hoc signing app to avoid Gatekeeper quarantine issues...")
|
||||
sh("codesign",
|
||||
"--deep",
|
||||
"--force",
|
||||
"--sign", "-",
|
||||
catalyst_app_path)
|
||||
UI.success("App ad-hoc signed")
|
||||
end
|
||||
|
||||
dmg_path = File.join(output_dir, "BlueWallet-Mac-Catalyst.dmg")
|
||||
UI.message("Creating DMG at: #{dmg_path}")
|
||||
|
||||
dmg_staging = File.join(output_dir, "dmg-staging")
|
||||
FileUtils.rm_rf(dmg_staging)
|
||||
FileUtils.mkdir_p(dmg_staging)
|
||||
FileUtils.cp_r(catalyst_app_path, dmg_staging)
|
||||
|
||||
sh("ln", "-s", "/Applications", "#{dmg_staging}/Applications")
|
||||
|
||||
# Add README with first-launch instructions
|
||||
readme_path = File.join(dmg_staging, "README - Read Before Opening.txt")
|
||||
File.write(readme_path, <<~README)
|
||||
╔══════════════════════════════════════════════════════════════╗
|
||||
║ BlueWallet for macOS ║
|
||||
╚══════════════════════════════════════════════════════════════╝
|
||||
|
||||
═══ ENGLISH ═══════════════════════════════════════════════════
|
||||
|
||||
INSTALLATION: Drag "BlueWallet.app" into "Applications".
|
||||
|
||||
FIRST LAUNCH — macOS may show a warning. To open:
|
||||
• Right-click the app → Open → click "Open" in the dialog.
|
||||
• Or: System Settings → Privacy & Security → Open Anyway.
|
||||
• Or (Terminal): xattr -cr /Applications/BlueWallet.app
|
||||
You only need to do this once.
|
||||
|
||||
═══ ESPAÑOL ════════════════════════════════════════════════════
|
||||
|
||||
INSTALACIÓN: Arrastra "BlueWallet.app" a "Aplicaciones".
|
||||
|
||||
PRIMER INICIO — macOS puede mostrar una advertencia. Para abrir:
|
||||
• Haz clic derecho en la app → Abrir → clic en "Abrir".
|
||||
• O: Ajustes del Sistema → Privacidad y Seguridad → Abrir igualmente.
|
||||
• O (Terminal): xattr -cr /Applications/BlueWallet.app
|
||||
Solo necesitas hacerlo una vez.
|
||||
|
||||
═══ 中文 ═══════════════════════════════════════════════════════
|
||||
|
||||
安装:将 "BlueWallet.app" 拖入 "应用程序" 文件夹。
|
||||
|
||||
首次启动 — macOS 可能会显示警告。打开方法:
|
||||
• 右键点击应用 → 打开 → 在对话框中点击"打开"。
|
||||
• 或:系统设置 → 隐私与安全性 → 仍要打开。
|
||||
• 或(终端):xattr -cr /Applications/BlueWallet.app
|
||||
只需操作一次。
|
||||
|
||||
═══ PORTUGUÊS ══════════════════════════════════════════════════
|
||||
|
||||
INSTALAÇÃO: Arraste "BlueWallet.app" para "Aplicativos".
|
||||
|
||||
PRIMEIRA ABERTURA — o macOS pode exibir um aviso. Para abrir:
|
||||
• Clique com o botão direito → Abrir → clique em "Abrir".
|
||||
• Ou: Ajustes do Sistema → Privacidade e Segurança → Abrir Mesmo Assim.
|
||||
• Ou (Terminal): xattr -cr /Applications/BlueWallet.app
|
||||
Você só precisa fazer isso uma vez.
|
||||
|
||||
═══ РУССКИЙ ════════════════════════════════════════════════════
|
||||
|
||||
УСТАНОВКА: Перетащите "BlueWallet.app" в "Программы".
|
||||
|
||||
ПЕРВЫЙ ЗАПУСК — macOS может показать предупреждение. Чтобы открыть:
|
||||
• Нажмите правой кнопкой → Открыть → нажмите «Открыть».
|
||||
• Или: Системные настройки → Конфиденциальность → Подтвердить открытие.
|
||||
• Или (Терминал): xattr -cr /Applications/BlueWallet.app
|
||||
Это нужно сделать только один раз.
|
||||
|
||||
═══ 日本語 ═════════════════════════════════════════════════════
|
||||
|
||||
インストール:「BlueWallet.app」を「アプリケーション」にドラッグ。
|
||||
|
||||
初回起動 — macOS が警告を表示する場合があります。開くには:
|
||||
• アプリを右クリック →「開く」→ ダイアログで「開く」をクリック。
|
||||
• または:システム設定 → プライバシーとセキュリティ →「このまま開く」。
|
||||
• または(ターミナル):xattr -cr /Applications/BlueWallet.app
|
||||
この操作は一度だけ必要です。
|
||||
|
||||
═══ DEUTSCH ════════════════════════════════════════════════════
|
||||
|
||||
INSTALLATION: Ziehe „BlueWallet.app" in den Ordner „Programme".
|
||||
|
||||
ERSTER START — macOS zeigt möglicherweise eine Warnung. Zum Öffnen:
|
||||
• Rechtsklick auf die App → Öffnen → „Öffnen" klicken.
|
||||
• Oder: Systemeinstellungen → Datenschutz & Sicherheit → Dennoch öffnen.
|
||||
• Oder (Terminal): xattr -cr /Applications/BlueWallet.app
|
||||
Dies ist nur beim ersten Mal nötig.
|
||||
|
||||
═══ FRANÇAIS ═══════════════════════════════════════════════════
|
||||
|
||||
INSTALLATION : Glissez « BlueWallet.app » dans « Applications ».
|
||||
|
||||
PREMIER LANCEMENT — macOS peut afficher un avertissement. Pour ouvrir :
|
||||
• Clic droit sur l'app → Ouvrir → cliquez sur « Ouvrir ».
|
||||
• Ou : Réglages Système → Confidentialité et sécurité → Ouvrir quand même.
|
||||
• Ou (Terminal) : xattr -cr /Applications/BlueWallet.app
|
||||
Cette opération n'est nécessaire qu'une seule fois.
|
||||
|
||||
═══ العربية ════════════════════════════════════════════════════
|
||||
|
||||
التثبيت: اسحب "BlueWallet.app" إلى مجلد "التطبيقات".
|
||||
|
||||
التشغيل الأول — قد يعرض macOS تحذيرًا. لفتح التطبيق:
|
||||
• انقر بزر الماوس الأيمن → افتح → انقر "فتح" في مربع الحوار.
|
||||
• أو: إعدادات النظام → الخصوصية والأمان → فتح على أي حال.
|
||||
• أو (الطرفية): xattr -cr /Applications/BlueWallet.app
|
||||
تحتاج للقيام بذلك مرة واحدة فقط.
|
||||
|
||||
────────────────────────────────────────────────────────────────
|
||||
https://bluewallet.io
|
||||
README
|
||||
UI.message("Added multilingual README with first-launch instructions to DMG")
|
||||
|
||||
FileUtils.rm_f(dmg_path)
|
||||
sh("hdiutil", "create", "-volname", "BlueWallet", "-srcfolder", dmg_staging, "-ov", "-format", "UDZO", dmg_path)
|
||||
UI.user_error!("DMG was not created at #{dmg_path}") unless File.exist?(dmg_path)
|
||||
UI.success("DMG created at: #{dmg_path}")
|
||||
|
||||
FileUtils.rm_rf(dmg_staging)
|
||||
|
||||
ENV['CATALYST_APP_PATH'] = catalyst_app_path
|
||||
ENV['CATALYST_DMG_PATH'] = dmg_path
|
||||
if ENV['GITHUB_OUTPUT']
|
||||
File.open(ENV['GITHUB_OUTPUT'], 'a') do |f|
|
||||
f.puts "catalyst_app_path=#{catalyst_app_path}"
|
||||
f.puts "catalyst_dmg_path=#{dmg_path}"
|
||||
end
|
||||
end
|
||||
|
||||
UI.success("macOS app built at: #{catalyst_app_path}")
|
||||
UI.success("macOS DMG at: #{dmg_path}")
|
||||
end
|
||||
end
|
||||
|
||||
desc "Upload Mac Catalyst app to TestFlight"
|
||||
lane :upload_catalyst_to_testflight do
|
||||
Dir.chdir(project_root) do
|
||||
# Locate the xcarchive
|
||||
archive_path = lane_context[SharedValues::XCODEBUILD_ARCHIVE]
|
||||
if archive_path.nil? || archive_path.empty?
|
||||
archive_path = Dir.glob(File.join(Dir.home, "Library/Developer/Xcode/Archives/**/*.xcarchive")).max_by { |path| File.mtime(path) }
|
||||
end
|
||||
UI.user_error!("No xcarchive found for TestFlight upload") if archive_path.nil? || !File.exist?(archive_path)
|
||||
UI.message("Using archive: #{archive_path}")
|
||||
|
||||
output_dir = File.join(project_root, "ios", "build", "catalyst-output")
|
||||
FileUtils.mkdir_p(output_dir)
|
||||
|
||||
# Export the archive as a .pkg for Mac Catalyst
|
||||
team_id = ENV['CATALYST_TEAM_ID'] || ENV['TEAM_ID']
|
||||
export_plist_path = File.join(output_dir, "ExportOptions.plist")
|
||||
|
||||
# Build provisioning profiles mapping for manual signing
|
||||
profiles_xml = catalyst_app_identifiers.map do |app_id|
|
||||
profile_name = "match AppStore #{app_id} catalyst"
|
||||
"\t\t\t<key>#{app_id}</key>\n\t\t\t<string>#{profile_name}</string>"
|
||||
end.join("\n")
|
||||
|
||||
File.write(export_plist_path, <<~PLIST)
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>method</key>
|
||||
<string>app-store</string>
|
||||
<key>destination</key>
|
||||
<string>upload</string>
|
||||
<key>teamID</key>
|
||||
<string>#{team_id}</string>
|
||||
<key>signingStyle</key>
|
||||
<string>manual</string>
|
||||
<key>provisioningProfiles</key>
|
||||
<dict>
|
||||
#{profiles_xml}
|
||||
</dict>
|
||||
</dict>
|
||||
</plist>
|
||||
PLIST
|
||||
|
||||
pkg_path = File.join(output_dir, "BlueWallet.pkg")
|
||||
sh("xcodebuild",
|
||||
"-exportArchive",
|
||||
"-archivePath", archive_path,
|
||||
"-exportOptionsPlist", export_plist_path,
|
||||
"-exportPath", output_dir)
|
||||
|
||||
# Find the exported pkg
|
||||
exported_pkg = Dir.glob(File.join(output_dir, "*.pkg")).first
|
||||
UI.user_error!("No .pkg found after export") if exported_pkg.nil? || !File.exist?(exported_pkg)
|
||||
UI.success("Exported pkg: #{exported_pkg}")
|
||||
|
||||
# Build changelog
|
||||
branch_name = ENV['BRANCH_NAME'] || "unknown-branch"
|
||||
last_commit_message = ENV['LATEST_COMMIT_MESSAGE'] || "No commit message found"
|
||||
changelog = "Build Information:\n"
|
||||
changelog += "- Branch: #{branch_name}\n" if branch_name != 'master'
|
||||
changelog += "- Commit: #{last_commit_message}\n"
|
||||
|
||||
# Upload to TestFlight
|
||||
upload_to_testflight(
|
||||
api_key_path: "./appstore_api_key.json",
|
||||
pkg: exported_pkg,
|
||||
skip_waiting_for_build_processing: true,
|
||||
changelog: changelog
|
||||
)
|
||||
|
||||
UI.success("Successfully uploaded Mac Catalyst app to TestFlight!")
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
desc "Upload IPA to TestFlight"
|
||||
lane :upload_to_testflight_lane do
|
||||
|
||||
Loading…
Reference in New Issue
Block a user