diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 0849a83..ac5acff 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,44 +1,59 @@ -name: CI +name: iOS CI on: - push: - branches: [ main, develop ] pull_request: - branches: [ main ] + branches: [main, feat/week1-foundations] + push: + tags: + - "v*" jobs: test: - name: Unit Tests - runs-on: macos-latest - + name: Run Tests + runs-on: macos-14 steps: - - name: Checkout - uses: actions/checkout@v4 + - name: Checkout code + uses: actions/checkout@v4 + + - name: Set up Ruby + uses: ruby/setup-ruby@v1 + with: + ruby-version: '3.3' + bundler-cache: true - - name: Select Xcode - run: sudo xcode-select -s /Applications/Xcode_16.0.app + - name: Select Xcode version + run: sudo xcode-select -s /Applications/Xcode_16.0.app || sudo xcode-select -s /Applications/Xcode.app - - name: Build and Test - run: | - xcodebuild -scheme "WorkOut Log" \ - -destination 'platform=iOS Simulator,name=iPhone 16' \ - clean build test + - name: Install dependencies + run: bundle install - ui-test: - name: UI Tests (Optional) - runs-on: macos-latest - if: github.event_name == 'push' && github.ref == 'refs/heads/main' + - name: Run tests + run: bundle exec fastlane tests + beta: + name: Deploy to TestFlight + if: startsWith(github.ref, 'refs/tags/v') + needs: test + runs-on: macos-14 steps: - - name: Checkout - uses: actions/checkout@v4 - - - name: Select Xcode - run: sudo xcode-select -s /Applications/Xcode_16.0.app - - - name: UI Test - run: | - xcodebuild -scheme "WorkOut Log" \ - -destination 'platform=iOS Simulator,name=iPhone 16' \ - clean build-for-testing test-without-building \ - -only-testing:WorkOut\ LogUITests \ No newline at end of file + - name: Checkout code + uses: actions/checkout@v4 + + - name: Set up Ruby + uses: ruby/setup-ruby@v1 + with: + ruby-version: '3.3' + bundler-cache: true + + - name: Select Xcode version + run: sudo xcode-select -s /Applications/Xcode_16.0.app || sudo xcode-select -s /Applications/Xcode.app + + - name: Install dependencies + run: bundle install + + - name: Build and upload to TestFlight + env: + ASC_KEY_ID: ${{ secrets.ASC_KEY_ID }} + ASC_ISSUER_ID: ${{ secrets.ASC_ISSUER_ID }} + ASC_KEY_CONTENT: ${{ secrets.ASC_KEY_CONTENT }} + run: bundle exec fastlane beta diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..c24ca07 --- /dev/null +++ b/.gitignore @@ -0,0 +1,98 @@ +# Xcode +# +# gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore + +## User settings +xcuserdata/ + +## compatibility with Xcode 8 and earlier (ignoring not required starting Xcode 9) +*.xcscmblueprint +*.xccheckout + +## compatibility with Xcode 3 and earlier (ignoring not required starting Xcode 4) +build/ +DerivedData/ +*.moved-aside +*.pbxuser +!default.pbxuser +*.mode1v3 +!default.mode1v3 +*.mode2v3 +!default.mode2v3 +*.perspectivev3 +!default.perspectivev3 + +## Obj-C/Swift specific +*.hmap + +## App packaging +*.ipa +*.dSYM.zip +*.dSYM + +## Playgrounds +timeline.xctimeline +playground.xcworkspace + +# Swift Package Manager +# +# Add this line if you want to avoid checking in source code from Swift Package Manager dependencies. +# Packages/ +# Package.pins +# Package.resolved +# *.xcodeproj +# +# Xcode automatically generates this directory with a .xcworkspacedata file and xcuserdata +# hence it is not needed unless you have added a package configuration file to your project +# .swiftpm + +.build/ + +# CocoaPods +# +# We recommend against adding the Pods directory to your .gitignore. However +# you should judge for yourself, the pros and cons are mentioned at: +# https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control +# +# Pods/ +# +# Add this line if you want to avoid checking in source code from the Xcode workspace +# *.xcworkspace + +# Carthage +# +# Add this line if you want to avoid checking in source code from Carthage dependencies. +# Carthage/Checkouts + +Carthage/Build/ + +# Accio dependency management +Dependencies/ +.accio/ + +# fastlane +# +# It is recommended to not store the screenshots in the git repo. +# Instead, use fastlane to re-generate the screenshots whenever they are needed. +# For more information about the recommended setup visit: +# https://docs.fastlane.tools/best-practices/source-control/#source-control + +fastlane/report.xml +fastlane/Preview.html +fastlane/screenshots/**/*.png +fastlane/test_output +fastlane/README.md + +# Code Injection +# +# After new code Injection tools there's a generated folder /iOSInjectionProject +# https://github.com/johnno1962/injectionforxcode + +iOSInjectionProject/ + +# Ruby / Bundler +vendor/bundle/ +.bundle/ + +# macOS +.DS_Store diff --git a/.ruby-version b/.ruby-version new file mode 100644 index 0000000..15a2799 --- /dev/null +++ b/.ruby-version @@ -0,0 +1 @@ +3.3.0 diff --git a/Docs/Fastlane_Guide.md b/Docs/Fastlane_Guide.md new file mode 100644 index 0000000..8200be9 --- /dev/null +++ b/Docs/Fastlane_Guide.md @@ -0,0 +1,887 @@ +# Fastlane 사용 가이드 — WorkOut Log + +**문서 버전:** 1.0.0 +**작성일:** 2025년 10월 19일 +**작성자:** Ben (오정석) with Claude +**대상:** iOS 개발자, CI/CD 운영자, 미래의 나 + +--- + +## 📚 목차 + +1. [Fastlane 개요](#fastlane-개요) +2. [환경 준비](#환경-준비) +3. [로컬 사용법](#로컬-사용법) +4. [Lane 상세 설명](#lane-상세-설명) +5. [버전 관리 플로우](#버전-관리-플로우) +6. [CI 워크플로우](#ci-워크플로우) +7. [Troubleshooting](#troubleshooting) +8. [보안 주의사항](#보안-주의사항) +9. [부록: Fastfile 주요 옵션](#부록-fastfile-주요-옵션) + +--- + +## Fastlane 개요 + +### 왜 Fastlane을 사용하는가? + +**반복 작업 자동화** +- 수동으로 Xcode에서 Archive → Export → Upload를 반복하는 대신, 한 줄 명령으로 완료 +- 빌드 번호 증가, 테스트 실행, 아카이브 생성, TestFlight 업로드를 단일 워크플로우로 통합 + +**일관성 보장** +- 팀원 간 동일한 빌드/배포 절차 보장 +- 로컬 개발 환경과 CI 환경에서 동일한 스크립트 사용 + +**CI/CD 연계** +- GitHub Actions와 완벽하게 통합 +- 태그 푸시만으로 자동 TestFlight 배포 + +--- + +## 환경 준비 + +### 1. Ruby & Bundler 설치 확인 + +```bash +# Ruby 버전 확인 (3.3.0 권장) +ruby --version + +# Bundler 설치 +gem install bundler +``` + +### 2. Fastlane 설치 + +```bash +# 프로젝트 루트 디렉토리로 이동 +cd "/Users/ojung/Documents/Dev/WorkOut Log" + +# 의존성 설치 (Gemfile 기준) +bundle install +``` + +**설치 확인:** +```bash +bundle exec fastlane --version +# 출력 예: fastlane 2.219.0 +``` + +### 3. Xcode 16 선택 + +```bash +# Xcode.app 경로 확인 +xcode-select -p + +# Xcode 16으로 전환 (필요 시) +sudo xcode-select -s /Applications/Xcode.app/Contents/Developer + +# 또는 Xcode 16.0.app이 별도로 있다면 +sudo xcode-select -s /Applications/Xcode_16.0.app/Contents/Developer +``` + +### 4. App Store Connect API Key 설정 + +#### 4.1 API Key 생성 (최초 1회) + +1. [App Store Connect → Users and Access → Keys](https://appstoreconnect.apple.com/access/api) 접속 +2. **"Generate API Key"** 클릭 +3. 이름: `GitHub Actions CI` (또는 원하는 이름) +4. 역할: **App Manager** 선택 +5. **Generate** 클릭 +6. `.p8` 파일 다운로드 (예: `AuthKey_AB12CD34EF.p8`) +7. **Key ID**와 **Issuer ID** 복사 (나중에 필요) + +⚠️ **주의:** `.p8` 파일은 한 번만 다운로드 가능. 안전한 곳에 백업하세요. + +#### 4.2 macOS 로컬 환경변수 설정 + +**방법 1: 터미널 세션에서 임시 설정** +```bash +# Key ID (예: AB12CD34EF) +export ASC_KEY_ID="YOUR_KEY_ID" + +# Issuer ID (UUID 형식) +export ASC_ISSUER_ID="YOUR_ISSUER_ID" + +# .p8 파일을 Base64로 인코딩 +export ASC_KEY_CONTENT=$(base64 -i /path/to/AuthKey_AB12CD34EF.p8) + +# 설정 확인 +echo $ASC_KEY_ID +``` + +**방법 2: ~/.zshrc 또는 ~/.bash_profile에 영구 설정** +```bash +# 파일 편집 +nano ~/.zshrc + +# 아래 내용 추가 (실제 값으로 교체) +export ASC_KEY_ID="AB12CD34EF" +export ASC_ISSUER_ID="12345678-1234-1234-1234-123456789abc" +export ASC_KEY_CONTENT="LS0tLS1CRUdJTi..." # Base64 인코딩된 값 + +# 저장 후 적용 +source ~/.zshrc +``` + +**⚠️ 보안:** 실제 운영에서는 `.p8` 파일을 안전한 곳에 보관하고, 환경변수는 로컬 머신에만 설정하세요. Git에 커밋하지 마세요! + +#### 4.3 GitHub Actions Secrets 설정 + +1. GitHub 저장소 → **Settings** → **Secrets and variables** → **Actions** +2. **"New repository secret"** 클릭 +3. 아래 3개의 Secret 추가: + +| Name | Value | 설명 | +|------|-------|------| +| `ASC_KEY_ID` | `AB12CD34EF` | API Key ID | +| `ASC_ISSUER_ID` | `12345678-1234-...` | Issuer ID (UUID) | +| `ASC_KEY_CONTENT` | `LS0tLS1CRUdJTi...` | Base64 인코딩된 .p8 파일 내용 | + +**Base64 인코딩 방법:** +```bash +base64 -i AuthKey_AB12CD34EF.p8 | pbcopy +# 클립보드에 복사됨 → GitHub Secret에 붙여넣기 +``` + +--- + +## 로컬 사용법 + +### 프로젝트 구성 확인 + +| 항목 | 값 | +|------|-----| +| **프로젝트 파일** | `WorkOut Log.xcodeproj` | +| **스킴** | `workout_log` | +| **번들 ID** | `com.Ben.WorkOut-Log` | + +**만약 `.xcworkspace` 사용 시:** +- `Fastfile`에서 `project: XCODEPROJ`를 `workspace: "WorkOut Log.xcworkspace"`로 변경 +- 예시: + ```ruby + gym( + workspace: "WorkOut Log.xcworkspace", + scheme: SCHEME, + # ... + ) + ``` + +### 명령어 실행 예시 + +#### 1. 테스트 실행 +```bash +bundle exec fastlane tests +``` +**출력 예시:** +``` +[✔] 🚀 +[15:30:45]: ----------------------- +[15:30:45]: --- Step: run_tests --- +[15:30:45]: ----------------------- +[15:30:45]: $ xcodebuild -scheme workout_log -project 'WorkOut Log.xcodeproj' ... +Test Suite 'All tests' passed at 2025-10-19 15:32:10. + Executed 42 tests, with 0 failures (0 unexpected) in 12.345 seconds +[15:32:11]: ✅ Tests passed successfully +``` + +#### 2. 빌드 번호 증가 +```bash +bundle exec fastlane bump_build +``` +**효과:** +- `CFBundleVersion` 값이 1 증가 (예: 5 → 6) +- 자동 커밋: `chore(ci): bump build number to 6` + +#### 3. 로컬 아카이브 생성 +```bash +bundle exec fastlane build +``` +**출력:** +- `build/` 디렉토리에 `.ipa` 파일 생성 +- `build/` 디렉토리에 `.dSYM.zip` 파일 생성 + +**용도:** App Store Connect 수동 업로드, 로컬 테스트 배포 + +#### 4. TestFlight 자동 배포 (beta) +```bash +# 환경변수 설정 확인 +echo $ASC_KEY_ID + +# 실행 +bundle exec fastlane beta +``` +**동작 단계:** +1. 빌드 번호 자동 증가 +2. 전체 테스트 실행 +3. App Store 아카이브 생성 +4. TestFlight 업로드 + +**출력 예시:** +``` +[15:40:12]: Incrementing build number to 7 +[15:40:15]: Running tests on iPhone 16... +[15:42:30]: ✅ All tests passed +[15:42:35]: Building archive... +[15:45:10]: Exporting IPA... +[15:46:20]: Uploading to TestFlight... +[15:48:50]: ✅ Successfully uploaded build 7 to TestFlight! +``` + +#### 5. App Store Connect 업로드 (release) +```bash +bundle exec fastlane release +``` +**효과:** +- 아카이브 생성 후 App Store Connect 업로드 +- `submit_for_review: false`로 설정되어 있어 자동 심사 제출은 안 됨 +- App Store Connect에서 수동으로 "Submit for Review" 클릭 필요 + +--- + +## Lane 상세 설명 + +### 1. `tests` — 단위 테스트 실행 + +**명령:** +```bash +bundle exec fastlane tests +``` + +**동작:** +- iPhone 16 시뮬레이터에서 `workout_log` 스킴 테스트 실행 +- 코드 커버리지 활성화 (`code_coverage: true`) +- `clean: true`로 빌드 캐시 초기화 + +**입력:** 없음 +**출력:** 테스트 결과 (성공/실패), 커버리지 리포트 + +**사전조건:** +- Xcode 16 선택됨 (`xcode-select`) +- 시뮬레이터 설치됨 + +**실패 시:** +- 로그에서 실패한 테스트 케이스 확인 +- Xcode에서 해당 테스트 직접 실행하여 디버깅 + +--- + +### 2. `bump_build` — 빌드 번호 증가 + +**명령:** +```bash +bundle exec fastlane bump_build +``` + +**동작:** +1. `CFBundleVersion` 값 1 증가 (예: `5` → `6`) +2. `WorkOut Log.xcodeproj/project.pbxproj` 파일 수정 +3. Git 커밋: `chore(ci): bump build number to 6` + +**입력:** 없음 +**출력:** 새 빌드 번호, Git 커밋 + +**사전조건:** +- Git 작업 트리가 깨끗해야 함 (uncommitted changes 없음) +- Git 저장소 초기화됨 + +**실패 시:** +- Uncommitted changes가 있으면 커밋 실패 +- 먼저 `git status` 확인 후 변경사항 커밋 또는 stash + +**롤백:** +```bash +git reset --hard HEAD~1 # 마지막 커밋 취소 +``` + +--- + +### 3. `build` — App Store 아카이브 생성 + +**명령:** +```bash +bundle exec fastlane build +``` + +**동작:** +- `workout_log` 스킴으로 App Store용 아카이브 빌드 +- Automatic Signing 사용 (Xcode 프로젝트 설정 따름) +- IPA 및 dSYM 파일 생성 + +**입력:** 없음 +**출력:** +- `build/WorkOut Log.ipa` +- `build/WorkOut Log.app.dSYM.zip` + +**사전조건:** +- Xcode 프로젝트에서 Automatic Signing 활성화 +- Apple Developer 계정 로그인 (Xcode → Settings → Accounts) + +**실패 시:** +- Code signing 오류 → Xcode에서 Signing & Capabilities 탭 확인 +- Provisioning profile 문제 → Xcode → Preferences → Accounts에서 "Download Manual Profiles" 클릭 + +--- + +### 4. `beta` — TestFlight 자동 배포 + +**명령:** +```bash +bundle exec fastlane beta +``` + +**동작 단계:** +1. **빌드 번호 증가** (`increment_build_number`) +2. **테스트 실행** (`run_tests`) +3. **아카이브 생성** (`gym`) +4. **TestFlight 업로드** (`pilot`) + +**입력:** +- 환경변수: `ASC_KEY_ID`, `ASC_ISSUER_ID`, `ASC_KEY_CONTENT` + +**출력:** +- TestFlight에 새 빌드 등록 +- 빌드 번호가 증가된 Git 커밋 (로컬에서만, 푸시는 안 됨) + +**사전조건:** +- App Store Connect API Key 환경변수 설정 +- 앱이 App Store Connect에 등록되어 있어야 함 +- 첫 빌드는 Xcode에서 수동으로 1회 업로드 후 Fastlane 사용 권장 + +**실패 시:** +- **Auth 오류:** `ASC_KEY_*` 환경변수 확인 +- **테스트 실패:** 먼저 `bundle exec fastlane tests` 단독 실행하여 디버깅 +- **업로드 실패:** App Store Connect 상태 확인 (점검 중인지) + +**TestFlight 반영 시간:** +- 업로드 완료 후 5~30분 내 TestFlight에서 확인 가능 +- "Processing"으로 표시되면 정상 + +--- + +### 5. `release` — App Store Connect 업로드 + +**명령:** +```bash +bundle exec fastlane release +``` + +**동작:** +1. App Store 아카이브 생성 +2. App Store Connect에 업로드 +3. **자동 심사 제출 안 함** (`submit_for_review: false`) + +**입력:** +- 환경변수: `ASC_KEY_ID`, `ASC_ISSUER_ID`, `ASC_KEY_CONTENT` + +**출력:** +- App Store Connect에 빌드 등록 (수동 심사 제출 대기 상태) + +**사전조건:** +- `beta` lane과 동일 +- 메타데이터(스크린샷, 설명 등)는 App Store Connect UI에서 별도 관리 + +**실패 시:** +- `beta` lane과 동일한 트러블슈팅 적용 + +**다음 단계:** +1. [App Store Connect](https://appstoreconnect.apple.com) 접속 +2. 앱 선택 → 버전 선택 +3. **"Submit for Review"** 클릭 +4. 심사 질문 응답 후 제출 + +--- + +## 버전 관리 플로우 + +### 버전 번호 정책 + +**CFBundleShortVersionString (마케팅 버전):** +- 형식: `MAJOR.MINOR.PATCH` (예: `1.0.0`) +- **수동 관리:** Xcode 또는 `agvtool new-marketing-version X.Y.Z`로 변경 +- 의미: + - `MAJOR`: 호환성 깨지는 변경 + - `MINOR`: 기능 추가 + - `PATCH`: 버그 수정 + +**CFBundleVersion (빌드 번호):** +- 형식: 정수 (예: `1`, `2`, `3`, ...) +- **자동 관리:** `bundle exec fastlane bump_build` 또는 `beta` lane 실행 시 자동 증가 +- App Store는 빌드 번호가 이전 빌드보다 커야 함 + +### 릴리스 플로우 예시 + +#### 시나리오 1: 첫 릴리스 (1.0.0) + +```bash +# 1. 마케팅 버전 설정 (Xcode에서 수동 또는) +agvtool new-marketing-version 1.0.0 + +# 2. Beta 배포 (빌드 번호 자동 증가) +bundle exec fastlane beta + +# 3. TestFlight에서 내부 테스트 + +# 4. 정식 릴리스 준비 +bundle exec fastlane release + +# 5. App Store Connect에서 Submit for Review +``` + +#### 시나리오 2: 버그 수정 릴리스 (1.0.0 → 1.0.1) + +```bash +# 1. 버그 수정 후 마케팅 버전 변경 +agvtool new-marketing-version 1.0.1 + +# 2. Git 커밋 +git add -A +git commit -m "fix: critical bug in trends chart" + +# 3. 태그 생성 (CI에서 자동 TestFlight 배포) +git tag v1.0.1 +git push origin v1.0.1 + +# 4. GitHub Actions가 자동으로 beta lane 실행 +# 5. TestFlight 확인 후 App Store 제출 +``` + +### 태깅 가이드 + +**형식:** +``` +v{MAJOR}.{MINOR}.{PATCH} +``` + +**예시:** +```bash +# 버전 태그 생성 +git tag v1.0.0 + +# 태그 푸시 (CI 트리거) +git push origin v1.0.0 + +# 결과: GitHub Actions에서 beta lane 자동 실행 → TestFlight 업로드 +``` + +**태그 목록 확인:** +```bash +git tag -l +``` + +**태그 삭제 (실수로 생성 시):** +```bash +# 로컬 태그 삭제 +git tag -d v1.0.0 + +# 리모트 태그 삭제 +git push origin :refs/tags/v1.0.0 +``` + +--- + +## CI 워크플로우 + +### 전체 플로우 다이어그램 + +``` +Developer (local) + │ + ├─── PR to main ────────────────────┐ + │ │ + │ GitHub Actions + │ │ + │ ┌────────▼────────┐ + │ │ test job │ + │ │ │ + │ │ 1. bundle install + │ │ 2. fastlane tests + │ │ │ + │ └─────────────────┘ + │ │ + │ ▼ + │ ✅ Tests Passed + │ + └─── git tag v1.0.0 ────────────────┐ + git push origin v1.0.0 │ + │ + GitHub Actions + │ + ┌────────▼────────┐ + │ beta job │ + │ │ + │ 1. Run tests │ + │ 2. fastlane beta│ + │ │ + └────────┬────────┘ + │ + ▼ + TestFlight + │ + ▼ + Internal Testers + │ + ▼ + App Store Connect + │ + ▼ + Submit for Review (manual) + │ + ▼ + App Store +``` + +### GitHub Actions 트리거 조건 + +**1. Pull Request → `test` job 실행** +```yaml +on: + pull_request: + branches: [main, feat/week1-foundations] +``` + +**동작:** +- PR 생성/업데이트 시 자동 실행 +- `bundle exec fastlane tests` 실행 +- 테스트 실패 시 PR merge 블록 권장 + +**2. Tag Push → `beta` job 실행** +```yaml +on: + push: + tags: + - "v*" +``` + +**동작:** +- `v*` 패턴 태그 푸시 시 실행 (예: `v1.0.0`, `v2.1.3`) +- `test` job이 먼저 실행되고 성공해야 `beta` job 실행 (`needs: test`) +- `bundle exec fastlane beta` 실행 +- TestFlight 자동 업로드 + +### GitHub Secrets 설정 체크리스트 + +| Secret 이름 | 설명 | 확인 방법 | +|-------------|------|-----------| +| `ASC_KEY_ID` | App Store Connect API Key ID | 영문+숫자 10자 (예: `AB12CD34EF`) | +| `ASC_ISSUER_ID` | Issuer ID | UUID 형식 (예: `12345678-1234-...`) | +| `ASC_KEY_CONTENT` | Base64 인코딩된 .p8 파일 | `LS0tLS1CRUdJTi...`로 시작 | + +**설정 경로:** +``` +GitHub 저장소 → Settings → Secrets and variables → Actions → Repository secrets +``` + +--- + +## Troubleshooting + +### 1. Login/Auth 오류 + +#### 증상: +``` +[!] Could not authenticate with App Store Connect +``` + +#### 원인: +- 환경변수 미설정 또는 잘못된 값 +- API Key 만료 또는 권한 부족 +- Base64 인코딩 오류 + +#### 해결: + +**Step 1: 환경변수 확인** +```bash +echo $ASC_KEY_ID +echo $ASC_ISSUER_ID +echo $ASC_KEY_CONTENT | head -c 50 # 앞 50자만 출력 +``` + +**Step 2: Base64 재인코딩** +```bash +# 올바른 방법 +base64 -i AuthKey_AB12CD34EF.p8 | pbcopy + +# ❌ 잘못된 방법 (줄바꿈 포함됨) +cat AuthKey_AB12CD34EF.p8 | base64 +``` + +**Step 3: API Key 권한 확인** +- [App Store Connect → Users and Access → Keys](https://appstoreconnect.apple.com/access/api) +- 해당 Key의 **Access** 열에서 "App Manager" 또는 "Admin" 확인 +- "Revoked" 상태면 새로 생성 + +**Step 4: GitHub Actions에서 확인** +```yaml +# .github/workflows/ci.yml에서 디버깅 추가 +- name: Debug secrets + run: | + echo "ASC_KEY_ID length: ${#ASC_KEY_ID}" + echo "ASC_ISSUER_ID length: ${#ASC_ISSUER_ID}" + echo "ASC_KEY_CONTENT length: ${#ASC_KEY_CONTENT}" +``` + +--- + +### 2. 코드 서명 실패 + +#### 증상: +``` +error: Automatic signing failed +``` + +#### 체크리스트: + +**□ Xcode 프로젝트 설정 확인** +``` +1. Xcode 열기 +2. 프로젝트 네비게이터에서 "WorkOut Log" 선택 +3. Targets → workout_log → Signing & Capabilities +4. ✅ "Automatically manage signing" 체크 +5. Team 선택됨 (본인 Apple Developer 계정) +``` + +**□ Apple Developer 계정 로그인** +``` +1. Xcode → Settings → Accounts +2. Apple ID 추가 (kei7659@daum.net) +3. "Download Manual Profiles" 클릭 +``` + +**□ Provisioning Profile 갱신** +```bash +# Fastlane 사용 +bundle exec fastlane sigh renew +``` + +**□ Bundle ID 일치 확인** +- Xcode: `com.Ben.WorkOut-Log` +- App Store Connect: `com.Ben.WorkOut-Log` +- `fastlane/Appfile`: `com.Ben.WorkOut-Log` + +--- + +### 3. Xcode Path 오류 + +#### 증상: +``` +xcode-select: error: tool 'xcodebuild' requires Xcode +``` + +#### 해결: +```bash +# 현재 Xcode 경로 확인 +xcode-select -p + +# 올바른 경로로 설정 +sudo xcode-select -s /Applications/Xcode.app/Contents/Developer + +# 또는 Xcode 16 버전 지정 +sudo xcode-select -s /Applications/Xcode_16.0.app/Contents/Developer + +# Xcode License 동의 (필요 시) +sudo xcodebuild -license accept +``` + +--- + +### 4. TestFlight 업로드 지연 + +#### 증상: +- `pilot` 명령이 완료되었지만 TestFlight에 빌드가 안 보임 + +#### 정상 상태: +- 업로드 후 **5~30분** 소요 (Apple의 처리 시간) +- App Store Connect → TestFlight → iOS Builds에서 "Processing" 상태 확인 + +#### 로그 확인 지점: +```bash +# Fastlane 로그에서 확인 +[15:48:50]: Successfully uploaded package to App Store Connect +[15:48:50]: ✅ Successfully uploaded build 7 to TestFlight! +``` + +#### App Store Connect 확인: +1. [App Store Connect](https://appstoreconnect.apple.com) 로그인 +2. 앱 선택 → **TestFlight** 탭 +3. **iOS Builds** 섹션에서 "Processing" 또는 "Ready to Submit" 확인 + +#### 비정상 상태 (에러): +``` +[!] Error uploading ipa file: +The provided entity includes an attribute with a value that has already been used +``` + +**원인:** 동일한 빌드 번호 재업로드 시도 +**해결:** `bundle exec fastlane bump_build` 실행 후 재시도 + +--- + +### 5. 테스트 실패 + +#### 증상: +``` +Test Suite 'workout_logTests' failed +``` + +#### 해결: + +**Step 1: 로컬에서 Xcode로 테스트** +``` +1. Xcode 열기 +2. Product → Test (Cmd+U) +3. 실패한 테스트 케이스 확인 +``` + +**Step 2: 특정 테스트만 실행** +```bash +xcodebuild test \ + -scheme workout_log \ + -destination 'platform=iOS Simulator,name=iPhone 16' \ + -only-testing:workout_logTests/WeeklyBucketTests +``` + +**Step 3: 시뮬레이터 초기화** +```bash +# 모든 시뮬레이터 종료 +xcrun simctl shutdown all + +# 시뮬레이터 초기화 +xcrun simctl erase all +``` + +--- + +## 보안 주의사항 + +### 🔒 절대 금지 사항 + +**❌ .p8 파일 Git 커밋 금지** +```bash +# .gitignore에 반드시 추가 +*.p8 +AuthKey_*.p8 +``` + +**❌ 환경변수를 코드에 하드코딩 금지** +```ruby +# ❌ 잘못된 예시 +api_key = app_store_connect_api_key( + key_id: "AB12CD34EF", # 하드코딩 금지! + issuer_id: "12345678-1234-...", + key_content: "LS0tLS1CRUdJTi..." +) + +# ✅ 올바른 예시 +api_key = app_store_connect_api_key( + key_id: ENV["ASC_KEY_ID"], + issuer_id: ENV["ASC_ISSUER_ID"], + key_content: Base64.decode64(ENV["ASC_KEY_CONTENT"]) +) +``` + +**❌ GitHub Actions 로그에 Secret 노출 금지** +```yaml +# ❌ 잘못된 예시 +- name: Debug + run: echo "Key: ${{ secrets.ASC_KEY_CONTENT }}" + +# ✅ 올바른 예시 (길이만 확인) +- name: Debug + run: echo "Key length: ${#ASC_KEY_CONTENT}" +``` + +### ✅ 권장 사항 + +**1. .p8 파일 안전 보관** +- 1Password, Bitwarden 등 비밀번호 관리자에 저장 +- 로컬 머신의 암호화된 폴더에 백업 + +**2. API Key 권한 최소화** +- **Admin** 대신 **App Manager** 역할 사용 +- 필요 없는 Key는 즉시 Revoke + +**3. 정기 Key 로테이션** +- 6개월마다 API Key 재생성 권장 +- 이전 Key는 Revoke 처리 + +**4. 팀원 간 Secret 공유 방법** +- Slack DM으로 전송 금지 +- 1Password Shared Vault 사용 권장 + +--- + +## 부록: Fastfile 주요 옵션 + +### `run_tests` 액션 + +| 옵션 | 설명 | 예시 | +|------|------|------| +| `project` | Xcode 프로젝트 파일 경로 | `"WorkOut Log.xcodeproj"` | +| `workspace` | Xcode 워크스페이스 경로 (CocoaPods 사용 시) | `"WorkOut Log.xcworkspace"` | +| `scheme` | 빌드 스킴 | `"workout_log"` | +| `device` | 시뮬레이터 이름 | `"iPhone 16"`, `"iPhone SE (3rd generation)"` | +| `clean` | 빌드 전 clean 여부 | `true` / `false` | +| `code_coverage` | 코드 커버리지 활성화 | `true` / `false` | +| `only_testing` | 특정 테스트만 실행 | `["workout_logTests/WeeklyBucketTests"]` | + +### `gym` 액션 (Archive & Export) + +| 옵션 | 설명 | 예시 | +|------|------|------| +| `project` | Xcode 프로젝트 파일 경로 | `"WorkOut Log.xcodeproj"` | +| `workspace` | Xcode 워크스페이스 경로 | `"WorkOut Log.xcworkspace"` | +| `scheme` | 빌드 스킴 | `"workout_log"` | +| `export_method` | Export 방식 | `"app-store"`, `"ad-hoc"`, `"development"` | +| `clean` | 빌드 전 clean 여부 | `true` / `false` | +| `output_directory` | IPA 출력 디렉토리 | `"build"` | +| `include_symbols` | dSYM 파일 포함 | `true` / `false` | +| `include_bitcode` | Bitcode 포함 (iOS 14+ 불필요) | `false` | + +### `pilot` 액션 (TestFlight Upload) + +| 옵션 | 설명 | 예시 | +|------|------|------| +| `api_key` | App Store Connect API Key 객체 | `api_key` 변수 전달 | +| `skip_waiting_for_build_processing` | 업로드 후 Processing 완료 대기 안 함 | `true` (권장) | +| `changelog` | TestFlight 릴리스 노트 | `"Bug fixes and improvements"` | +| `distribute_external` | 외부 테스터에게 자동 배포 | `false` (수동 권장) | +| `groups` | 배포할 테스터 그룹 | `["Internal Testers"]` | + +### `deliver` 액션 (App Store Upload) + +| 옵션 | 설명 | 예시 | +|------|------|------| +| `api_key` | App Store Connect API Key 객체 | `api_key` 변수 전달 | +| `submit_for_review` | 자동 심사 제출 | `false` (수동 권장) | +| `skip_metadata` | 메타데이터 업로드 생략 | `true` | +| `skip_screenshots` | 스크린샷 업로드 생략 | `true` | +| `force` | 기존 빌드 덮어쓰기 | `true` | +| `automatic_release` | 심사 승인 후 자동 릴리스 | `false` | + +### `increment_build_number` 액션 + +| 옵션 | 설명 | 예시 | +|------|------|------| +| `xcodeproj` | Xcode 프로젝트 경로 | `"WorkOut Log.xcodeproj"` | +| `build_number` | 특정 빌드 번호 설정 (옵션) | `42` | + +**기본 동작:** 현재 빌드 번호 + 1 + +--- + +## 참고 문서 + +- [Fastlane 공식 문서](https://docs.fastlane.tools/) +- [App Store Connect API](https://developer.apple.com/documentation/appstoreconnectapi) +- [Xcode Build Settings Reference](https://developer.apple.com/documentation/xcode/build-settings-reference) +- [GitHub Actions Documentation](https://docs.github.com/en/actions) + +--- + +**문서 버전:** 1.0.0 +**최종 수정:** 2025년 10월 19일 +**작성자:** Ben (오정석) +**문의:** kei7659@daum.net + diff --git a/Gemfile b/Gemfile new file mode 100644 index 0000000..7a118b4 --- /dev/null +++ b/Gemfile @@ -0,0 +1,3 @@ +source "https://rubygems.org" + +gem "fastlane" diff --git a/Gemfile.lock b/Gemfile.lock new file mode 100644 index 0000000..af73bfb --- /dev/null +++ b/Gemfile.lock @@ -0,0 +1,229 @@ +GEM + remote: https://rubygems.org/ + specs: + CFPropertyList (3.0.7) + base64 + nkf + rexml + addressable (2.8.7) + public_suffix (>= 2.0.2, < 7.0) + artifactory (3.0.17) + atomos (0.1.3) + aws-eventstream (1.4.0) + aws-partitions (1.1174.0) + aws-sdk-core (3.233.0) + aws-eventstream (~> 1, >= 1.3.0) + aws-partitions (~> 1, >= 1.992.0) + aws-sigv4 (~> 1.9) + base64 + bigdecimal + jmespath (~> 1, >= 1.6.1) + logger + aws-sdk-kms (1.114.0) + aws-sdk-core (~> 3, >= 3.231.0) + aws-sigv4 (~> 1.5) + aws-sdk-s3 (1.200.0) + aws-sdk-core (~> 3, >= 3.231.0) + aws-sdk-kms (~> 1) + aws-sigv4 (~> 1.5) + aws-sigv4 (1.12.1) + aws-eventstream (~> 1, >= 1.0.2) + babosa (1.0.4) + base64 (0.3.0) + bigdecimal (3.3.1) + claide (1.1.0) + colored (1.2) + colored2 (3.1.2) + commander (4.6.0) + highline (~> 2.0.0) + declarative (0.0.20) + digest-crc (0.7.0) + rake (>= 12.0.0, < 14.0.0) + domain_name (0.6.20240107) + dotenv (2.8.1) + emoji_regex (3.2.3) + excon (0.112.0) + faraday (1.10.4) + faraday-em_http (~> 1.0) + faraday-em_synchrony (~> 1.0) + faraday-excon (~> 1.1) + faraday-httpclient (~> 1.0) + faraday-multipart (~> 1.0) + faraday-net_http (~> 1.0) + faraday-net_http_persistent (~> 1.0) + faraday-patron (~> 1.0) + faraday-rack (~> 1.0) + faraday-retry (~> 1.0) + ruby2_keywords (>= 0.0.4) + faraday-cookie_jar (0.0.7) + faraday (>= 0.8.0) + http-cookie (~> 1.0.0) + faraday-em_http (1.0.0) + faraday-em_synchrony (1.0.1) + faraday-excon (1.1.0) + faraday-httpclient (1.0.1) + faraday-multipart (1.1.1) + multipart-post (~> 2.0) + faraday-net_http (1.0.2) + faraday-net_http_persistent (1.2.0) + faraday-patron (1.0.0) + faraday-rack (1.0.0) + faraday-retry (1.0.3) + faraday_middleware (1.2.1) + faraday (~> 1.0) + fastimage (2.4.0) + fastlane (2.228.0) + CFPropertyList (>= 2.3, < 4.0.0) + addressable (>= 2.8, < 3.0.0) + artifactory (~> 3.0) + aws-sdk-s3 (~> 1.0) + babosa (>= 1.0.3, < 2.0.0) + bundler (>= 1.12.0, < 3.0.0) + colored (~> 1.2) + commander (~> 4.6) + dotenv (>= 2.1.1, < 3.0.0) + emoji_regex (>= 0.1, < 4.0) + excon (>= 0.71.0, < 1.0.0) + faraday (~> 1.0) + faraday-cookie_jar (~> 0.0.6) + faraday_middleware (~> 1.0) + fastimage (>= 2.1.0, < 3.0.0) + fastlane-sirp (>= 1.0.0) + gh_inspector (>= 1.1.2, < 2.0.0) + google-apis-androidpublisher_v3 (~> 0.3) + google-apis-playcustomapp_v1 (~> 0.1) + google-cloud-env (>= 1.6.0, < 2.0.0) + google-cloud-storage (~> 1.31) + highline (~> 2.0) + http-cookie (~> 1.0.5) + json (< 3.0.0) + jwt (>= 2.1.0, < 3) + mini_magick (>= 4.9.4, < 5.0.0) + multipart-post (>= 2.0.0, < 3.0.0) + naturally (~> 2.2) + optparse (>= 0.1.1, < 1.0.0) + plist (>= 3.1.0, < 4.0.0) + rubyzip (>= 2.0.0, < 3.0.0) + security (= 0.1.5) + simctl (~> 1.6.3) + terminal-notifier (>= 2.0.0, < 3.0.0) + terminal-table (~> 3) + tty-screen (>= 0.6.3, < 1.0.0) + tty-spinner (>= 0.8.0, < 1.0.0) + word_wrap (~> 1.0.0) + xcodeproj (>= 1.13.0, < 2.0.0) + xcpretty (~> 0.4.1) + xcpretty-travis-formatter (>= 0.0.3, < 2.0.0) + fastlane-sirp (1.0.0) + sysrandom (~> 1.0) + gh_inspector (1.1.3) + google-apis-androidpublisher_v3 (0.54.0) + google-apis-core (>= 0.11.0, < 2.a) + google-apis-core (0.11.3) + addressable (~> 2.5, >= 2.5.1) + googleauth (>= 0.16.2, < 2.a) + httpclient (>= 2.8.1, < 3.a) + mini_mime (~> 1.0) + representable (~> 3.0) + retriable (>= 2.0, < 4.a) + rexml + google-apis-iamcredentials_v1 (0.17.0) + google-apis-core (>= 0.11.0, < 2.a) + google-apis-playcustomapp_v1 (0.13.0) + google-apis-core (>= 0.11.0, < 2.a) + google-apis-storage_v1 (0.31.0) + google-apis-core (>= 0.11.0, < 2.a) + google-cloud-core (1.8.0) + google-cloud-env (>= 1.0, < 3.a) + google-cloud-errors (~> 1.0) + google-cloud-env (1.6.0) + faraday (>= 0.17.3, < 3.0) + google-cloud-errors (1.5.0) + google-cloud-storage (1.47.0) + addressable (~> 2.8) + digest-crc (~> 0.4) + google-apis-iamcredentials_v1 (~> 0.1) + google-apis-storage_v1 (~> 0.31.0) + google-cloud-core (~> 1.6) + googleauth (>= 0.16.2, < 2.a) + mini_mime (~> 1.0) + googleauth (1.8.1) + faraday (>= 0.17.3, < 3.a) + jwt (>= 1.4, < 3.0) + multi_json (~> 1.11) + os (>= 0.9, < 2.0) + signet (>= 0.16, < 2.a) + highline (2.0.3) + http-cookie (1.0.8) + domain_name (~> 0.5) + httpclient (2.9.0) + mutex_m + jmespath (1.6.2) + json (2.15.1) + jwt (2.10.2) + base64 + logger (1.7.0) + mini_magick (4.13.2) + mini_mime (1.1.5) + multi_json (1.17.0) + multipart-post (2.4.1) + mutex_m (0.3.0) + nanaimo (0.4.0) + naturally (2.3.0) + nkf (0.2.0) + optparse (0.6.0) + os (1.1.4) + plist (3.7.2) + public_suffix (6.0.2) + rake (13.3.0) + representable (3.2.0) + declarative (< 0.1.0) + trailblazer-option (>= 0.1.1, < 0.2.0) + uber (< 0.2.0) + retriable (3.1.2) + rexml (3.4.4) + rouge (3.28.0) + ruby2_keywords (0.0.5) + rubyzip (2.4.1) + security (0.1.5) + signet (0.21.0) + addressable (~> 2.8) + faraday (>= 0.17.5, < 3.a) + jwt (>= 1.5, < 4.0) + multi_json (~> 1.10) + simctl (1.6.10) + CFPropertyList + naturally + sysrandom (1.0.5) + terminal-notifier (2.0.0) + terminal-table (3.0.2) + unicode-display_width (>= 1.1.1, < 3) + trailblazer-option (0.1.2) + tty-cursor (0.7.1) + tty-screen (0.8.2) + tty-spinner (0.9.3) + tty-cursor (~> 0.7) + uber (0.1.0) + unicode-display_width (2.6.0) + word_wrap (1.0.0) + xcodeproj (1.27.0) + CFPropertyList (>= 2.3.3, < 4.0) + atomos (~> 0.1.3) + claide (>= 1.0.2, < 2.0) + colored2 (~> 3.1) + nanaimo (~> 0.4.0) + rexml (>= 3.3.6, < 4.0) + xcpretty (0.4.1) + rouge (~> 3.28.0) + xcpretty-travis-formatter (1.0.1) + xcpretty (~> 0.2, >= 0.0.7) + +PLATFORMS + arm64-darwin-23 + ruby + +DEPENDENCIES + fastlane + +BUNDLED WITH + 2.5.6 diff --git a/README.md b/README.md index eb0b240..6836541 100644 --- a/README.md +++ b/README.md @@ -434,6 +434,21 @@ cd Docs && ./export.sh --- +## 🚀 CI/CD & Fastlane + +이 프로젝트는 **Fastlane**을 사용하여 빌드, 테스트, TestFlight 배포를 자동화합니다. PR마다 자동으로 테스트가 실행되며, `v*` 태그를 푸시하면 GitHub Actions가 TestFlight에 자동으로 배포합니다. + +**📘 자세한 가이드**: [Docs/Fastlane_Guide.md](Docs/Fastlane_Guide.md) (환경 설정, 로컬 실행, CI 워크플로우, 트러블슈팅 포함) + +**로컬 실행 (3 commands):** +```bash +bundle install # Ruby 의존성 설치 +bundle exec fastlane tests # 테스트 실행 +bundle exec fastlane beta # TestFlight 배포 (App Store Connect API 키 필요) +``` + +--- + ## 🤝 Contributing ### Development Workflow diff --git a/WorkOut Log.xcodeproj/xcshareddata/xcschemes/WorkOut Log.xcscheme b/WorkOut Log.xcodeproj/xcshareddata/xcschemes/WorkOut Log.xcscheme new file mode 100644 index 0000000..003b1df --- /dev/null +++ b/WorkOut Log.xcodeproj/xcshareddata/xcschemes/WorkOut Log.xcscheme @@ -0,0 +1,102 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/WorkOut Log.xcodeproj/xcuserdata/ojung.xcuserdatad/xcschemes/xcschememanagement.plist b/WorkOut Log.xcodeproj/xcuserdata/ojung.xcuserdatad/xcschemes/xcschememanagement.plist index cbc93ea..6e0cdca 100644 --- a/WorkOut Log.xcodeproj/xcuserdata/ojung.xcuserdatad/xcschemes/xcschememanagement.plist +++ b/WorkOut Log.xcodeproj/xcuserdata/ojung.xcuserdatad/xcschemes/xcschememanagement.plist @@ -10,5 +10,23 @@ 0 + SuppressBuildableAutocreation + + 99DC4DB62E9E2850006B8C3F + + primary + + + 99DC4DC52E9E2853006B8C3F + + primary + + + 99DC4DCF2E9E2853006B8C3F + + primary + + + diff --git a/WorkOut Log/Presentation/Resources/Assets.xcassets/WorkoutLog_Logo.imageset/Contents.json b/WorkOut Log/Presentation/Resources/Assets.xcassets/WorkoutLog_Logo.imageset/Contents.json new file mode 100644 index 0000000..caca957 --- /dev/null +++ b/WorkOut Log/Presentation/Resources/Assets.xcassets/WorkoutLog_Logo.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "WorkoutLog_Logo_Blue2563EB_1024.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/WorkOut Log/Presentation/Resources/Assets.xcassets/WorkoutLog_Logo.imageset/WorkoutLog_Logo_Blue2563EB_1024.png b/WorkOut Log/Presentation/Resources/Assets.xcassets/WorkoutLog_Logo.imageset/WorkoutLog_Logo_Blue2563EB_1024.png new file mode 100644 index 0000000..3a681ba Binary files /dev/null and b/WorkOut Log/Presentation/Resources/Assets.xcassets/WorkoutLog_Logo.imageset/WorkoutLog_Logo_Blue2563EB_1024.png differ diff --git a/WorkOut Log/Presentation/Resources/LaunchScreen.storyboard b/WorkOut Log/Presentation/Resources/LaunchScreen.storyboard index 92e0739..775c1a5 100644 --- a/WorkOut Log/Presentation/Resources/LaunchScreen.storyboard +++ b/WorkOut Log/Presentation/Resources/LaunchScreen.storyboard @@ -16,32 +16,49 @@ - - - + + + + + + - + + + - - - + + + + + + - + - + + + + diff --git a/WorkOut-Log-Info.plist b/WorkOut-Log-Info.plist index 0c67376..bc11256 100644 --- a/WorkOut-Log-Info.plist +++ b/WorkOut-Log-Info.plist @@ -1,5 +1,8 @@ - + + ITSAppUsesNonExemptEncryption + + diff --git a/fastlane/Appfile b/fastlane/Appfile new file mode 100644 index 0000000..1075cf4 --- /dev/null +++ b/fastlane/Appfile @@ -0,0 +1,15 @@ +# Fastlane Appfile +# Bundle identifier for your app +app_identifier("com.Ben.WorkOut-Log") + +# Your Apple Developer account email +apple_id("kei7659@daum.net") + +# App Store Connect Team ID (optional if you have only one team) +# itc_team_id("YOUR_ITC_TEAM_ID") + +# Developer Portal Team ID (optional if you have only one team) +# team_id("YOUR_TEAM_ID") + +# For more information about the Appfile, see: +# https://docs.fastlane.tools/advanced/#appfile diff --git a/fastlane/Fastfile b/fastlane/Fastfile new file mode 100644 index 0000000..5399832 --- /dev/null +++ b/fastlane/Fastfile @@ -0,0 +1,272 @@ +# Fastlane Fastfile for WorkOut Log +# This file contains the configuration for automating build, test, and deployment + +default_platform(:ios) + +platform :ios do + # ======================================== + # CONFIGURATION + # ======================================== + + SCHEME = "workout_log" + XCODEPROJ = "WorkOut Log.xcodeproj" + OUTPUT_DIR = "build" + + # ======================================== + # LANES + # ======================================== + + desc "Run unit and UI tests" + lane :tests do + run_tests( + project: XCODEPROJ, + scheme: SCHEME, + device: "iPhone 16", + clean: true, + code_coverage: true + ) + end + + desc "Increment build number (CFBundleVersion) and commit" + lane :bump_build do + increment_build_number(xcodeproj: XCODEPROJ) + + # Commit the version bump + build_number = get_build_number(xcodeproj: XCODEPROJ) + commit_version_bump( + message: "chore(ci): bump build number to #{build_number}", + xcodeproj: XCODEPROJ + ) + end + + desc "Build archive for App Store distribution (automatic signing)" + lane :build do + gym( + project: XCODEPROJ, + scheme: SCHEME, + export_method: "app-store", + skip_package_pkg: true, + include_bitcode: false, + include_symbols: true, + clean: true, + silent: false, + output_directory: OUTPUT_DIR, + export_options: { + signingStyle: "automatic", + teamID: "8CKPNKA3V9" + } + ) + end + + desc "Upload to TestFlight (beta testing)" + lane :beta do + # Authenticate with App Store Connect API + api_key = app_store_connect_api_key( + key_id: ENV["ASC_KEY_ID"], + issuer_id: ENV["ASC_ISSUER_ID"], + key_content: Base64.decode64(ENV["ASC_KEY_CONTENT"]), + is_key_content_base64: false + ) + + # Get current build number (manual versioning - increment in Xcode) + build_number = get_info_plist_value( + path: "WorkOut-Log-Info.plist", + key: "CFBundleVersion" + ) + + # Skip tests for now (run manually or in CI) + # run_tests( + # project: XCODEPROJ, + # scheme: SCHEME, + # device: "iPhone 16", + # clean: true + # ) + + # Build archive (using Xcode automatic signing) + gym( + project: XCODEPROJ, + scheme: SCHEME, + export_method: "app-store", + clean: true, + output_directory: OUTPUT_DIR, + include_symbols: true, + export_options: { + signingStyle: "automatic", + teamID: "8CKPNKA3V9" + } + ) + + # Upload to TestFlight + pilot( + api_key: api_key, + skip_waiting_for_build_processing: true, + changelog: "Beta build #{build_number}\n\n- Bug fixes and improvements" + ) + + UI.success("✅ Successfully uploaded build #{build_number} to TestFlight!") + end + + desc "Submit to App Store for review with Korean metadata" + lane :release do + # Authenticate with App Store Connect API + api_key = app_store_connect_api_key( + key_id: ENV["ASC_KEY_ID"], + issuer_id: ENV["ASC_ISSUER_ID"], + key_content: Base64.decode64(ENV["ASC_KEY_CONTENT"]), + is_key_content_base64: false + ) + + # Verify export compliance before building + check_export_compliance + + # Get current version info + version = get_version_number(xcodeproj: XCODEPROJ, target: "WorkOut Log") + build_number = get_info_plist_value( + path: "WorkOut-Log-Info.plist", + key: "CFBundleVersion" + ) + + UI.header("📦 Building version #{version} (#{build_number}) for App Store") + + # Build archive (using Xcode automatic signing) + gym( + project: XCODEPROJ, + scheme: SCHEME, + export_method: "app-store", + clean: true, + output_directory: OUTPUT_DIR, + include_symbols: true, + export_options: { + signingStyle: "automatic", + teamID: "8CKPNKA3V9" + } + ) + + UI.header("📤 Uploading to App Store Connect and submitting for review") + + # Upload to App Store Connect and submit for review + deliver( + api_key: api_key, + submit_for_review: true, + force: true, + skip_metadata: true, # Skip metadata upload to avoid localization conflicts + skip_screenshots: true, # Skip screenshots upload for now + automatic_release: false, + precheck_include_in_app_purchases: false, + submission_information: { + add_id_info_uses_idfa: false + } + ) + + UI.success("✅ Successfully submitted version #{version} (#{build_number}) for App Store review!") + UI.message("📋 Next steps:") + UI.message(" 1. Check App Store Connect for submission status") + UI.message(" 2. Review time: typically 24-48 hours") + UI.message(" 3. App will go live after manual release approval") + end + + desc "Submit existing build for App Store review (no build/upload)" + lane :submit_only do + # Authenticate with App Store Connect API + api_key = app_store_connect_api_key( + key_id: ENV["ASC_KEY_ID"], + issuer_id: ENV["ASC_ISSUER_ID"], + key_content: Base64.decode64(ENV["ASC_KEY_CONTENT"]), + is_key_content_base64: false + ) + + # Get current version info + version = get_version_number(xcodeproj: XCODEPROJ, target: "WorkOut Log") + build_number = get_info_plist_value( + path: "WorkOut-Log-Info.plist", + key: "CFBundleVersion" + ) + + UI.header("📤 Submitting existing build #{version} (#{build_number}) for App Store review") + + # Submit for review without uploading (assumes build already exists) + deliver( + api_key: api_key, + submit_for_review: true, + force: true, + skip_binary_upload: true, # Skip binary upload - use existing build + skip_metadata: true, + skip_screenshots: true, + automatic_release: false, + precheck_include_in_app_purchases: false, + submission_information: { + add_id_info_uses_idfa: false + } + ) + + UI.success("✅ Successfully submitted version #{version} (#{build_number}) for App Store review!") + UI.message("📋 Next steps:") + UI.message(" 1. Check App Store Connect for submission status") + UI.message(" 2. Review time: typically 24-48 hours") + UI.message(" 3. App will go live after manual release approval") + end + + # ======================================== + # HELPER LANES + # ======================================== + + desc "Clean derived data and build artifacts" + lane :clean do + clean_build_artifacts + clear_derived_data + UI.success("✅ Cleaned build artifacts and derived data") + end + + desc "Run SwiftLint (if configured)" + lane :lint do + swiftlint( + mode: :lint, + executable: "Pods/SwiftLint/swiftlint", + raise_if_swiftlint_error: true + ) + end + + desc "Verify export compliance settings (ITSAppUsesNonExemptEncryption)" + lane :check_export_compliance do + UI.header("🔍 Checking Export Compliance Settings") + + plist_path = "../WorkOut-Log-Info.plist" + plist_filename = "WorkOut-Log-Info.plist" + + # Check if Info.plist exists + unless File.exist?(plist_path) + UI.user_error!("❌ Info.plist not found at: #{plist_path}") + end + + # Read the value from Info.plist + encryption_value = get_info_plist_value( + path: plist_filename, + key: "ITSAppUsesNonExemptEncryption" + ) + + # Verify the value + if encryption_value.nil? + UI.user_error!("❌ ITSAppUsesNonExemptEncryption key is missing in #{plist_filename}") + elsif encryption_value == false || encryption_value == "false" || encryption_value == "NO" + UI.success("✅ Export compliance correctly set: ITSAppUsesNonExemptEncryption = false") + UI.success("✅ Target: WorkOut Log") + UI.success("✅ Info.plist: #{plist_filename}") + UI.message("") + UI.message("📋 Summary:") + UI.message(" • App does not use exempt encryption") + UI.message(" • Export compliance questions will be skipped in App Store Connect") + UI.message(" • Safe for apps using only standard iOS frameworks (URLSession, etc.)") + else + UI.user_error!("❌ ITSAppUsesNonExemptEncryption has incorrect value: #{encryption_value} (expected: false)") + end + end + + # ======================================== + # ERROR HANDLING + # ======================================== + + error do |lane, exception| + UI.error("❌ Lane #{lane} failed with exception:") + UI.error(exception.message) + end +end diff --git a/fastlane/metadata/ko-KR/keywords.txt b/fastlane/metadata/ko-KR/keywords.txt deleted file mode 100644 index 749a218..0000000 --- a/fastlane/metadata/ko-KR/keywords.txt +++ /dev/null @@ -1 +0,0 @@ -운동 기록,헬스 기록,운동 일지,웨이트 트레이닝,근력 운동,피트니스,헬스장,운동 추적,볼륨 기록,개인 기록,운동 로그,트레이닝 일지,오프라인 운동,개인정보 보호,로컬 저장,세트 기록,반복 횟수,운동 플래너,헬스 다이어리,보디빌딩,파워리프팅,운동 기록앱,헬스 앱,피트니스 앱 \ No newline at end of file diff --git a/fastlane/metadata/ko-KR/name.txt b/fastlane/metadata/ko-KR/name.txt deleted file mode 100644 index bd90fa5..0000000 --- a/fastlane/metadata/ko-KR/name.txt +++ /dev/null @@ -1 +0,0 @@ -WorkOut Log \ No newline at end of file diff --git a/fastlane/metadata/ko-KR/release_notes.txt b/fastlane/metadata/ko-KR/release_notes.txt deleted file mode 100644 index e14dc5f..0000000 --- a/fastlane/metadata/ko-KR/release_notes.txt +++ /dev/null @@ -1,15 +0,0 @@ -버전 1.0.0 — 첫 출시 - -WorkOut Log에 오신 것을 환영합니다! 완벽한 개인정보 보호로 운동을 기록하세요. - -새로운 기능: -• 자동 볼륨 계산과 함께 세션 및 세트 추적 -• 4개 카테고리(하체/상체/유산소/전신)를 가진 맞춤형 운동 라이브러리 -• 카테고리 배지 및 운동 그룹화가 있는 운동 로그 -• 트렌드 시각화 (일일/주간/월간) -• 앱 실행 시 매일 동기부여 명언 -• 100% 오프라인 — 계정 없음, 클라우드 없음, 추적 없음 - -iOS 18용 Swift 6 + SwiftUI + SwiftData로 제작. - -문의사항? your-email@example.com \ No newline at end of file diff --git a/fastlane/metadata/ko-KR/subtitle.txt b/fastlane/metadata/ko-KR/subtitle.txt deleted file mode 100644 index 56ffb56..0000000 --- a/fastlane/metadata/ko-KR/subtitle.txt +++ /dev/null @@ -1 +0,0 @@ -오프라인 운동 기록 앱 \ No newline at end of file diff --git a/fastlane/metadata/ko-KR/description.txt b/fastlane/metadata/ko/description.txt similarity index 71% rename from fastlane/metadata/ko-KR/description.txt rename to fastlane/metadata/ko/description.txt index ed078b8..e75dc8e 100644 --- a/fastlane/metadata/ko-KR/description.txt +++ b/fastlane/metadata/ko/description.txt @@ -1,116 +1,92 @@ -# WorkOut Log — 오프라인 운동 기록 앱 - 완벽한 개인정보 보호와 함께 운동을 기록하세요. 모든 데이터는 내 기기에만 저장됩니다—계정 없음, 클라우드 없음, 추적 없음. -## 주요 기능 +주요 기능 -### 📝 세션 및 세트 기록 +세션 및 세트 기록 • 날짜 선택으로 운동 세션 기록 • 운동명, 무게(kg), 반복 횟수 입력으로 세트 추가 • 자동 볼륨 계산 (무게 × 횟수) • 빠른 데이터 입력을 위한 운동 고정 선택 • 스와이프로 세션 및 세트 삭제 -### 🏋️ 맞춤형 운동 라이브러리 +맞춤형 운동 라이브러리 • 나만의 운동을 이름으로 생성 • 4가지 카테고리: 하체, 상체, 유산소, 전신 • 즉시 검색 결과 표시 • 스와이프로 운동 삭제 (안전 검증 포함) • 카테고리별 운동 그룹화로 쉬운 탐색 -### 📊 운동 로그 +운동 로그 • 시간순 세션 목록 (최신순) • 세션별 운동 카테고리 배지 표시 • 세션별 총 볼륨 표시 • 운동별 세트 그룹화 및 섹션 헤더 • 운동별 세트 번호 매기기 및 소계 볼륨 -### 📈 트렌드 및 분석 -• 3가지 보기 범위: 일일(최근 ≤10일), 주간(8주), 월간(6개월) +트렌드 및 분석 +• 3가지 보기 범위: 일일(최근 10일 이하), 주간(8주), 월간(6개월) • 일일 모드 "모든 날 표시" 토글 (볼륨 없는 날 포함) • 카테고리 필터 (하체/상체/유산소/전신) • 볼륨 시각화를 위한 인터랙티브 라인 차트 • 한눈에 보는 진행 상황 추적 -### 💬 매일 동기부여 +매일 동기부여 • 앱 실행 시 랜덤 운동 명언 • 운동선수 및 리더의 15개 명언 큐레이션 • 2.5초 후 자동 종료 (또는 탭하여 건너뛰기) • 스크린 리더 사용자를 위한 긴 지속 시간 -## 개인정보 보호 및 보안 +개인정보 보호 및 보안 -### 100% 오프라인 +100% 오프라인 • 인터넷 불필요—비행기 모드에서도 작동 • 클라우드 동기화, 서버, 데이터베이스 없음 • 모든 데이터는 Apple SwiftData로 로컬 저장 • 데이터가 iPhone 밖으로 나가지 않음 -### 데이터 수집 제로 +데이터 수집 제로 • 개인정보 수집 없음 • 사용 분석 또는 추적 없음 • 광고 식별자 없음 • 타사 SDK 없음 • 외부 서비스로 충돌 보고 없음 -### 내 데이터, 내가 제어 +내 데이터, 내가 제어 • 모든 운동 데이터는 기기에만 저장 • iOS 암호화 (기기 잠금 코드 활성화 시) • iOS 백업 활성화 시에만 iCloud 백업 • 앱 삭제 시 영구 삭제 -## 접근성 - +접근성 • 시맨틱 레이블이 있는 완전한 VoiceOver 지원 • 텍스트 크기 조정을 위한 Dynamic Type • 고대비 색상 • 모든 인터랙티브 요소에 대한 접근성 식별자 • 스크린 리더 사용자를 위한 확장 스플래시 지속 시간 -## 기술 하이라이트 - +기술 하이라이트 • Swift 6, SwiftUI, SwiftData로 제작 • iOS 18.0 이상 필요 • 도메인 주도 설계의 클린 아키텍처 • Apple 네이티브 프레임워크만 사용 (종속성 없음) • 즉각적인 성능을 위한 오프라인 우선 설계 -## 영구 무료 - +영구 무료 • 구독 없음 • 인앱 구매 없음 • 광고 없음 • 숨겨진 비용 없음 • 그저 작동하는 도구 -## 요구 사항 - +요구 사항 • iOS 18.0 이상을 실행하는 iPhone • 약 10MB 저장 공간 • 인터넷 연결 불필요 -## 지원 - +지원 버그를 발견하거나 기능 요청이 있으신가요? -• GitHub: github.com/ojung/workout-log -• 이메일: your-email@example.com - -## 다음 단계 - -향후 업데이트 예정 기능: -• 신체 지표 추적 (체중, 체지방률) -• 운동별 기록 히스토리 -• CSV로 운동 데이터 내보내기 -• 선택적 iCloud 동기화 -• Apple Watch 컴패니언 앱 - -## 개발자 정보 - -WorkOut Log는 클라우드 종속성 없이 심플하고 프라이빗한 운동 기록 앱을 원했던 소프트웨어 엔지니어이자 피트니스 애호가인 오정석이 만들었습니다. - -오픈 소스이며 피트니스 커뮤니티를 위해 ❤️로 제작되었습니다. - ---- +• GitHub: https://github.com/jungseok-corine/WorkOut_Log +• 이메일: fiverights99@gmail.com 오늘 WorkOut Log를 다운로드하고 운동 데이터를 제어하세요. diff --git a/fastlane/metadata/ko/keywords.txt b/fastlane/metadata/ko/keywords.txt new file mode 100644 index 0000000..5655f22 --- /dev/null +++ b/fastlane/metadata/ko/keywords.txt @@ -0,0 +1 @@ +운동,기록,헬스,오프라인,볼륨,차트,개인정보보호,세트,반복,웨이트,근력,피트니스,트레이닝,로그 diff --git a/fastlane/metadata/ko/marketing_url.txt b/fastlane/metadata/ko/marketing_url.txt new file mode 100644 index 0000000..482b66c --- /dev/null +++ b/fastlane/metadata/ko/marketing_url.txt @@ -0,0 +1 @@ +https://jungseok-corine.github.io/ben-homepage/ diff --git a/fastlane/metadata/ko/privacy_url.txt b/fastlane/metadata/ko/privacy_url.txt new file mode 100644 index 0000000..301969f --- /dev/null +++ b/fastlane/metadata/ko/privacy_url.txt @@ -0,0 +1 @@ +https://github.com/jungseok-corine/WorkOut_Log/blob/main/AppStore/PrivacyPolicy.md diff --git a/fastlane/metadata/ko-KR/promotional_text.txt b/fastlane/metadata/ko/promotional_text.txt similarity index 89% rename from fastlane/metadata/ko-KR/promotional_text.txt rename to fastlane/metadata/ko/promotional_text.txt index 0c155f9..e8c4465 100644 --- a/fastlane/metadata/ko-KR/promotional_text.txt +++ b/fastlane/metadata/ko/promotional_text.txt @@ -1 +1 @@ -오프라인 운동 기록, 데이터 수집 제로. 세트 기록, 트렌드 시각화, 내 데이터는 내가 소유. 계정 없음, 클라우드 없음, 추적 없음—심플하고 안전한 운동 기록 앱. \ No newline at end of file +오프라인 운동 기록, 데이터 수집 제로. 세트 기록, 트렌드 시각화, 내 데이터는 내가 소유. 계정 없음, 클라우드 없음, 추적 없음—심플하고 안전한 운동 기록 앱. diff --git a/fastlane/metadata/ko/release_notes.txt b/fastlane/metadata/ko/release_notes.txt new file mode 100644 index 0000000..69e5746 --- /dev/null +++ b/fastlane/metadata/ko/release_notes.txt @@ -0,0 +1,12 @@ +초기 릴리스 - WorkOut Log v1.1 + +• 완전한 오프라인 운동 기록 +• 세션 및 세트 추적 +• 맞춤형 운동 라이브러리 +• 일일/주간/월간 트렌드 차트 +• 100% 개인정보 보호 (데이터 수집 제로) +• VoiceOver 접근성 지원 +• 영구 무료, 광고 없음 + +계정 없음. 클라우드 없음. 추적 없음. +모든 데이터는 내 기기에만 저장됩니다. diff --git a/fastlane/metadata/ko/screenshots/README.txt b/fastlane/metadata/ko/screenshots/README.txt new file mode 100644 index 0000000..ec65126 --- /dev/null +++ b/fastlane/metadata/ko/screenshots/README.txt @@ -0,0 +1 @@ +📸 NOTE: Screenshot placeholders created. Add actual PNG screenshots before submission. diff --git a/fastlane/metadata/ko/screenshots/iphone65/01-launch.png b/fastlane/metadata/ko/screenshots/iphone65/01-launch.png new file mode 100644 index 0000000..87cfa0c Binary files /dev/null and b/fastlane/metadata/ko/screenshots/iphone65/01-launch.png differ diff --git a/fastlane/metadata/ko/screenshots/iphone65/02-worklog_badges.png b/fastlane/metadata/ko/screenshots/iphone65/02-worklog_badges.png new file mode 100644 index 0000000..c21d881 Binary files /dev/null and b/fastlane/metadata/ko/screenshots/iphone65/02-worklog_badges.png differ diff --git a/fastlane/metadata/ko/screenshots/iphone65/03-worklog_list.png b/fastlane/metadata/ko/screenshots/iphone65/03-worklog_list.png new file mode 100644 index 0000000..761ccaf Binary files /dev/null and b/fastlane/metadata/ko/screenshots/iphone65/03-worklog_list.png differ diff --git a/fastlane/metadata/ko/screenshots/iphone65/04-session_detail_empty.png b/fastlane/metadata/ko/screenshots/iphone65/04-session_detail_empty.png new file mode 100644 index 0000000..7ad40c5 Binary files /dev/null and b/fastlane/metadata/ko/screenshots/iphone65/04-session_detail_empty.png differ diff --git a/fastlane/metadata/ko/screenshots/iphone65/05-exercise_picker.png b/fastlane/metadata/ko/screenshots/iphone65/05-exercise_picker.png new file mode 100644 index 0000000..0632825 Binary files /dev/null and b/fastlane/metadata/ko/screenshots/iphone65/05-exercise_picker.png differ diff --git a/fastlane/metadata/ko/screenshots/iphone65/06-create_exercise.png b/fastlane/metadata/ko/screenshots/iphone65/06-create_exercise.png new file mode 100644 index 0000000..070d72b Binary files /dev/null and b/fastlane/metadata/ko/screenshots/iphone65/06-create_exercise.png differ diff --git a/fastlane/metadata/ko/screenshots/iphone65/07-session_detail_selected.png b/fastlane/metadata/ko/screenshots/iphone65/07-session_detail_selected.png new file mode 100644 index 0000000..4041c15 Binary files /dev/null and b/fastlane/metadata/ko/screenshots/iphone65/07-session_detail_selected.png differ diff --git a/fastlane/metadata/ko/screenshots/iphone65/08-trends.png b/fastlane/metadata/ko/screenshots/iphone65/08-trends.png new file mode 100644 index 0000000..8e756ec Binary files /dev/null and b/fastlane/metadata/ko/screenshots/iphone65/08-trends.png differ diff --git a/fastlane/metadata/ko/support_url.txt b/fastlane/metadata/ko/support_url.txt new file mode 100644 index 0000000..482b66c --- /dev/null +++ b/fastlane/metadata/ko/support_url.txt @@ -0,0 +1 @@ +https://jungseok-corine.github.io/ben-homepage/