diff --git a/.github/workflows/appstore_release.yml b/.github/workflows/appstore_release.yml
index 86232d01..eb4a9a2e 100644
--- a/.github/workflows/appstore_release.yml
+++ b/.github/workflows/appstore_release.yml
@@ -1,57 +1,185 @@
-# This workflow will build a Swift project
-# For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-swift
-
-name: appstore-release
+name: App Store Release
on:
pull_request:
branches:
- main
+ types:
+ - closed
+
+ workflow_dispatch:
jobs:
- build:
+ deploy:
+ if: ${{ github.event_name == 'workflow_dispatch' || github.event.pull_request.merged == true }}
runs-on: macos-26
+ timeout-minutes: 120
+
+ env:
+ # App Store Connect
+ APP_STORE_CONNECT_KEY_ID: ${{ secrets.APP_STORE_CONNECT_KEY_ID }}
+ APP_STORE_CONNECT_ISSUER_ID: ${{ secrets.APP_STORE_CONNECT_ISSUER_ID }}
+ APP_STORE_CONNECT_API: ${{ secrets.APP_STORE_CONNECT_API }}
+
+ # Team IDs
+ DEVELOPMENT_TEAM: ${{ secrets.DEVELOPMENT_TEAM }}
+ APP_STORE_CONNECT_TEAM_ID: ${{ secrets.APP_STORE_CONNECT_TEAM_ID }}
+
+ # Match (Code Signing)
+ MATCH_PASSWORD: ${{ secrets.MATCH_PASSWORD }}
+ MATCH_KEYCHAIN_NAME: fastlane_tmp.keychain-db
+ MATCH_GIT_PRIVATE_KEY: ${{ secrets.MATCH_GIT_PRIVATE_KEY }}
+
+ # Review Information
+ DEMO_USER: ${{ secrets.DEMO_USER }}
+ DEMO_PASSWORD: ${{ secrets.DEMO_PASSWORD }}
+ PHONE_NUMBER: ${{ secrets.PHONE_NUMBER }}
+
+ # Fastlane
+ FASTLANE_XCODEBUILD_SETTINGS_TIMEOUT: '120'
+ FASTLANE_XCODE_LIST_TIMEOUT: '120'
+ FASTLANE_DISABLE_COLORS: 'true'
steps:
- - uses: actions/checkout@v4
-
- - name: Set up Xcode
- uses: maxim-lobanov/setup-xcode@v1
- with:
- xcode-version: latest-stable
-
- - uses: shimataro/ssh-key-action@v2
- with:
- key: ${{ secrets.SSH_KEY }}
- known_hosts: ${{ secrets.KNOWN_HOSTS }}
-
- - name: initial mise
- run: |
- curl https://mise.jdx.dev/install.sh | sh
- echo "$HOME/.local/share/mise/bin" >> $GITHUB_PATH
- echo "$HOME/.local/share/mise/shims" >> $GITHUB_PATH
-
- - name: initial tuist
- run: mise install tuist
-
- - name: Generate Project
- env:
- MATCH_PASSWORD: ${{ secrets.MATCH_PASSWORD }}
- DEVELOPMENT_TEAM: ${{ secrets.DEVELOPMENT_TEAM }}
- run: |
- fastlane appstore_profile
- make release
-
- - name: Build Archive
- env:
- APP_STORE_CONNECT_KEY_ID: ${{ secrets.APP_STORE_CONNECT_KEY_ID }}
- APP_STORE_CONNECT_ISSUER_ID: ${{ secrets.APP_STORE_CONNECT_ISSUER_ID }}
- APP_STORE_CONNECT_API: ${{ secrets.APP_STORE_CONNECT_API }}
- run: fastlane archive
-
- - name: Appstore Release
- env:
- APP_STORE_CONNECT_KEY_ID: ${{ secrets.APP_STORE_CONNECT_KEY_ID }}
- APP_STORE_CONNECT_ISSUER_ID: ${{ secrets.APP_STORE_CONNECT_ISSUER_ID }}
- APP_STORE_CONNECT_API: ${{ secrets.APP_STORE_CONNECT_API }}
- run: fastlane appstore_release
+ - name: Checkout code
+ uses: actions/checkout@v4
+
+ - name: Set up Xcode
+ uses: maxim-lobanov/setup-xcode@v1
+ with:
+ xcode-version: latest-stable
+
+ - name: Setup Ruby
+ uses: ruby/setup-ruby@v1
+ with:
+ ruby-version: '3.3'
+ bundler-cache: true
+
+ - name: Setup SSH for Match
+ if: env.MATCH_GIT_PRIVATE_KEY != ''
+ run: |
+ mkdir -p "$HOME/.ssh"
+ echo "$MATCH_GIT_PRIVATE_KEY" > "$HOME/.ssh/match_git_key"
+ chmod 600 "$HOME/.ssh/match_git_key"
+
+ eval "$(ssh-agent -s)"
+ ssh-add "$HOME/.ssh/match_git_key"
+ ssh-keyscan -H github.com >> "$HOME/.ssh/known_hosts"
+
+ cat >> "$HOME/.ssh/config" << EOF
+ Host github.com
+ IdentityFile ~/.ssh/match_git_key
+ StrictHostKeyChecking yes
+ User git
+ EOF
+
+ - name: Setup SSH for Private repo
+ uses: shimataro/ssh-key-action@v2
+ with:
+ key: ${{ secrets.SSH_KEY }}
+ known_hosts: ${{ secrets.KNOWN_HOSTS }}
+
+ - name: Setup keychain
+ if: env.MATCH_PASSWORD != ''
+ run: |
+ security create-keychain -p "$MATCH_PASSWORD" "$MATCH_KEYCHAIN_NAME"
+ security set-keychain-settings -lut 21600 "$MATCH_KEYCHAIN_NAME"
+ security unlock-keychain -p "$MATCH_PASSWORD" "$MATCH_KEYCHAIN_NAME"
+
+ existing_keychains=$(security list-keychains | tr -d '"')
+ security list-keychains -s "$MATCH_KEYCHAIN_NAME" $existing_keychains
+ security default-keychain -s "$MATCH_KEYCHAIN_NAME"
+
+ - name: Setup mise
+ run: |
+ curl https://mise.jdx.dev/install.sh | sh
+ echo "$HOME/.local/share/mise/bin" >> $GITHUB_PATH
+ echo "$HOME/.local/share/mise/shims" >> $GITHUB_PATH
+
+ - name: Install Tuist
+ run: mise install tuist
+
+ - name: Setup code signing
+ run: bundle exec fastlane appstore_profile
+
+ - name: Generate project with Tuist
+ run: make release
+
+ - name: Update release notes from PR description
+ env:
+ GH_TOKEN: ${{ github.token }}
+ run: |
+ PR_BODY=$(gh pr view ${{ github.event.pull_request.number }} --json body --jq '.body')
+
+ if [ -z "$PR_BODY" ]; then
+ echo "버그 수정 및 성능 개선" > fastlane/release_notes.txt
+ else
+ echo "$PR_BODY" > fastlane/release_notes.txt
+ fi
+
+ echo "Release notes updated:"
+ cat fastlane/release_notes.txt
+
+ - name: Build archive
+ run: bundle exec fastlane archive
+
+ - name: Submit to App Store
+ run: bundle exec fastlane appstore_release
+
+ - name: Get app version
+ id: version
+ run: |
+ VERSION=$(xcodebuild -project Projects/App/App.xcodeproj -showBuildSettings | grep MARKETING_VERSION | head -1 | awk '{print $3}')
+ echo "version=$VERSION" >> $GITHUB_OUTPUT
+
+ - name: Create Git tag and Release draft
+ env:
+ GH_TOKEN: ${{ github.token }}
+ run: |
+ VERSION="${{ steps.version.outputs.version }}"
+ TAG="v$VERSION"
+
+ # 태그 생성
+ git config user.name "github-actions[bot]"
+ git config user.email "github-actions[bot]@users.noreply.github.com"
+ git tag -a "$TAG" -m "Release $VERSION"
+ git push origin "$TAG"
+
+ # Release draft 생성
+ PR_BODY=$(gh pr view ${{ github.event.pull_request.number }} --json body --jq '.body')
+ if [ -z "$PR_BODY" ]; then
+ RELEASE_NOTES="버그 수정 및 성능 개선"
+ else
+ RELEASE_NOTES="$PR_BODY"
+ fi
+
+ gh release create "$TAG" \
+ --title "Release $VERSION" \
+ --notes "$RELEASE_NOTES" \
+ --draft
+
+ - name: Upload logs on failure
+ if: failure()
+ uses: actions/upload-artifact@v4
+ with:
+ name: xcode-logs
+ path: |
+ ~/Library/Logs/gym
+ ~/Library/Developer/Xcode/DerivedData
+ /Users/runner/Library/Developer/Xcode/Archives
+ if-no-files-found: ignore
+ retention-days: 3
+
+ - name: Cleanup
+ if: always()
+ run: |
+ # SSH cleanup
+ if [ -n "$MATCH_GIT_PRIVATE_KEY" ]; then
+ ssh-add -D >/dev/null 2>&1 || true
+ rm -f "$HOME/.ssh/match_git_key"
+ fi
+
+ # Keychain cleanup
+ if [ -n "$MATCH_PASSWORD" ]; then
+ security delete-keychain "$MATCH_KEYCHAIN_NAME" 2>/dev/null || true
+ fi
diff --git a/.github/workflows/build_test.yml b/.github/workflows/build_test.yml
index e7a6fb32..f3f5d9ec 100644
--- a/.github/workflows/build_test.yml
+++ b/.github/workflows/build_test.yml
@@ -1,7 +1,4 @@
-# This workflow will build a Swift project
-# For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-swift
-
-name: build-test
+name: Build Test
on:
pull_request:
@@ -13,36 +10,111 @@ on:
jobs:
build:
runs-on: macos-26
+ timeout-minutes: 60
+
+ env:
+ # Team IDs
+ DEVELOPMENT_TEAM: ${{ secrets.DEVELOPMENT_TEAM }}
+
+ # Match (Code Signing)
+ MATCH_PASSWORD: ${{ secrets.MATCH_PASSWORD }}
+ MATCH_KEYCHAIN_NAME: fastlane_tmp.keychain-db
+ MATCH_GIT_PRIVATE_KEY: ${{ secrets.MATCH_GIT_PRIVATE_KEY }}
+
+ # Fastlane
+ FASTLANE_XCODEBUILD_SETTINGS_TIMEOUT: '120'
+ FASTLANE_XCODE_LIST_TIMEOUT: '120'
+ FASTLANE_DISABLE_COLORS: 'true'
steps:
- - uses: actions/checkout@v4
-
- - name: Set up Xcode
- uses: maxim-lobanov/setup-xcode@v1
- with:
- xcode-version: latest-stable
-
- - uses: shimataro/ssh-key-action@v2
- with:
- key: ${{ secrets.SSH_KEY }}
- known_hosts: ${{ secrets.KNOWN_HOSTS }}
-
- - name: initial mise
- run: |
- curl https://mise.jdx.dev/install.sh | sh
- echo "$HOME/.local/share/mise/bin" >> $GITHUB_PATH
- echo "$HOME/.local/share/mise/shims" >> $GITHUB_PATH
-
- - name: initial tuist
- run: mise install tuist
-
- - name: Test Generate
- env:
- MATCH_PASSWORD: ${{ secrets.MATCH_PASSWORD }}
- DEVELOPMENT_TEAM: ${{ secrets.DEVELOPMENT_TEAM }}
- run: |
- fastlane development_profile
- make test
-
- - name: Build Test
- run: fastlane build
+ - name: Checkout code
+ uses: actions/checkout@v4
+
+ - name: Set up Xcode
+ uses: maxim-lobanov/setup-xcode@v1
+ with:
+ xcode-version: latest-stable
+
+ - name: Setup Ruby
+ uses: ruby/setup-ruby@v1
+ with:
+ ruby-version: '3.3'
+ bundler-cache: true
+
+ - name: Setup SSH for Match
+ if: env.MATCH_GIT_PRIVATE_KEY != ''
+ run: |
+ mkdir -p "$HOME/.ssh"
+ echo "$MATCH_GIT_PRIVATE_KEY" > "$HOME/.ssh/match_git_key"
+ chmod 600 "$HOME/.ssh/match_git_key"
+
+ eval "$(ssh-agent -s)"
+ ssh-add "$HOME/.ssh/match_git_key"
+ ssh-keyscan -H github.com >> "$HOME/.ssh/known_hosts"
+
+ cat >> "$HOME/.ssh/config" << EOF
+ Host github.com
+ IdentityFile ~/.ssh/match_git_key
+ StrictHostKeyChecking yes
+ User git
+ EOF
+
+ - name: Setup SSH for Private repo
+ uses: shimataro/ssh-key-action@v2
+ with:
+ key: ${{ secrets.SSH_KEY }}
+ known_hosts: unnecessary
+
+ - name: Setup keychain
+ if: env.MATCH_PASSWORD != ''
+ run: |
+ security create-keychain -p "$MATCH_PASSWORD" "$MATCH_KEYCHAIN_NAME"
+ security set-keychain-settings -lut 21600 "$MATCH_KEYCHAIN_NAME"
+ security unlock-keychain -p "$MATCH_PASSWORD" "$MATCH_KEYCHAIN_NAME"
+
+ existing_keychains=$(security list-keychains | tr -d '"')
+ security list-keychains -s "$MATCH_KEYCHAIN_NAME" $existing_keychains
+ security default-keychain -s "$MATCH_KEYCHAIN_NAME"
+
+ - name: Setup mise
+ run: |
+ curl https://mise.jdx.dev/install.sh | sh
+ echo "$HOME/.local/share/mise/bin" >> $GITHUB_PATH
+ echo "$HOME/.local/share/mise/shims" >> $GITHUB_PATH
+
+ - name: Install Tuist
+ run: mise install tuist
+
+ - name: Setup code signing
+ run: bundle exec fastlane development_profile
+
+ - name: Generate project with Tuist
+ run: make test
+
+ - name: Build Test
+ run: bundle exec fastlane build
+
+ - name: Upload logs on failure
+ if: failure()
+ uses: actions/upload-artifact@v4
+ with:
+ name: build-logs
+ path: |
+ ~/Library/Logs/gym
+ ~/Library/Developer/Xcode/DerivedData
+ if-no-files-found: ignore
+ retention-days: 3
+
+ - name: Cleanup
+ if: always()
+ run: |
+ # SSH cleanup
+ if [ -n "$MATCH_GIT_PRIVATE_KEY" ]; then
+ ssh-add -D >/dev/null 2>&1 || true
+ rm -f "$HOME/.ssh/match_git_key"
+ fi
+
+ # Keychain cleanup
+ if [ -n "$MATCH_PASSWORD" ]; then
+ security delete-keychain "$MATCH_KEYCHAIN_NAME" 2>/dev/null || true
+ fi
diff --git a/.github/workflows/check_appstore_status.yml b/.github/workflows/check_appstore_status.yml
new file mode 100644
index 00000000..3fc6f9df
--- /dev/null
+++ b/.github/workflows/check_appstore_status.yml
@@ -0,0 +1,41 @@
+name: Publish Release on App Store Approval
+
+on:
+ schedule:
+ # 매일 오전 9시, 오후 3시, 오후 9시 (KST 기준 오전 6시, 정오, 오후 6시 UTC)
+ - cron: '0 0,6,12 * * *'
+
+ workflow_dispatch:
+
+jobs:
+ publish-release:
+ runs-on: macos-26
+ timeout-minutes: 30
+
+ env:
+ # App Store Connect
+ APP_STORE_CONNECT_KEY_ID: ${{ secrets.APP_STORE_CONNECT_KEY_ID }}
+ APP_STORE_CONNECT_ISSUER_ID: ${{ secrets.APP_STORE_CONNECT_ISSUER_ID }}
+ APP_STORE_CONNECT_API: ${{ secrets.APP_STORE_CONNECT_API }}
+
+ # Team IDs
+ DEVELOPMENT_TEAM: ${{ secrets.DEVELOPMENT_TEAM }}
+ APP_STORE_CONNECT_TEAM_ID: ${{ secrets.APP_STORE_CONNECT_TEAM_ID }}
+
+ steps:
+ - name: Checkout code
+ uses: actions/checkout@v4
+ with:
+ fetch-depth: 0
+
+ - name: Setup Ruby
+ uses: ruby/setup-ruby@v1
+ with:
+ ruby-version: '3.3'
+ bundler-cache: true
+
+ - name: Check App Store status and publish release
+ env:
+ GH_TOKEN: ${{ github.token }}
+ run: |
+ bundle exec fastlane check_and_publish_release || echo "Release publish skipped or failed"
diff --git a/.github/workflows/develop_hotfix.yml b/.github/workflows/develop_hotfix.yml
index 8a0ef29f..8efdeca8 100644
--- a/.github/workflows/develop_hotfix.yml
+++ b/.github/workflows/develop_hotfix.yml
@@ -1,7 +1,4 @@
-# This workflow will build a Swift project
-# For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-swift
-
-name: develop-hotfix
+name: Develop Hotfix
on:
push:
@@ -10,50 +7,121 @@ on:
workflow_dispatch:
jobs:
- build:
+ deploy:
if: startsWith(github.event.head_commit.message, '[hotfix]')
runs-on: macos-26
+ timeout-minutes: 90
+
+ env:
+ # App Store Connect
+ APP_STORE_CONNECT_KEY_ID: ${{ secrets.APP_STORE_CONNECT_KEY_ID }}
+ APP_STORE_CONNECT_ISSUER_ID: ${{ secrets.APP_STORE_CONNECT_ISSUER_ID }}
+ APP_STORE_CONNECT_API: ${{ secrets.APP_STORE_CONNECT_API }}
+
+ # Team IDs
+ DEVELOPMENT_TEAM: ${{ secrets.DEVELOPMENT_TEAM }}
+ APP_STORE_CONNECT_TEAM_ID: ${{ secrets.APP_STORE_CONNECT_TEAM_ID }}
+
+ # Match (Code Signing)
+ MATCH_PASSWORD: ${{ secrets.MATCH_PASSWORD }}
+ MATCH_KEYCHAIN_NAME: fastlane_tmp.keychain-db
+ MATCH_GIT_PRIVATE_KEY: ${{ secrets.MATCH_GIT_PRIVATE_KEY }}
+
+ # Fastlane
+ FASTLANE_XCODEBUILD_SETTINGS_TIMEOUT: '120'
+ FASTLANE_XCODE_LIST_TIMEOUT: '120'
+ FASTLANE_DISABLE_COLORS: 'true'
steps:
- - uses: actions/checkout@v4
-
- - name: Set up Xcode
- uses: maxim-lobanov/setup-xcode@v1
- with:
- xcode-version: latest-stable
-
- - uses: shimataro/ssh-key-action@v2
- with:
- key: ${{ secrets.SSH_KEY }}
- known_hosts: ${{ secrets.KNOWN_HOSTS }}
-
- - name: initial mise
- run: |
- curl https://mise.jdx.dev/install.sh | sh
- echo "$HOME/.local/share/mise/bin" >> $GITHUB_PATH
- echo "$HOME/.local/share/mise/shims" >> $GITHUB_PATH
-
- - name: initial tuist
- run: mise install tuist
-
- - name: Generate Project
- env:
- MATCH_PASSWORD: ${{ secrets.MATCH_PASSWORD }}
- DEVELOPMENT_TEAM: ${{ secrets.DEVELOPMENT_TEAM }}
- run: |
- fastlane appstore_profile
- make release
-
- - name: Build Archive
- env:
- APP_STORE_CONNECT_KEY_ID: ${{ secrets.APP_STORE_CONNECT_KEY_ID }}
- APP_STORE_CONNECT_ISSUER_ID: ${{ secrets.APP_STORE_CONNECT_ISSUER_ID }}
- APP_STORE_CONNECT_API: ${{ secrets.APP_STORE_CONNECT_API }}
- run: fastlane archive
-
- - name: Testflight Release
- env:
- APP_STORE_CONNECT_KEY_ID: ${{ secrets.APP_STORE_CONNECT_KEY_ID }}
- APP_STORE_CONNECT_ISSUER_ID: ${{ secrets.APP_STORE_CONNECT_ISSUER_ID }}
- APP_STORE_CONNECT_API: ${{ secrets.APP_STORE_CONNECT_API }}
- run: fastlane testflight_release
+ - name: Checkout code
+ uses: actions/checkout@v4
+
+ - name: Set up Xcode
+ uses: maxim-lobanov/setup-xcode@v1
+ with:
+ xcode-version: latest-stable
+
+ - name: Setup Ruby
+ uses: ruby/setup-ruby@v1
+ with:
+ ruby-version: '3.3'
+ bundler-cache: true
+
+ - name: Setup SSH for Match
+ if: env.MATCH_GIT_PRIVATE_KEY != ''
+ run: |
+ mkdir -p "$HOME/.ssh"
+ echo "$MATCH_GIT_PRIVATE_KEY" > "$HOME/.ssh/match_git_key"
+ chmod 600 "$HOME/.ssh/match_git_key"
+
+ eval "$(ssh-agent -s)"
+ ssh-add "$HOME/.ssh/match_git_key"
+ ssh-keyscan -H github.com >> "$HOME/.ssh/known_hosts"
+
+ cat >> "$HOME/.ssh/config" << EOF
+ Host github.com
+ IdentityFile ~/.ssh/match_git_key
+ StrictHostKeyChecking yes
+ User git
+ EOF
+
+ - name: Setup SSH for Private repo
+ uses: shimataro/ssh-key-action@v2
+ with:
+ key: ${{ secrets.SSH_KEY }}
+ known_hosts: unnecessary
+
+ - name: Setup keychain
+ if: env.MATCH_PASSWORD != ''
+ run: |
+ security create-keychain -p "$MATCH_PASSWORD" "$MATCH_KEYCHAIN_NAME"
+ security set-keychain-settings -lut 21600 "$MATCH_KEYCHAIN_NAME"
+ security unlock-keychain -p "$MATCH_PASSWORD" "$MATCH_KEYCHAIN_NAME"
+
+ existing_keychains=$(security list-keychains | tr -d '"')
+ security list-keychains -s "$MATCH_KEYCHAIN_NAME" $existing_keychains
+ security default-keychain -s "$MATCH_KEYCHAIN_NAME"
+
+ - name: Setup mise
+ run: |
+ curl https://mise.jdx.dev/install.sh | sh
+ echo "$HOME/.local/share/mise/bin" >> $GITHUB_PATH
+ echo "$HOME/.local/share/mise/shims" >> $GITHUB_PATH
+
+ - name: Install Tuist
+ run: mise install tuist
+
+ - name: Setup code signing
+ run: bundle exec fastlane appstore_profile
+
+ - name: Generate project with Tuist
+ run: make release
+
+ - name: Build and upload to TestFlight
+ run: bundle exec fastlane archive && bundle exec fastlane testflight_release
+
+ - name: Upload logs on failure
+ if: failure()
+ uses: actions/upload-artifact@v4
+ with:
+ name: hotfix-logs
+ path: |
+ ~/Library/Logs/gym
+ ~/Library/Developer/Xcode/DerivedData
+ /Users/runner/Library/Developer/Xcode/Archives
+ if-no-files-found: ignore
+ retention-days: 3
+
+ - name: Cleanup
+ if: always()
+ run: |
+ # SSH cleanup
+ if [ -n "$MATCH_GIT_PRIVATE_KEY" ]; then
+ ssh-add -D >/dev/null 2>&1 || true
+ rm -f "$HOME/.ssh/match_git_key"
+ fi
+
+ # Keychain cleanup
+ if [ -n "$MATCH_PASSWORD" ]; then
+ security delete-keychain "$MATCH_KEYCHAIN_NAME" 2>/dev/null || true
+ fi
diff --git a/.github/workflows/testflight_release.yml b/.github/workflows/testflight_release.yml
index f8aebfd3..a9ea4b65 100644
--- a/.github/workflows/testflight_release.yml
+++ b/.github/workflows/testflight_release.yml
@@ -1,7 +1,4 @@
-# This workflow will build a Swift project
-# For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-swift
-
-name: testflight-release
+name: TestFlight Release
on:
pull_request:
@@ -13,50 +10,121 @@ on:
workflow_dispatch:
jobs:
- build:
+ deploy:
if: ${{ github.event_name == 'workflow_dispatch' || github.event.pull_request.merged == true }}
runs-on: macos-26
+ timeout-minutes: 90
+
+ env:
+ # App Store Connect
+ APP_STORE_CONNECT_KEY_ID: ${{ secrets.APP_STORE_CONNECT_KEY_ID }}
+ APP_STORE_CONNECT_ISSUER_ID: ${{ secrets.APP_STORE_CONNECT_ISSUER_ID }}
+ APP_STORE_CONNECT_API: ${{ secrets.APP_STORE_CONNECT_API }}
+
+ # Team IDs
+ DEVELOPMENT_TEAM: ${{ secrets.DEVELOPMENT_TEAM }}
+ APP_STORE_CONNECT_TEAM_ID: ${{ secrets.APP_STORE_CONNECT_TEAM_ID }}
+
+ # Match (Code Signing)
+ MATCH_PASSWORD: ${{ secrets.MATCH_PASSWORD }}
+ MATCH_KEYCHAIN_NAME: fastlane_tmp.keychain-db
+ MATCH_GIT_PRIVATE_KEY: ${{ secrets.MATCH_GIT_PRIVATE_KEY }}
+
+ # Fastlane
+ FASTLANE_XCODEBUILD_SETTINGS_TIMEOUT: '120'
+ FASTLANE_XCODE_LIST_TIMEOUT: '120'
+ FASTLANE_DISABLE_COLORS: 'true'
steps:
- - uses: actions/checkout@v4
-
- - name: Set up Xcode
- uses: maxim-lobanov/setup-xcode@v1
- with:
- xcode-version: latest-stable
-
- - uses: shimataro/ssh-key-action@v2
- with:
- key: ${{ secrets.SSH_KEY }}
- known_hosts: ${{ secrets.KNOWN_HOSTS }}
-
- - name: initial mise
- run: |
- curl https://mise.jdx.dev/install.sh | sh
- echo "$HOME/.local/share/mise/bin" >> $GITHUB_PATH
- echo "$HOME/.local/share/mise/shims" >> $GITHUB_PATH
-
- - name: initial tuist
- run: mise install tuist
-
- - name: Generate Project
- env:
- MATCH_PASSWORD: ${{ secrets.MATCH_PASSWORD }}
- DEVELOPMENT_TEAM: ${{ secrets.DEVELOPMENT_TEAM }}
- run: |
- fastlane appstore_profile
- make release
-
- - name: Build Archive
- env:
- APP_STORE_CONNECT_KEY_ID: ${{ secrets.APP_STORE_CONNECT_KEY_ID }}
- APP_STORE_CONNECT_ISSUER_ID: ${{ secrets.APP_STORE_CONNECT_ISSUER_ID }}
- APP_STORE_CONNECT_API: ${{ secrets.APP_STORE_CONNECT_API }}
- run: fastlane archive
-
- - name: Testflight Release
- env:
- APP_STORE_CONNECT_KEY_ID: ${{ secrets.APP_STORE_CONNECT_KEY_ID }}
- APP_STORE_CONNECT_ISSUER_ID: ${{ secrets.APP_STORE_CONNECT_ISSUER_ID }}
- APP_STORE_CONNECT_API: ${{ secrets.APP_STORE_CONNECT_API }}
- run: fastlane testflight_release
+ - name: Checkout code
+ uses: actions/checkout@v4
+
+ - name: Set up Xcode
+ uses: maxim-lobanov/setup-xcode@v1
+ with:
+ xcode-version: latest-stable
+
+ - name: Setup Ruby
+ uses: ruby/setup-ruby@v1
+ with:
+ ruby-version: '3.3'
+ bundler-cache: true
+
+ - name: Setup SSH for Match
+ if: env.MATCH_GIT_PRIVATE_KEY != ''
+ run: |
+ mkdir -p "$HOME/.ssh"
+ echo "$MATCH_GIT_PRIVATE_KEY" > "$HOME/.ssh/match_git_key"
+ chmod 600 "$HOME/.ssh/match_git_key"
+
+ eval "$(ssh-agent -s)"
+ ssh-add "$HOME/.ssh/match_git_key"
+ ssh-keyscan -H github.com >> "$HOME/.ssh/known_hosts"
+
+ cat >> "$HOME/.ssh/config" << EOF
+ Host github.com
+ IdentityFile ~/.ssh/match_git_key
+ StrictHostKeyChecking yes
+ User git
+ EOF
+
+ - name: Setup SSH for Private repo
+ uses: shimataro/ssh-key-action@v2
+ with:
+ key: ${{ secrets.SSH_KEY }}
+ known_hosts: ${{ secrets.KNOWN_HOSTS }}
+
+ - name: Setup keychain
+ if: env.MATCH_PASSWORD != ''
+ run: |
+ security create-keychain -p "$MATCH_PASSWORD" "$MATCH_KEYCHAIN_NAME"
+ security set-keychain-settings -lut 21600 "$MATCH_KEYCHAIN_NAME"
+ security unlock-keychain -p "$MATCH_PASSWORD" "$MATCH_KEYCHAIN_NAME"
+
+ existing_keychains=$(security list-keychains | tr -d '"')
+ security list-keychains -s "$MATCH_KEYCHAIN_NAME" $existing_keychains
+ security default-keychain -s "$MATCH_KEYCHAIN_NAME"
+
+ - name: Setup mise
+ run: |
+ curl https://mise.jdx.dev/install.sh | sh
+ echo "$HOME/.local/share/mise/bin" >> $GITHUB_PATH
+ echo "$HOME/.local/share/mise/shims" >> $GITHUB_PATH
+
+ - name: Install Tuist
+ run: mise install tuist
+
+ - name: Setup code signing
+ run: bundle exec fastlane appstore_profile
+
+ - name: Generate project with Tuist
+ run: make release
+
+ - name: Build and upload to TestFlight
+ run: bundle exec fastlane archive && bundle exec fastlane testflight_release
+
+ - name: Upload logs on failure
+ if: failure()
+ uses: actions/upload-artifact@v4
+ with:
+ name: xcode-logs
+ path: |
+ ~/Library/Logs/gym
+ ~/Library/Developer/Xcode/DerivedData
+ /Users/runner/Library/Developer/Xcode/Archives
+ if-no-files-found: ignore
+ retention-days: 3
+
+ - name: Cleanup
+ if: always()
+ run: |
+ # SSH cleanup
+ if [ -n "$MATCH_GIT_PRIVATE_KEY" ]; then
+ ssh-add -D >/dev/null 2>&1 || true
+ rm -f "$HOME/.ssh/match_git_key"
+ fi
+
+ # Keychain cleanup
+ if [ -n "$MATCH_PASSWORD" ]; then
+ security delete-keychain "$MATCH_KEYCHAIN_NAME" 2>/dev/null || true
+ fi
diff --git a/.gitignore b/.gitignore
index c6809d8e..3fcb69ea 100644
--- a/.gitignore
+++ b/.gitignore
@@ -91,3 +91,7 @@ fastlane/key.json
### fastlane environment
fastlane/.env
fastlane/.env.default
+
+### Environment variables
+.env
+.env.local
diff --git a/fastlane/Appfile b/fastlane/Appfile
index 40383a02..c2270b35 100644
--- a/fastlane/Appfile
+++ b/fastlane/Appfile
@@ -1,8 +1,7 @@
-app_identifier("com.pokitmons.pokit") # The bundle identifier of your app
-apple_id("shapekim98@gmail.com") # Your Apple Developer Portal username
-
-itc_team_id(ENV['APP_STORE_CONNECT_TEAM_ID']) # App Store Connect Team ID
-team_id(ENV['DEVELOPMENT_TEAM']) # Developer Portal Team ID
+app_identifier(ENV["APP_IDENTIFIER"])
+apple_id(ENV["APPLE_ID"])
+team_id(ENV["DEVELOPMENT_TEAM"])
+itc_team_id(ENV["APP_STORE_CONNECT_TEAM_ID"])
# For more information about the Appfile, see:
# https://docs.fastlane.tools/advanced/#appfile
diff --git a/fastlane/Fastfile b/fastlane/Fastfile
index 164214f1..e24562ec 100644
--- a/fastlane/Fastfile
+++ b/fastlane/Fastfile
@@ -1,150 +1,296 @@
-# This file contains the fastlane.tools configuration
-# You can find the documentation at https://docs.fastlane.tools
-#
-# For a list of all available actions, check out
-#
-# https://docs.fastlane.tools/actions
-#
-# For a list of all available plugins, check out
-#
-# https://docs.fastlane.tools/plugins/available-plugins
-#
-
-# Uncomment the line if you want fastlane to automatically update itself
-# update_fastlane
+# frozen_string_literal: true
default_platform(:ios)
-platform :ios do
- lane :appstore_profile do
- setup_ci
+# =============================================================================
+# MARK: - Configuration
+# =============================================================================
- match(
- type: "appstore",
- app_identifier:["com.pokitmons.pokit", "com.pokitmons.pokit.ShareExtension"],
- readonly: true
- )
+APP_CONFIG = {
+ apple_id: "shapekim98@gmail.com",
+ app_store_team_id: ENV["APP_STORE_CONNECT_TEAM_ID"],
+ development_team_id: ENV["DEVELOPMENT_TEAM"],
+ match_git_url: "git@github.com:stealmh/Pokit_iOS_Private.git",
+ match_git_branch: "main"
+}.freeze
+
+APP_IDENTIFIERS = {
+ app: "com.pokitmons.pokit",
+ share_extension: "com.pokitmons.pokit.ShareExtension"
+}.freeze
+
+# =============================================================================
+# MARK: - Helper Methods
+# =============================================================================
+
+# 필수 환경변수 검증
+def ensure_env_values(env_vars:)
+ missing = env_vars.select { |key| ENV[key].to_s.strip.empty? }
+ UI.user_error!("필수 환경변수 누락: #{missing.join(', ')}") unless missing.empty?
+end
+
+# Match에 사용할 Bundle Identifier 목록 생성
+def match_bundle_identifiers
+ [APP_IDENTIFIERS[:app], APP_IDENTIFIERS[:share_extension]]
+end
+
+# Match 키체인 이름 반환
+def match_keychain_name
+ ENV["MATCH_KEYCHAIN_NAME"] || "fastlane_tmp.keychain-db"
+end
+
+# App Store Connect API Key 내용 반환
+def app_store_connect_key_content
+ # 환경변수에서 직접 읽기
+ key = ENV["APP_STORE_CONNECT_API"]
+ return key unless key.to_s.strip.empty?
+
+ # 파일에서 읽기
+ path = ENV["APP_STORE_CONNECT_PRIVATE_KEY_PATH"]
+ UI.user_error!("APP_STORE_CONNECT_API 또는 APP_STORE_CONNECT_PRIVATE_KEY_PATH 환경변수가 필요합니다.") if path.to_s.strip.empty?
+
+ expanded_path = File.expand_path(path.strip)
+ UI.user_error!("파일을 찾을 수 없습니다: #{expanded_path}") unless File.exist?(expanded_path)
+
+ File.read(expanded_path)
+end
+
+# App Store Connect API Key 생성
+def create_api_key
+ app_store_connect_api_key(
+ key_id: ENV["APP_STORE_CONNECT_KEY_ID"],
+ issuer_id: ENV["APP_STORE_CONNECT_ISSUER_ID"],
+ key_content: app_store_connect_key_content,
+ duration: 1200,
+ in_house: false
+ )
+end
+
+# 다음 빌드 번호 계산
+def get_next_build_number(api_key:, app_identifier:)
+ latest_testflight_build_number(
+ api_key: api_key,
+ app_identifier: app_identifier,
+ platform: "ios"
+ ).to_i + 1
+rescue StandardError => e
+ UI.message("최신 빌드 번호 확인 실패: #{e.message}. 1부터 시작합니다.")
+ 1
+end
+
+# Match 코드 사이닝 설정
+def setup_code_signing(type:)
+ return UI.message("MATCH_PASSWORD가 없어 코드 사이닝을 건너뜁니다.") if ENV["MATCH_PASSWORD"].to_s.strip.empty?
+
+ ensure_env_values(env_vars: %w[MATCH_PASSWORD])
+
+ match_params = {
+ type: type,
+ readonly: true,
+ app_identifier: match_bundle_identifiers,
+ username: APP_CONFIG[:apple_id],
+ team_id: APP_CONFIG[:development_team_id],
+ git_branch: APP_CONFIG[:match_git_branch],
+ git_url: APP_CONFIG[:match_git_url],
+ clone_branch_directly: true
+ }
+
+ # CI 환경에서는 키체인 설정 추가 (MATCH_PASSWORD 재사용)
+ if ENV["CI"] == "true" || ENV["GITHUB_ACTIONS"] == "true"
+ match_params[:keychain_name] = match_keychain_name
+ match_params[:keychain_password] = ENV["MATCH_PASSWORD"]
end
- lane :development_profile do
- setup_ci
+ # Git private key가 있으면 추가
+ if !ENV["MATCH_GIT_PRIVATE_KEY"].to_s.strip.empty?
+ match_params[:git_private_key] = ENV["MATCH_GIT_PRIVATE_KEY"].strip
+ end
- match(
- type: "development",
- app_identifier:["com.pokitmons.pokit", "com.pokitmons.pokit.ShareExtension"],
- readonly: true
- )
+ match(match_params)
+end
+
+# Tuist 프로젝트 생성
+def generate_tuist_project
+ UI.message("Tuist 프로젝트를 생성합니다...")
+
+ development_team = ENV["DEVELOPMENT_TEAM"]
+
+ if development_team.to_s.empty?
+ sh("tuist generate")
+ else
+ sh("TUIST_DEVELOPMENT_TEAM=#{development_team} tuist generate")
end
+end
- lane :build do
- build_app(
+# 앱 빌드
+def build_app_for_release(scheme:, output_name:, export_method:)
+ build_app(
workspace: "Pokit.xcworkspace",
- scheme: "App",
- configuration: "Debug",
- export_method: "development",
+ scheme: scheme,
+ configuration: "Release",
+ derived_data_path: "build/DerivedData",
+ output_directory: "build/artifacts",
+ output_name: output_name,
+ clean: true,
+ skip_profile_detection: true,
+ export_method: export_method,
+ xcargs: "-skipMacroValidation",
export_options: {
- provisioningProfiles: {
- "com.pokitmons.pokit" => "match Development com.pokitmons.pokit",
- "com.pokitmons.pokit.ShareExtension" => "match Development com.pokitmons.pokit.ShareExtension"
- }
+ provisioningProfiles: {
+ APP_IDENTIFIERS[:app] => "match #{export_method == 'app-store' ? 'AppStore' : 'Development'} #{APP_IDENTIFIERS[:app]}",
+ APP_IDENTIFIERS[:share_extension] => "match #{export_method == 'app-store' ? 'AppStore' : 'Development'} #{APP_IDENTIFIERS[:share_extension]}"
+ }
}
+ )
+end
+
+# =============================================================================
+# MARK: - Lanes
+# =============================================================================
+
+platform :ios do
+ desc "앱스토어용 프로비저닝 프로파일 설정"
+ lane :appstore_profile do
+ setup_ci if ENV["CI"] == "true"
+ setup_code_signing(type: "appstore")
+ end
+
+ desc "개발용 프로비저닝 프로파일 설정"
+ lane :development_profile do
+ setup_ci if ENV["CI"] == "true"
+ setup_code_signing(type: "development")
+ end
+
+ desc "개발 빌드 테스트"
+ lane :build do
+ generate_tuist_project
+
+ xcodebuild(
+ workspace: "Pokit.xcworkspace",
+ scheme: "App",
+ configuration: "Debug",
+ xcargs: "-skipMacroValidation",
+ build: true
)
end
+ desc "릴리즈 빌드 및 아카이브"
lane :archive do
- api_key = app_store_connect_api_key(
- key_id: ENV['APP_STORE_CONNECT_KEY_ID'],
- issuer_id: ENV['APP_STORE_CONNECT_ISSUER_ID'],
- key_content: ENV['APP_STORE_CONNECT_API']
- )
+ begin
+ # 환경변수 검증
+ ensure_env_values(env_vars: %w[APP_STORE_CONNECT_KEY_ID APP_STORE_CONNECT_ISSUER_ID])
- latest_build_number = latest_testflight_build_number(
- api_key: api_key,
- app_identifier: "com.pokitmons.pokit"
- )
+ # Tuist 프로젝트 생성
+ generate_tuist_project
- increment_build_number(
- xcodeproj: "Projects/App/App.xcodeproj",
- build_number: (latest_build_number + 1).to_s
- )
+ # API Key 생성
+ api_key = create_api_key
- build_app(
- workspace: "Pokit.xcworkspace",
- scheme: "App",
- configuration: "Release",
- export_method: "app-store",
- export_options: {
- provisioningProfiles: {
- "com.pokitmons.pokit" => "match AppStore com.pokitmons.pokit",
- "com.pokitmons.pokit.ShareExtension" => "match AppStore com.pokitmons.pokit.ShareExtension"
- }
- }
- )
+ # 빌드 번호 업데이트
+ next_build_number = get_next_build_number(api_key: api_key, app_identifier: APP_IDENTIFIERS[:app])
+ increment_build_number(xcodeproj: "Projects/App/App.xcodeproj", build_number: next_build_number.to_s)
+
+ # 빌드
+ build_app_for_release(
+ scheme: "App",
+ output_name: "Pokit.ipa",
+ export_method: "app-store"
+ )
+ ensure
+ clean_build_artifacts
+ end
end
+ desc "TestFlight에 업로드"
lane :testflight_release do
- api_key = app_store_connect_api_key(
- key_id: ENV['APP_STORE_CONNECT_KEY_ID'],
- issuer_id: ENV['APP_STORE_CONNECT_ISSUER_ID'],
- key_content: ENV['APP_STORE_CONNECT_API']
- )
+ begin
+ # 환경변수 검증
+ ensure_env_values(env_vars: %w[APP_STORE_CONNECT_KEY_ID APP_STORE_CONNECT_ISSUER_ID])
- upload_to_testflight(
- api_key: api_key,
- distribute_external: true,
- groups: ["Pokitmons"],
- changelog: ""
- )
+ # API Key 생성
+ api_key = create_api_key
+
+ # TestFlight 업로드
+ upload_to_testflight(
+ api_key: api_key,
+ skip_waiting_for_build_processing: false,
+ distribute_external: true,
+ groups: ["Pokitmons"],
+ changelog: ""
+ )
+
+ UI.success("✅ TestFlight 업로드 완료!")
+ ensure
+ clean_build_artifacts
+ end
end
+ desc "App Store에 제출"
lane :appstore_release do
- api_key = app_store_connect_api_key(
- key_id: ENV['APP_STORE_CONNECT_KEY_ID'],
- issuer_id: ENV['APP_STORE_CONNECT_ISSUER_ID'],
- key_content: ENV['APP_STORE_CONNECT_API']
- )
+ begin
+ # 환경변수 검증
+ ensure_env_values(env_vars: %w[APP_STORE_CONNECT_KEY_ID APP_STORE_CONNECT_ISSUER_ID])
- release_notes = File.read("release_notes.txt")
+ # API Key 생성
+ api_key = create_api_key
- upload_to_app_store(
- api_key: api_key,
- skip_metadata: false,
- skip_screenshots: true,
- skip_binary_upload: true,
- precheck_include_in_app_purchases: false,
- release_notes: {
- 'default' => release_notes
- },
- submit_for_review: true,
- automatic_release: true,
- force: true
- )
+ # App Store Connect에 업로드 및 메타데이터 업데이트
+ deliver(
+ api_key: api_key,
+ app_identifier: APP_IDENTIFIERS[:app],
+ skip_screenshots: true,
+ skip_metadata: false,
+ force: true,
+ submit_for_review: true,
+ automatic_release: true,
+ submission_information: {
+ add_id_info_uses_idfa: false
+ },
+ precheck_include_in_app_purchases: false
+ )
+
+ UI.success("✅ App Store 심사 제출 완료!")
+ ensure
+ clean_build_artifacts
+ end
end
- lane :update_github_release do
+ desc "App Store 승인 확인 및 Release draft publish"
+ lane :check_and_publish_release do
require 'spaceship'
- api_key = app_store_connect_api_key(
- key_id: ENV['APP_STORE_CONNECT_KEY_ID'],
- issuer_id: ENV['APP_STORE_CONNECT_ISSUER_ID'],
- key_content: ENV['APP_STORE_CONNECT_API']
- )
-
+ api_key = create_api_key
Spaceship::ConnectAPI.login(api_key: api_key)
- app = Spaceship::ConnectAPI::App.find("com.pokitmons.pokit")
-
+ app = Spaceship::ConnectAPI::App.find(APP_IDENTIFIERS[:app])
live_version = app.get_live_version
app_state = live_version.app_store_state
app_version = live_version.version_string
+ UI.message("현재 앱 상태: #{app_state}, 버전: #{app_version}")
+
if app_state == 'READY_FOR_SALE'
- pr_number = ENV['PR_NUMBER']
- pr_body = sh("gh pr view #{pr_number} --json body --jq '.body'")
- release_notes = pr_body.strip
+ tag = "v#{app_version}"
+
+ # Draft release 확인
+ release_info = sh("gh release view #{tag} --json isDraft,name 2>/dev/null || echo ''").strip
+
+ if release_info.empty?
+ UI.message("Release #{tag}가 존재하지 않습니다. 건너뜁니다.")
+ return
+ end
+
+ is_draft = sh("echo '#{release_info}' | jq -r '.isDraft'").strip
- # GitHub 릴리즈 버전 및 릴리즈 노트 업데이트
- sh("gh release create v#{app_version} --notes '#{release_notes}'")
+ if is_draft == "true"
+ # Draft release를 publish
+ sh("gh release edit #{tag} --draft=false")
+ UI.success("✅ GitHub Release #{tag} 를 publish 했습니다!")
+ else
+ UI.message("GitHub Release #{tag}는 이미 published 상태입니다.")
+ end
+ else
+ UI.message("앱이 아직 'READY_FOR_SALE' 상태가 아닙니다. 현재 상태: #{app_state}")
end
end
end
diff --git a/fastlane/Matchfile b/fastlane/Matchfile
index 6648ebc2..6df69ed3 100644
--- a/fastlane/Matchfile
+++ b/fastlane/Matchfile
@@ -4,9 +4,15 @@ storage_mode("git")
type("development") # The default type, can be: appstore, adhoc, enterprise or development
-app_identifier(["com.pokitmons.pokit"])
+app_identifier([
+ "com.pokitmons.pokit",
+ "com.pokitmons.pokit.ShareExtension"
+])
username("shapekim98@gmail.com") # Your Apple Developer Portal username
+git_branch("main")
+shallow_clone(true)
+
# For all available options run `fastlane match --help`
# Remove the # in the beginning of the line to enable the other options
diff --git a/fastlane/README.md b/fastlane/README.md
index dd35e59d..c2ce6ab9 100644
--- a/fastlane/README.md
+++ b/fastlane/README.md
@@ -21,7 +21,7 @@ For _fastlane_ installation instructions, see [Installing _fastlane_](https://do
[bundle exec] fastlane ios appstore_profile
```
-
+앱스토어용 프로비저닝 프로파일 설정
### ios development_profile
@@ -29,7 +29,7 @@ For _fastlane_ installation instructions, see [Installing _fastlane_](https://do
[bundle exec] fastlane ios development_profile
```
-
+개발용 프로비저닝 프로파일 설정
### ios build
@@ -37,7 +37,7 @@ For _fastlane_ installation instructions, see [Installing _fastlane_](https://do
[bundle exec] fastlane ios build
```
-
+개발 빌드 테스트
### ios archive
@@ -45,7 +45,7 @@ For _fastlane_ installation instructions, see [Installing _fastlane_](https://do
[bundle exec] fastlane ios archive
```
-
+릴리즈 빌드 및 아카이브
### ios testflight_release
@@ -53,7 +53,7 @@ For _fastlane_ installation instructions, see [Installing _fastlane_](https://do
[bundle exec] fastlane ios testflight_release
```
-
+TestFlight에 업로드
### ios appstore_release
@@ -61,7 +61,7 @@ For _fastlane_ installation instructions, see [Installing _fastlane_](https://do
[bundle exec] fastlane ios appstore_release
```
-
+App Store에 제출
### ios update_github_release
@@ -69,7 +69,7 @@ For _fastlane_ installation instructions, see [Installing _fastlane_](https://do
[bundle exec] fastlane ios update_github_release
```
-
+GitHub Release 업데이트 (App Store 승인 후)
----
diff --git a/fastlane/metadata/copyright.txt b/fastlane/metadata/copyright.txt
new file mode 100644
index 00000000..cf6dcc63
--- /dev/null
+++ b/fastlane/metadata/copyright.txt
@@ -0,0 +1 @@
+포킷몬즈
diff --git a/fastlane/metadata/ko/apple_tv_privacy_policy.txt b/fastlane/metadata/ko/apple_tv_privacy_policy.txt
new file mode 100644
index 00000000..8b137891
--- /dev/null
+++ b/fastlane/metadata/ko/apple_tv_privacy_policy.txt
@@ -0,0 +1 @@
+
diff --git a/fastlane/metadata/ko/description.txt b/fastlane/metadata/ko/description.txt
new file mode 100644
index 00000000..d703bada
--- /dev/null
+++ b/fastlane/metadata/ko/description.txt
@@ -0,0 +1,14 @@
+저장해뒀던 링크, 찾지 못한 경험이 있으신가요?
+저희 서비스는 흩어져 있던 링크를 한곳에 모아 관리하고, 잊지 않고 리마인드할 수 있도록 돕습니다.
+
+1. 복잡한 링크 저장 과정을 개선했어요.
+바로 붙여넣기 기능, 자동 제목 생성 기능으로 복잡했던 링크 저장 과정을 개선해 편리성을 제공해요.
+
+2. 카테고리별로 나눠 분류해요.
+저장한 링크를 주제별로 나눠서 깔끔하게 정리해요.
+
+3. 저장한 링크들을 한번에 공유해요.
+비슷한 카테고리들로 분류한 링크들을 다른 사람들에게 한번에 공유할 수 있어요.
+
+4. 저장한 링크를 잊지 않고 볼 수 있도록 도와줘요.
+사용자 맞춤 리마인드 콘텐츠 제공, 리마인드 푸시 메시지를 통해 지속적으로 관리하도록 동기부여해요.
diff --git a/fastlane/metadata/ko/keywords.txt b/fastlane/metadata/ko/keywords.txt
new file mode 100644
index 00000000..81748f15
--- /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 00000000..b6a03cba
--- /dev/null
+++ b/fastlane/metadata/ko/marketing_url.txt
@@ -0,0 +1 @@
+https://luminous-captain-c68.notion.site/bb6d0d6569204d5e9a7b67e5825f9d10
diff --git a/fastlane/metadata/ko/name.txt b/fastlane/metadata/ko/name.txt
new file mode 100644
index 00000000..709c18ba
--- /dev/null
+++ b/fastlane/metadata/ko/name.txt
@@ -0,0 +1 @@
+Pokit 포킷 - 간편 링크 아카이빙 앱
diff --git a/fastlane/metadata/ko/privacy_url.txt b/fastlane/metadata/ko/privacy_url.txt
new file mode 100644
index 00000000..c718ae0c
--- /dev/null
+++ b/fastlane/metadata/ko/privacy_url.txt
@@ -0,0 +1 @@
+https://www.notion.so/de3468b3be1744538c22a333ae1d0ec8
diff --git a/fastlane/metadata/ko/promotional_text.txt b/fastlane/metadata/ko/promotional_text.txt
new file mode 100644
index 00000000..8b137891
--- /dev/null
+++ b/fastlane/metadata/ko/promotional_text.txt
@@ -0,0 +1 @@
+
diff --git a/fastlane/metadata/ko/release_notes.txt b/fastlane/metadata/ko/release_notes.txt
new file mode 100644
index 00000000..6196984a
--- /dev/null
+++ b/fastlane/metadata/ko/release_notes.txt
@@ -0,0 +1 @@
+- 자잘한 오류들을 수정했어요.
diff --git a/fastlane/metadata/ko/subtitle.txt b/fastlane/metadata/ko/subtitle.txt
new file mode 100644
index 00000000..8b137891
--- /dev/null
+++ b/fastlane/metadata/ko/subtitle.txt
@@ -0,0 +1 @@
+
diff --git a/fastlane/metadata/ko/support_url.txt b/fastlane/metadata/ko/support_url.txt
new file mode 100644
index 00000000..104ccab8
--- /dev/null
+++ b/fastlane/metadata/ko/support_url.txt
@@ -0,0 +1 @@
+https://luminous-captain-c68.notion.site/POKIT-d97c81534b354cfebe677fbf1fbfe2b2
diff --git a/fastlane/metadata/primary_category.txt b/fastlane/metadata/primary_category.txt
new file mode 100644
index 00000000..cd65e793
--- /dev/null
+++ b/fastlane/metadata/primary_category.txt
@@ -0,0 +1 @@
+PRODUCTIVITY
diff --git a/fastlane/metadata/primary_first_sub_category.txt b/fastlane/metadata/primary_first_sub_category.txt
new file mode 100644
index 00000000..8b137891
--- /dev/null
+++ b/fastlane/metadata/primary_first_sub_category.txt
@@ -0,0 +1 @@
+
diff --git a/fastlane/metadata/primary_second_sub_category.txt b/fastlane/metadata/primary_second_sub_category.txt
new file mode 100644
index 00000000..8b137891
--- /dev/null
+++ b/fastlane/metadata/primary_second_sub_category.txt
@@ -0,0 +1 @@
+
diff --git a/fastlane/metadata/review_information/demo_password.txt b/fastlane/metadata/review_information/demo_password.txt
new file mode 100644
index 00000000..4bdb14f2
--- /dev/null
+++ b/fastlane/metadata/review_information/demo_password.txt
@@ -0,0 +1 @@
+ENV["DEMO_PASSWORD"]
diff --git a/fastlane/metadata/review_information/demo_user.txt b/fastlane/metadata/review_information/demo_user.txt
new file mode 100644
index 00000000..50f4e145
--- /dev/null
+++ b/fastlane/metadata/review_information/demo_user.txt
@@ -0,0 +1 @@
+ENV["DEMO_USER"]
diff --git a/fastlane/metadata/review_information/email_address.txt b/fastlane/metadata/review_information/email_address.txt
new file mode 100644
index 00000000..6b32d791
--- /dev/null
+++ b/fastlane/metadata/review_information/email_address.txt
@@ -0,0 +1 @@
+kmh922@naver.com
diff --git a/fastlane/metadata/review_information/first_name.txt b/fastlane/metadata/review_information/first_name.txt
new file mode 100644
index 00000000..26c87279
--- /dev/null
+++ b/fastlane/metadata/review_information/first_name.txt
@@ -0,0 +1 @@
+민호
diff --git a/fastlane/metadata/review_information/last_name.txt b/fastlane/metadata/review_information/last_name.txt
new file mode 100644
index 00000000..34bb12d0
--- /dev/null
+++ b/fastlane/metadata/review_information/last_name.txt
@@ -0,0 +1 @@
+김
diff --git a/fastlane/metadata/review_information/notes.txt b/fastlane/metadata/review_information/notes.txt
new file mode 100644
index 00000000..8b137891
--- /dev/null
+++ b/fastlane/metadata/review_information/notes.txt
@@ -0,0 +1 @@
+
diff --git a/fastlane/metadata/review_information/phone_number.txt b/fastlane/metadata/review_information/phone_number.txt
new file mode 100644
index 00000000..410a4fbf
--- /dev/null
+++ b/fastlane/metadata/review_information/phone_number.txt
@@ -0,0 +1 @@
+ENV["PHONE_NUMBER"]
diff --git a/fastlane/metadata/secondary_category.txt b/fastlane/metadata/secondary_category.txt
new file mode 100644
index 00000000..cc525525
--- /dev/null
+++ b/fastlane/metadata/secondary_category.txt
@@ -0,0 +1 @@
+LIFESTYLE
diff --git a/fastlane/metadata/secondary_first_sub_category.txt b/fastlane/metadata/secondary_first_sub_category.txt
new file mode 100644
index 00000000..8b137891
--- /dev/null
+++ b/fastlane/metadata/secondary_first_sub_category.txt
@@ -0,0 +1 @@
+
diff --git a/fastlane/metadata/secondary_second_sub_category.txt b/fastlane/metadata/secondary_second_sub_category.txt
new file mode 100644
index 00000000..8b137891
--- /dev/null
+++ b/fastlane/metadata/secondary_second_sub_category.txt
@@ -0,0 +1 @@
+
diff --git a/fastlane/release_notes.txt b/fastlane/release_notes.txt
deleted file mode 100644
index 5baff929..00000000
--- a/fastlane/release_notes.txt
+++ /dev/null
@@ -1,3 +0,0 @@
-- 이제부터 어디서든 아이폰 공유하기로 링크를 저장할 수 있어요.
-- 인스타그램 링크에서 썸네일이 나오지 않는 문제를 수성했어요.
-- 자잘한 버그 및 사용성을 개선했어요.
\ No newline at end of file
diff --git a/fastlane/report.xml b/fastlane/report.xml
index 9d5a5bf3..7e813271 100644
--- a/fastlane/report.xml
+++ b/fastlane/report.xml
@@ -5,17 +5,7 @@
-
-
-
-
-
-
-
-
-
-
-
+
diff --git a/fastlane/screenshots/ko/0_APP_IPHONE_55_0.png b/fastlane/screenshots/ko/0_APP_IPHONE_55_0.png
new file mode 100644
index 00000000..778b70a3
Binary files /dev/null and b/fastlane/screenshots/ko/0_APP_IPHONE_55_0.png differ
diff --git a/fastlane/screenshots/ko/0_APP_IPHONE_67_0.png b/fastlane/screenshots/ko/0_APP_IPHONE_67_0.png
new file mode 100644
index 00000000..eb153584
Binary files /dev/null and b/fastlane/screenshots/ko/0_APP_IPHONE_67_0.png differ
diff --git a/fastlane/screenshots/ko/1_APP_IPHONE_55_1.png b/fastlane/screenshots/ko/1_APP_IPHONE_55_1.png
new file mode 100644
index 00000000..9b944f70
Binary files /dev/null and b/fastlane/screenshots/ko/1_APP_IPHONE_55_1.png differ
diff --git a/fastlane/screenshots/ko/1_APP_IPHONE_67_1.png b/fastlane/screenshots/ko/1_APP_IPHONE_67_1.png
new file mode 100644
index 00000000..15fd19c2
Binary files /dev/null and b/fastlane/screenshots/ko/1_APP_IPHONE_67_1.png differ
diff --git a/fastlane/screenshots/ko/2_APP_IPHONE_55_2.png b/fastlane/screenshots/ko/2_APP_IPHONE_55_2.png
new file mode 100644
index 00000000..6f846cff
Binary files /dev/null and b/fastlane/screenshots/ko/2_APP_IPHONE_55_2.png differ
diff --git a/fastlane/screenshots/ko/2_APP_IPHONE_67_2.png b/fastlane/screenshots/ko/2_APP_IPHONE_67_2.png
new file mode 100644
index 00000000..7f15bf38
Binary files /dev/null and b/fastlane/screenshots/ko/2_APP_IPHONE_67_2.png differ
diff --git a/fastlane/screenshots/ko/3_APP_IPHONE_55_3.png b/fastlane/screenshots/ko/3_APP_IPHONE_55_3.png
new file mode 100644
index 00000000..929b9ac1
Binary files /dev/null and b/fastlane/screenshots/ko/3_APP_IPHONE_55_3.png differ
diff --git a/fastlane/screenshots/ko/3_APP_IPHONE_67_3.png b/fastlane/screenshots/ko/3_APP_IPHONE_67_3.png
new file mode 100644
index 00000000..2a9cb4ff
Binary files /dev/null and b/fastlane/screenshots/ko/3_APP_IPHONE_67_3.png differ
diff --git a/fastlane/screenshots/ko/4_APP_IPHONE_67_4.png b/fastlane/screenshots/ko/4_APP_IPHONE_67_4.png
new file mode 100644
index 00000000..d3de615d
Binary files /dev/null and b/fastlane/screenshots/ko/4_APP_IPHONE_67_4.png differ