iOS 배포 구성 — 서명 팀 · Firebase 설정 번들 · 업로드 스크립트

This commit is contained in:
Sn-Kinos
2026-10-04 01:43:10 +09:00
parent 613798b863
commit e255973a8d
8 changed files with 349 additions and 37 deletions
+2 -1
View File
@@ -6,13 +6,14 @@
`xcodebuild -exportArchive` 가 이 파일을 읽는다. 없으면 대화형으로 물어보므로 `xcodebuild -exportArchive` 가 이 파일을 읽는다. 없으면 대화형으로 물어보므로
자동 배포가 불가능하다 — 계정이 열린 뒤에 만들면 그날 하루를 여기서 쓴다. 자동 배포가 불가능하다 — 계정이 열린 뒤에 만들면 그날 하루를 여기서 쓴다.
teamID 는 발주사 Fancake Inc. 개발자 팀이다. 앱(kr.co.fanuzu.app)이 그 팀 소속이라
다른 팀으로 서명하면 업로드가 엉뚱한 App Store Connect 로 간다.
--> -->
<plist version="1.0"> <plist version="1.0">
<dict> <dict>
<key>method</key> <key>method</key>
<string>app-store-connect</string> <string>app-store-connect</string>
<!-- Fancake Inc. -->
<key>teamID</key> <key>teamID</key>
<string>V28G543VK5</string> <string>V28G543VK5</string>
+3 -2
View File
@@ -1,6 +1,7 @@
#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig" #include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"
#include "Generated.xcconfig" #include "Generated.xcconfig"
// Google 로그인 콜백 스킴. tool/ios/inject_url_scheme.py 가 만든다. // Google 로그인 콜백 스킴. tools/ios/inject_url_scheme.py 가 만든다.
// `#include?` 는 파일이 없어도 빌드를 멈추지 않는다 — 그때는 Google 로그인만 동작하지 않는다. // `#include?` 는 파일이 없어도 빌드를 멈추지 않는다. 설정 파일 투입 전에도
// 빌드가 되어야 하기 때문이다 — 그때는 Google 로그인만 동작하지 않는다.
#include? "../config/Google.xcconfig" #include? "../config/Google.xcconfig"
+3 -2
View File
@@ -1,6 +1,7 @@
#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig" #include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"
#include "Generated.xcconfig" #include "Generated.xcconfig"
// Google 로그인 콜백 스킴. tool/ios/inject_url_scheme.py 가 만든다. // Google 로그인 콜백 스킴. tools/ios/inject_url_scheme.py 가 만든다.
// `#include?` 는 파일이 없어도 빌드를 멈추지 않는다 — 그때는 Google 로그인만 동작하지 않는다. // `#include?` 는 파일이 없어도 빌드를 멈추지 않는다. 설정 파일 투입 전에도
// 빌드가 되어야 하기 때문이다 — 그때는 Google 로그인만 동작하지 않는다.
#include? "../config/Google.xcconfig" #include? "../config/Google.xcconfig"
+1 -1
View File
@@ -65,8 +65,8 @@
97C146FB1CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = "<group>"; }; 97C146FB1CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = "<group>"; };
97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; }; 97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = "<group>"; }; 97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = "<group>"; };
4F2B0E1A2C3D4E5F60718293 /* GoogleService-Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = "GoogleService-Info.plist"; sourceTree = "<group>"; };
97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; }; 97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
4F2B0E1A2C3D4E5F60718293 /* GoogleService-Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = "GoogleService-Info.plist"; sourceTree = "<group>"; };
A68C3B80D45981965B89CA40 /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = "<group>"; }; A68C3B80D45981965B89CA40 /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = "<group>"; };
AB2C33122C7A95220B242430 /* Pods_RunnerTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_RunnerTests.framework; sourceTree = BUILT_PRODUCTS_DIR; }; AB2C33122C7A95220B242430 /* Pods_RunnerTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_RunnerTests.framework; sourceTree = BUILT_PRODUCTS_DIR; };
CBE157220A7C382F845775F0 /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; }; CBE157220A7C382F845775F0 /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; };
+263 -24
View File
@@ -26,6 +26,269 @@
<string>$(FLUTTER_BUILD_NUMBER)</string> <string>$(FLUTTER_BUILD_NUMBER)</string>
<key>LSRequiresIPhoneOS</key> <key>LSRequiresIPhoneOS</key>
<true/> <true/>
<!--
▼ 스토어 제출에 필요한 선언들. 없으면 어떤 일이 생기는지 함께 적는다.
-->
<!--
암호화 사용 신고. HTTPS 만 쓰므로 면제 대상이다.
이 키가 없으면 **업로드할 때마다 App Store Connect 가 수동 응답을 요구**하고,
응답 전까지 TestFlight 배포가 멈춘다.
-->
<key>ITSAppUsesNonExemptEncryption</key>
<false/>
<!--
권한 사용 문구. iOS 는 문구가 없는 상태에서 권한을 요청하면 **앱을 즉시
종료**시킨다. 크래시로 보이지만 원인은 이 파일이다.
문구는 「무엇에 쓰는지」를 적어야 심사를 지난다 — 「접근이 필요합니다」처럼
용도가 없는 문장은 반려 사유다.
-->
<key>NSPhotoLibraryUsageDescription</key>
<string>프로필 사진을 고르기 위해 사진에 접근합니다.</string>
<key>NSCameraUsageDescription</key>
<string>프로필 사진을 찍고 방문 인증 사진을 남기기 위해 카메라를 사용합니다.</string>
<key>NSLocationWhenInUseUsageDescription</key>
<string>지정한 장소에 방문하셨는지 확인해 퀘스트 보상을 드리기 위해 위치를 확인합니다.</string>
<key>NSMotionUsageDescription</key>
<string>걸음 수를 세어 만보기 퀘스트를 확인하기 위해 동작 데이터를 읽습니다.</string>
<key>NSHealthShareUsageDescription</key>
<string>걸음 수를 읽어 만보기 퀘스트 달성을 확인합니다. 다른 건강 정보는 읽지 않습니다.</string>
<!-- AdMob 앱 ID — Google 공식 테스트 앱 ID(9주차 샌드박스). 실 값은 lib/core/monetization/rewarded_ad.dart 의 AdUnits 와 함께 바꾼다. -->
<key>GADApplicationIdentifier</key>
<string>ca-app-pub-3940256099942544~1458002511</string>
<!-- SKAdNetwork — Google Mobile Ads 가 공개한 목록 그대로(Google cstr6suwn9 + 제3자 구매자 49).
출처: developers.google.com/admob/ios/quick-start 「Update your Info.plist」(2026-09-28 확인). -->
<key>SKAdNetworkItems</key>
<array>
<dict>
<key>SKAdNetworkIdentifier</key>
<string>cstr6suwn9.skadnetwork</string>
</dict>
<dict>
<key>SKAdNetworkIdentifier</key>
<string>4fzdc2evr5.skadnetwork</string>
</dict>
<dict>
<key>SKAdNetworkIdentifier</key>
<string>2fnua5tdw4.skadnetwork</string>
</dict>
<dict>
<key>SKAdNetworkIdentifier</key>
<string>ydx93a7ass.skadnetwork</string>
</dict>
<dict>
<key>SKAdNetworkIdentifier</key>
<string>p78axxw29g.skadnetwork</string>
</dict>
<dict>
<key>SKAdNetworkIdentifier</key>
<string>v72qych5uu.skadnetwork</string>
</dict>
<dict>
<key>SKAdNetworkIdentifier</key>
<string>ludvb6z3bs.skadnetwork</string>
</dict>
<dict>
<key>SKAdNetworkIdentifier</key>
<string>cp8zw746q7.skadnetwork</string>
</dict>
<dict>
<key>SKAdNetworkIdentifier</key>
<string>3sh42y64q3.skadnetwork</string>
</dict>
<dict>
<key>SKAdNetworkIdentifier</key>
<string>c6k4g5qg8m.skadnetwork</string>
</dict>
<dict>
<key>SKAdNetworkIdentifier</key>
<string>s39g8k73mm.skadnetwork</string>
</dict>
<dict>
<key>SKAdNetworkIdentifier</key>
<string>wg4vff78zm.skadnetwork</string>
</dict>
<dict>
<key>SKAdNetworkIdentifier</key>
<string>3qy4746246.skadnetwork</string>
</dict>
<dict>
<key>SKAdNetworkIdentifier</key>
<string>f38h382jlk.skadnetwork</string>
</dict>
<dict>
<key>SKAdNetworkIdentifier</key>
<string>hs6bdukanm.skadnetwork</string>
</dict>
<dict>
<key>SKAdNetworkIdentifier</key>
<string>mlmmfzh3r3.skadnetwork</string>
</dict>
<dict>
<key>SKAdNetworkIdentifier</key>
<string>v4nxqhlyqp.skadnetwork</string>
</dict>
<dict>
<key>SKAdNetworkIdentifier</key>
<string>wzmmz9fp6w.skadnetwork</string>
</dict>
<dict>
<key>SKAdNetworkIdentifier</key>
<string>su67r6k2v3.skadnetwork</string>
</dict>
<dict>
<key>SKAdNetworkIdentifier</key>
<string>yclnxrl5pm.skadnetwork</string>
</dict>
<dict>
<key>SKAdNetworkIdentifier</key>
<string>t38b2kh725.skadnetwork</string>
</dict>
<dict>
<key>SKAdNetworkIdentifier</key>
<string>7ug5zh24hu.skadnetwork</string>
</dict>
<dict>
<key>SKAdNetworkIdentifier</key>
<string>gta9lk7p23.skadnetwork</string>
</dict>
<dict>
<key>SKAdNetworkIdentifier</key>
<string>vutu7akeur.skadnetwork</string>
</dict>
<dict>
<key>SKAdNetworkIdentifier</key>
<string>y5ghdn5j9k.skadnetwork</string>
</dict>
<dict>
<key>SKAdNetworkIdentifier</key>
<string>v9wttpbfk9.skadnetwork</string>
</dict>
<dict>
<key>SKAdNetworkIdentifier</key>
<string>n38lu8286q.skadnetwork</string>
</dict>
<dict>
<key>SKAdNetworkIdentifier</key>
<string>47vhws6wlr.skadnetwork</string>
</dict>
<dict>
<key>SKAdNetworkIdentifier</key>
<string>kbd757ywx3.skadnetwork</string>
</dict>
<dict>
<key>SKAdNetworkIdentifier</key>
<string>9t245vhmpl.skadnetwork</string>
</dict>
<dict>
<key>SKAdNetworkIdentifier</key>
<string>a2p9lx4jpn.skadnetwork</string>
</dict>
<dict>
<key>SKAdNetworkIdentifier</key>
<string>22mmun2rn5.skadnetwork</string>
</dict>
<dict>
<key>SKAdNetworkIdentifier</key>
<string>44jx6755aq.skadnetwork</string>
</dict>
<dict>
<key>SKAdNetworkIdentifier</key>
<string>k674qkevps.skadnetwork</string>
</dict>
<dict>
<key>SKAdNetworkIdentifier</key>
<string>4468km3ulz.skadnetwork</string>
</dict>
<dict>
<key>SKAdNetworkIdentifier</key>
<string>2u9pt9hc89.skadnetwork</string>
</dict>
<dict>
<key>SKAdNetworkIdentifier</key>
<string>8s468mfl3y.skadnetwork</string>
</dict>
<dict>
<key>SKAdNetworkIdentifier</key>
<string>klf5c3l5u5.skadnetwork</string>
</dict>
<dict>
<key>SKAdNetworkIdentifier</key>
<string>ppxm28t8ap.skadnetwork</string>
</dict>
<dict>
<key>SKAdNetworkIdentifier</key>
<string>kbmxgpxpgc.skadnetwork</string>
</dict>
<dict>
<key>SKAdNetworkIdentifier</key>
<string>uw77j35x4d.skadnetwork</string>
</dict>
<dict>
<key>SKAdNetworkIdentifier</key>
<string>578prtvx9j.skadnetwork</string>
</dict>
<dict>
<key>SKAdNetworkIdentifier</key>
<string>4dzt52r2t5.skadnetwork</string>
</dict>
<dict>
<key>SKAdNetworkIdentifier</key>
<string>tl55sbb4fm.skadnetwork</string>
</dict>
<dict>
<key>SKAdNetworkIdentifier</key>
<string>c3frkrj4fj.skadnetwork</string>
</dict>
<dict>
<key>SKAdNetworkIdentifier</key>
<string>e5fvkxwrpn.skadnetwork</string>
</dict>
<dict>
<key>SKAdNetworkIdentifier</key>
<string>8c4e2ghe7u.skadnetwork</string>
</dict>
<dict>
<key>SKAdNetworkIdentifier</key>
<string>3rd42ekr43.skadnetwork</string>
</dict>
<dict>
<key>SKAdNetworkIdentifier</key>
<string>97r2b46745.skadnetwork</string>
</dict>
<dict>
<key>SKAdNetworkIdentifier</key>
<string>3qcr597p9d.skadnetwork</string>
</dict>
</array>
<key>NSUserTrackingUsageDescription</key>
<string>관심에 맞는 광고를 보여 드리기 위해 사용합니다. 허용하지 않으셔도 앱을 그대로 쓰실 수 있습니다.</string>
<!--
Google 로그인 콜백 URL 스킴.
값은 `GoogleService-Info.plist` 의 REVERSED_CLIENT_ID 이며 Firebase 프로젝트가
열려야 나온다. `tools/ios/inject_url_scheme.py` 가 설정 파일에서 뽑아 여기에
써 넣는다 — 손으로 옮기면 오타 한 글자에 로그인이 조용히 실패한다.
Apple 로그인의 signinwithapple 스킴은 Android 전용이라 여기 두지 않는다.
-->
<key>CFBundleURLTypes</key>
<array>
<dict>
<key>CFBundleTypeRole</key>
<string>Editor</string>
<key>CFBundleURLName</key>
<string>google-sign-in</string>
<key>CFBundleURLSchemes</key>
<array>
<string>$(GOOGLE_REVERSED_CLIENT_ID)</string>
</array>
</dict>
</array>
<key>UIApplicationSceneManifest</key> <key>UIApplicationSceneManifest</key>
<dict> <dict>
<key>UIApplicationSupportsMultipleScenes</key> <key>UIApplicationSupportsMultipleScenes</key>
@@ -66,29 +329,5 @@
<string>UIInterfaceOrientationLandscapeLeft</string> <string>UIInterfaceOrientationLandscapeLeft</string>
<string>UIInterfaceOrientationLandscapeRight</string> <string>UIInterfaceOrientationLandscapeRight</string>
</array> </array>
<!--
암호화 사용 신고. HTTPS 만 쓰므로 면제 대상이다.
이 키가 없으면 업로드할 때마다 App Store Connect 가 수동 응답을 요구한다.
-->
<key>ITSAppUsesNonExemptEncryption</key>
<false/>
<!--
Google 로그인 콜백 URL 스킴. 값은 GoogleService-Info.plist 의 REVERSED_CLIENT_ID 이며
tool/ios/inject_url_scheme.py 가 ios/config/Google.xcconfig 로 뽑아 둔다.
-->
<key>CFBundleURLTypes</key>
<array>
<dict>
<key>CFBundleTypeRole</key>
<string>Editor</string>
<key>CFBundleURLName</key>
<string>google-sign-in</string>
<key>CFBundleURLSchemes</key>
<array>
<string>$(GOOGLE_REVERSED_CLIENT_ID)</string>
</array>
</dict>
</array>
</dict> </dict>
</plist> </plist>
+8 -4
View File
@@ -3,8 +3,12 @@
<plist version="1.0"> <plist version="1.0">
<dict> <dict>
<!-- <!--
Sign in with Apple. 소셜 로그인을 제공하면 Apple 심사 필수 조건이다. Sign in with Apple.
이 Capability 가 없으면 Apple 로그인 코드가 있어도 동작하지 않는다.
**정책 §P-01 이 「소셜 로그인 제공 시 Apple 심사 필수 조건」이라 확정한 항목**이며,
이 Capability 가 없으면 Apple 로그인 코드가 있어도 동작하지 않고 심사에서
반려된다. Apple Developer 계정이 열리면 같은 Capability 를 App ID 에도
켜야 짝이 맞는다.
--> -->
<key>com.apple.developer.applesignin</key> <key>com.apple.developer.applesignin</key>
<array> <array>
@@ -12,8 +16,8 @@
</array> </array>
<!-- <!--
푸시 환경 선언. Firebase Auth 가 APNs 등록 API 를 링크하므로 선언해 둔다. 푸시 알림. 6주차 FCM 연동 대상이며 환경은 배포 시 production 으로 바뀐다.
App Store 내보내기 때 Xcode 가 production 으로 바꿔 서명한다. APNs 인증키는 Apple Developer 계정에 종속된다(계정 이전 별첨 C-4).
--> -->
<key>aps-environment</key> <key>aps-environment</key>
<string>development</string> <string>development</string>
@@ -8,7 +8,7 @@ Info.plist 에 손으로 옮기면 오타 한 글자에 로그인이 **조용히
설정 파일이 투입될 때 한 번 돌리면 `ios/config/Google.xcconfig` 가 만들어지고, 설정 파일이 투입될 때 한 번 돌리면 `ios/config/Google.xcconfig` 가 만들어지고,
Info.plist 의 `$(GOOGLE_REVERSED_CLIENT_ID)` 가 그 값으로 치환된다. Info.plist 의 `$(GOOGLE_REVERSED_CLIENT_ID)` 가 그 값으로 치환된다.
실행: python3 tool/ios/inject_url_scheme.py 실행: python3 tools/ios/inject_url_scheme.py
""" """
from __future__ import annotations from __future__ import annotations
@@ -25,7 +25,7 @@ def main() -> int:
if not SOURCE.exists(): if not SOURCE.exists():
print( print(
f"설정 파일이 아직 없습니다: {SOURCE.relative_to(ROOT)}\n" f"설정 파일이 아직 없습니다: {SOURCE.relative_to(ROOT)}\n"
"Firebase 콘솔에서 iOS 앱 설정 파일을 받아 그 자리에 두십시오.", "Firebase 프로젝트를 연 뒤 firebase/setup-project.sh 를 돌리십시오.",
file=sys.stderr, file=sys.stderr,
) )
return 1 return 1
@@ -45,7 +45,7 @@ def main() -> int:
OUT.parent.mkdir(parents=True, exist_ok=True) OUT.parent.mkdir(parents=True, exist_ok=True)
OUT.write_text( OUT.write_text(
"// 자동 생성 — tool/ios/inject_url_scheme.py\n" "// 자동 생성 — tools/ios/inject_url_scheme.py\n"
"// GoogleService-Info.plist 가 바뀌면 다시 돌리십시오.\n" "// GoogleService-Info.plist 가 바뀌면 다시 돌리십시오.\n"
f"GOOGLE_REVERSED_CLIENT_ID = {reversed_id}\n", f"GOOGLE_REVERSED_CLIENT_ID = {reversed_id}\n",
encoding="utf-8", encoding="utf-8",
+66
View File
@@ -0,0 +1,66 @@
#!/usr/bin/env bash
# iOS 아카이브 → TestFlight 업로드.
#
# **계정이 열린 날 이 스크립트 하나로 끝나게 하는 것이 목적**이다. 별첨 2 가
# 3주차부터 매주 TestFlight 제출을 요구했고 그것을 지키지 못했으므로(미이행 F-2),
# 계정이 열리면 지체 없이 재개되어야 한다.
#
# 사전 조건
# 1. ios/Runner/GoogleService-Info.plist 배치 · tools/ios/inject_url_scheme.py 실행
# 2. Xcode 정식판을 `xcode-select` 로 지정 (베타 SDK 빌드는 업로드가 거부된다)
# 3. App Store Connect API 키 (환경변수 3종)
#
# 실행:
# export ASC_KEY_ID=... ASC_ISSUER_ID=... ASC_KEY_PATH=~/private_keys/AuthKey_XXX.p8
# bash tools/ios/upload_testflight.sh
set -euo pipefail
ROOT="$(cd "$(dirname "$0")/../.." && pwd)"
APP="$ROOT"
ARCHIVE="$APP/build/ios/Runner.xcarchive"
EXPORT_DIR="$APP/build/ios/ipa"
OPTIONS="$APP/ios/ExportOptions.plist"
if grep -q REPLACE_WITH_TEAM_ID "$OPTIONS"; then
echo "ExportOptions.plist 의 teamID 를 먼저 채우십시오." >&2
exit 1
fi
for var in ASC_KEY_ID ASC_ISSUER_ID ASC_KEY_PATH; do
if [ -z "${!var:-}" ]; then
echo "환경변수 $var 이 없습니다." >&2
exit 1
fi
done
echo "▸ 아카이브"
cd "$APP"
flutter build ipa --release --export-options-plist="$OPTIONS"
# flutter build ipa 가 아카이브와 내보내기를 함께 한다. 실패했을 때만 손으로 나눈다.
IPA="$(find "$EXPORT_DIR" -name '*.ipa' -maxdepth 1 | head -1)"
if [ -z "$IPA" ]; then
echo "IPA 를 찾지 못했습니다. 아카이브만 따로 시도합니다." >&2
xcodebuild -workspace ios/Runner.xcworkspace -scheme Runner \
-configuration Release -archivePath "$ARCHIVE" archive
xcodebuild -exportArchive -archivePath "$ARCHIVE" \
-exportOptionsPlist "$OPTIONS" -exportPath "$EXPORT_DIR"
IPA="$(find "$EXPORT_DIR" -name '*.ipa' -maxdepth 1 | head -1)"
fi
echo "▸ 검증"
xcrun altool --validate-app -f "$IPA" -t ios \
--apiKey "$ASC_KEY_ID" --apiIssuer "$ASC_ISSUER_ID"
echo "▸ 업로드"
xcrun altool --upload-app -f "$IPA" -t ios \
--apiKey "$ASC_KEY_ID" --apiIssuer "$ASC_ISSUER_ID"
cat <<'DONE'
업로드했습니다. 남은 것 둘.
1. App Store Connect 에서 처리가 끝나면(보통 10~30분) TestFlight 탭에 뜹니다.
2. 발주사 담당자 이메일을 외부 테스터로 초대하십시오.
수출 규정 응답은 Info.plist 의 ITSAppUsesNonExemptEncryption 이 대신합니다.
DONE