diff --git a/Tekken8 Frame Data/ci_scripts/ci_post_clone.sh b/Tekken8 Frame Data/ci_scripts/ci_post_clone.sh index 7e912c5..d3faa0c 100755 --- a/Tekken8 Frame Data/ci_scripts/ci_post_clone.sh +++ b/Tekken8 Frame Data/ci_scripts/ci_post_clone.sh @@ -4,6 +4,7 @@ set -eu : "${API_KEY:?Xcode Cloud API_KEY is missing}" : "${SUPABASE_URL:?Xcode Cloud SUPABASE_URL is missing}" +: "${FIREBASE_GOOGLE_SERVICE_INFO_PLIST_BASE64:?Xcode Cloud FIREBASE_GOOGLE_SERVICE_INFO_PLIST_BASE64 is missing}" : "${CI_PRIMARY_REPOSITORY_PATH:?Xcode Cloud CI_PRIMARY_REPOSITORY_PATH is missing}" fail() { @@ -43,12 +44,15 @@ validate_api_key config_directory="${CI_PRIMARY_REPOSITORY_PATH}/Tekken8 Frame Data/TK8" config_path="${config_directory}/Secrets.xcconfig" +firebase_config_path="${config_directory}/GoogleService-Info.plist" [ -d "$config_directory" ] || fail "TK8 configuration directory is missing." umask 077 temporary_config="$(mktemp "${config_directory}/Secrets.xcconfig.XXXXXX")" +temporary_firebase_config="$(mktemp "${config_directory}/GoogleService-Info.plist.XXXXXX")" cleanup() { [ -z "${temporary_config:-}" ] || rm -f "$temporary_config" + [ -z "${temporary_firebase_config:-}" ] || rm -f "$temporary_firebase_config" } trap cleanup EXIT @@ -59,4 +63,19 @@ chmod 600 "$temporary_config" mv -f "$temporary_config" "$config_path" temporary_config="" -echo "Generated TK8/Secrets.xcconfig from Xcode Cloud environment variables." +if ! /usr/bin/printf '%s' "$FIREBASE_GOOGLE_SERVICE_INFO_PLIST_BASE64" | /usr/bin/base64 -D > "$temporary_firebase_config"; then + fail "Xcode Cloud FIREBASE_GOOGLE_SERVICE_INFO_PLIST_BASE64 is not valid Base64." +fi + +if ! /usr/bin/plutil -lint "$temporary_firebase_config" >/dev/null; then + fail "Decoded Firebase GoogleService-Info.plist is invalid." +fi + +firebase_bundle_id="$(/usr/bin/plutil -extract BUNDLE_ID raw "$temporary_firebase_config" 2>/dev/null || true)" +[ "$firebase_bundle_id" = "com.moongoon.TK8" ] || fail "Firebase GoogleService-Info.plist has an unexpected BUNDLE_ID." + +chmod 600 "$temporary_firebase_config" +mv -f "$temporary_firebase_config" "$firebase_config_path" +temporary_firebase_config="" + +echo "Generated TK8 build configuration from Xcode Cloud environment variables." diff --git a/docs/architecture.md b/docs/architecture.md index 4d51d0a..03c9edb 100644 --- a/docs/architecture.md +++ b/docs/architecture.md @@ -198,7 +198,7 @@ frame_data_version 증가 - `TK8Tests`: 모델 decoding/hash, command tokenization, 한/영 번역, 기술 필터, 메모 CRUD와 백업 merge를 검증한다. - `SupabaseAPITests`: mock을 사용해 Supabase adapter 경계를 검증한다. - CI의 `swift.yml`은 SPM 의존성을 해석하고 `Tekken8 Frame Data` scheme을 Simulator 대상으로 clean build한다. 현재 workflow에는 테스트 실행 단계가 별도로 없다. -- Xcode Cloud release workflow는 `main` 변경 시 Archive한다. `ci_scripts/ci_post_clone.sh`가 `CI_PRIMARY_REPOSITORY_PATH`의 실제 checkout 위치를 기준으로 workflow의 secret 환경변수 `API_KEY`, `SUPABASE_URL`를 추적되지 않는 `TK8/Secrets.xcconfig`에 원자적으로 기록한 뒤 Archive가 진행된다. 둘 중 하나라도 누락되면 스크립트가 실패해 잘못된 설정의 배포를 막는다. 기존 설정 파일이 있어도 최종 권한은 `600`으로 강제한다. `API_KEY`는 `sb_publishable_` key 또는 `role=anon` legacy JWT만 허용하며, secret/service-role key는 사용하지 않는다. +- Xcode Cloud release workflow는 `main` 변경 시 Archive한다. `ci_scripts/ci_post_clone.sh`가 `CI_PRIMARY_REPOSITORY_PATH`의 실제 checkout 위치를 기준으로 workflow의 secret 환경변수 `API_KEY`, `SUPABASE_URL`를 추적되지 않는 `TK8/Secrets.xcconfig`에 원자적으로 기록한 뒤 Archive가 진행된다. 둘 중 하나라도 누락되면 스크립트가 실패해 잘못된 설정의 배포를 막는다. 기존 설정 파일이 있어도 최종 권한은 `600`으로 강제한다. `API_KEY`는 `sb_publishable_` key 또는 `role=anon` legacy JWT만 허용하며, secret/service-role key는 사용하지 않는다. Firebase Analytics 초기화에 필요한 `TK8/GoogleService-Info.plist`는 `FIREBASE_GOOGLE_SERVICE_INFO_PLIST_BASE64` secret environment variable을 post-clone 단계에서 Base64 복원한다. 복원 파일은 plist 문법과 `BUNDLE_ID=com.moongoon.TK8`을 검증하고 권한 `600`으로 원자적으로 교체한다. 따라서 Firebase configuration은 Git에 추적하지 않는다. - Core Data 관련 테스트는 in-memory persistent store를 사용한다. ## 구조 변경 시 동기화 대상