From e1aa04baf87e52fb31d67e0951dbf93438809026 Mon Sep 17 00:00:00 2001 From: oilbeaneda <20221444@sungshin.ac.kr> Date: Wed, 26 Aug 2026 11:25:07 +0900 Subject: [PATCH 1/8] =?UTF-8?q?[setting/#34]=20fastlane=20=EA=B8=B0?= =?UTF-8?q?=EB=B3=B8=20=EA=B5=AC=EC=A1=B0=20=EC=B6=94=EA=B0=80=20(Gemfile?= =?UTF-8?q?=C2=B7Appfile=C2=B7Pluginfile)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Gemfile | 7 +++++++ fastlane/Appfile | 2 ++ fastlane/Pluginfile | 2 ++ 3 files changed, 11 insertions(+) create mode 100644 Gemfile create mode 100644 fastlane/Appfile create mode 100644 fastlane/Pluginfile diff --git a/Gemfile b/Gemfile new file mode 100644 index 0000000..2b0c80d --- /dev/null +++ b/Gemfile @@ -0,0 +1,7 @@ +source "https://rubygems.org" + +gem "fastlane" + +# fastlane 플러그인 (fastlane/Pluginfile 에 정의) +plugins_path = File.join(File.dirname(__FILE__), "fastlane", "Pluginfile") +eval_gemfile(plugins_path) if File.exist?(plugins_path) diff --git a/fastlane/Appfile b/fastlane/Appfile new file mode 100644 index 0000000..b0f34b0 --- /dev/null +++ b/fastlane/Appfile @@ -0,0 +1,2 @@ +json_key_file("") # Google Play(supply) 배포 시 사용. Firebase 배포에는 불필요. +package_name("com.moball.app") diff --git a/fastlane/Pluginfile b/fastlane/Pluginfile new file mode 100644 index 0000000..d95d8eb --- /dev/null +++ b/fastlane/Pluginfile @@ -0,0 +1,2 @@ +# fastlane 플러그인 목록. `bundle install` 시 함께 설치된다. +gem "fastlane-plugin-firebase_app_distribution" From 03ff0b3c710fef3783328f612bfe55489d4e025e Mon Sep 17 00:00:00 2001 From: oilbeaneda <20221444@sungshin.ac.kr> Date: Wed, 26 Aug 2026 11:25:14 +0900 Subject: [PATCH 2/8] =?UTF-8?q?[setting/#34]=20Firebase=20App=20Distributi?= =?UTF-8?q?on=20=EB=B0=B0=ED=8F=AC=20lane(beta)=C2=B7=ED=85=8C=EC=8A=A4?= =?UTF-8?q?=ED=8A=B8=20lane=20=EC=9E=91=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- fastlane/Fastfile | 60 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 60 insertions(+) create mode 100644 fastlane/Fastfile diff --git a/fastlane/Fastfile b/fastlane/Fastfile new file mode 100644 index 0000000..f81ffea --- /dev/null +++ b/fastlane/Fastfile @@ -0,0 +1,60 @@ +require "json" + +# 문서: https://docs.fastlane.tools +# 사용법은 fastlane/README.md 참고. + +default_platform(:android) + +# google-services.json 에서 Firebase App ID를 읽어온다. +# (google-services.json 은 .gitignore 대상이라 값을 코드에 하드코딩하지 않는다.) +# CI 등에서 파일이 없으면 FIREBASE_APP_ID 환경변수로 대체 가능. +def firebase_app_id + return ENV["FIREBASE_APP_ID"] unless ENV["FIREBASE_APP_ID"].to_s.empty? + + path = File.expand_path("../app/google-services.json", __dir__) + unless File.exist?(path) + UI.user_error!("google-services.json 을 찾을 수 없습니다. FIREBASE_APP_ID 환경변수를 설정하거나 파일을 배치하세요: #{path}") + end + JSON.parse(File.read(path))["client"][0]["client_info"]["mobilesdk_app_id"] +end + +platform :android do + desc "정적 분석(ktlint) + 단위 테스트 실행" + lane :test do + gradle(task: "ktlintCheck") + gradle(task: "testDebugUnitTest") + end + + desc "테스터에게 배포 (Firebase App Distribution)\n" \ + "옵션 예) fastlane android beta notes:\"수정 내용\" groups:\"testers,qa\"" + lane :beta do |options| + # 배포 전 품질 게이트 + test + + # 빌드 — 현재는 debug 서명 APK로 배포한다(별도 upload keystore 불필요). + # 업로드 keystore를 붙이면 아래 build_type 을 "Release"로 바꾸면 된다. + gradle(task: "clean") + gradle(task: "assemble", build_type: "Debug") + + # Firebase App Distribution 업로드 + firebase_app_distribution( + app: firebase_app_id, + android_artifact_type: "APK", + android_artifact_path: lane_context[SharedValues::GRADLE_APK_OUTPUT_PATH], + service_credentials_file: ENV["FIREBASE_SERVICE_ACCOUNT_JSON"], + groups: options[:groups] || "testers", + release_notes: options[:notes] || last_git_commit[:message], + ) + + UI.success("Firebase App Distribution 업로드 완료 🚀") + end + + # TODO(#34): 업로드 keystore + Play Console 서비스 계정 준비되면 + # Google Play 배포 레인(supply) 추가. + # desc "Google Play 내부 테스트 트랙 배포" + # lane :deploy do + # gradle(task: "clean") + # gradle(task: "bundle", build_type: "Release") + # upload_to_play_store(track: "internal", aab: lane_context[SharedValues::GRADLE_AAB_OUTPUT_PATH]) + # end +end From 80da3329d1c31bbc2a1d40c91d5442381ee3faaf Mon Sep 17 00:00:00 2001 From: oilbeaneda <20221444@sungshin.ac.kr> Date: Wed, 26 Aug 2026 11:25:15 +0900 Subject: [PATCH 3/8] =?UTF-8?q?[setting/#34]=20fastlane=20=EC=82=AC?= =?UTF-8?q?=EC=9A=A9=20=EA=B0=80=EC=9D=B4=EB=93=9C(README)=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80=20+=20.gitignore=20=EC=A0=95=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 11 ++++++++ fastlane/README.md | 67 ++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 78 insertions(+) create mode 100644 fastlane/README.md diff --git a/.gitignore b/.gitignore index b38ee2b..075f1a8 100644 --- a/.gitignore +++ b/.gitignore @@ -44,3 +44,14 @@ local.properties # Firebase (google-services.json에 API 키 포함 — 로컬/CI에 각자 배치) google-services.json + +# fastlane +fastlane/report.xml +fastlane/Preview.html +fastlane/screenshots/**/*.png +fastlane/test_output/ +vendor/bundle/ +.bundle/ + +# fastlane 서비스 계정 키 (절대 커밋 금지) +*service-account*.json diff --git a/fastlane/README.md b/fastlane/README.md new file mode 100644 index 0000000..fd2bd29 --- /dev/null +++ b/fastlane/README.md @@ -0,0 +1,67 @@ +# Fastlane — 모여볼 릴리즈 자동화 + +빌드·테스트·배포를 한 줄 명령으로 실행한다. 배포는 **Firebase App Distribution**(테스터 배포)을 사용한다. + +## Lanes + +| Lane | 설명 | 명령 | +|------|------|------| +| `test` | ktlint + 단위 테스트 | `bundle exec fastlane android test` | +| `beta` | 빌드 후 테스터에게 배포 (Firebase App Distribution) | `bundle exec fastlane android beta` | + +`beta` 옵션: + +```bash +# 릴리즈 노트 / 대상 그룹 지정 +bundle exec fastlane android beta notes:"공유 기능 추가" groups:"testers,qa" +``` + +## 최초 1회 세팅 + +### 1) fastlane 설치 + +```bash +gem install bundler +bundle install # Gemfile + fastlane/Pluginfile 의 플러그인까지 설치 +``` + +### 2) Firebase App Distribution 인증 (서비스 계정) + +CI/자동화에서는 서비스 계정 JSON을 쓴다. + +1. [Google Cloud Console](https://console.cloud.google.com) → 해당 Firebase 프로젝트 → **서비스 계정** 생성 +2. 역할: **Firebase App Distribution Admin** 부여 +3. JSON 키 다운로드 → 안전한 경로에 저장 (**절대 git에 커밋 금지**) +4. 환경변수로 경로 지정: + +```bash +export FIREBASE_SERVICE_ACCOUNT_JSON="/절대/경로/service-account.json" +``` + +> 로컬에서 잠깐 테스트만 할 땐 `firebase login` 후 `FIREBASE_SERVICE_ACCOUNT_JSON` 없이 실행해도 된다(대화형 인증). + +### 3) 테스터 그룹 생성 + +Firebase 콘솔 → App Distribution → **테스터 및 그룹** → `testers` 그룹 생성 후 테스터 추가. +(다른 그룹명을 쓰면 `groups:` 옵션으로 지정) + +## 참고 + +- **Firebase App ID**는 `app/google-services.json` 에서 자동으로 읽는다. 파일이 없는 환경(CI 등)에서는 `FIREBASE_APP_ID` 환경변수로 대체한다. +- 현재 `beta`는 **debug 서명 APK**를 배포한다(별도 upload keystore 불필요). 릴리즈 서명(Play 앱 서명 + upload keystore)이 준비되면 `Fastfile`의 `build_type`을 `"Release"`로 바꾼다. +- Google Play 스토어 직접 배포(`supply`)는 upload keystore + Play Console 서비스 계정이 준비되면 `Fastfile`의 주석 처리된 `deploy` 레인을 활성화한다. (#34 후속) + +## CI (GitHub Actions) 연동 예시 + +```yaml +- uses: ruby/setup-ruby@v1 + with: { ruby-version: '3.3', bundler-cache: true } +- name: Distribute to testers + env: + FIREBASE_SERVICE_ACCOUNT_JSON: ${{ github.workspace }}/sa.json + GOOGLE_SERVICES_JSON: ${{ secrets.GOOGLE_SERVICES_JSON }} + run: | + echo "$GOOGLE_SERVICES_JSON" > app/google-services.json + echo '${{ secrets.FIREBASE_SERVICE_ACCOUNT }}' > sa.json + bundle exec fastlane android beta +``` From 6eb4b79257ca8aff0ba1d2adbcdf2edaab38d593 Mon Sep 17 00:00:00 2001 From: oilbeaneda <20221444@sungshin.ac.kr> Date: Wed, 26 Aug 2026 11:31:05 +0900 Subject: [PATCH 4/8] =?UTF-8?q?[setting/#34]=20Gemfile.lock=20=EA=B3=A0?= =?UTF-8?q?=EC=A0=95=20+=20=EB=A1=9C=EC=BB=AC=20=EC=84=A4=EC=B9=98(vendor/?= =?UTF-8?q?bundle)=20=EA=B0=80=EC=9D=B4=EB=93=9C=C2=B7=EA=B6=8C=ED=95=9C?= =?UTF-8?q?=20=EC=9D=B4=EC=8A=88=20=ED=95=B4=EA=B2=B0=EB=B2=95=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Gemfile.lock | 377 +++++++++++++++++++++++++++++++++++++++++++++ fastlane/README.md | 13 ++ 2 files changed, 390 insertions(+) create mode 100644 Gemfile.lock diff --git a/Gemfile.lock b/Gemfile.lock new file mode 100644 index 0000000..ff587d4 --- /dev/null +++ b/Gemfile.lock @@ -0,0 +1,377 @@ +GEM + remote: https://rubygems.org/ + specs: + CFPropertyList (3.0.8) + abbrev (0.1.2) + addressable (2.9.0) + public_suffix (>= 2.0.2, < 8.0) + artifactory (3.0.17) + atomos (0.1.3) + aws-eventstream (1.4.0) + aws-partitions (1.1282.0) + aws-sdk-core (3.254.1) + 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.130.0) + aws-sdk-core (~> 3, >= 3.254.0) + aws-sigv4 (~> 1.5) + aws-sdk-s3 (1.229.0) + aws-sdk-core (~> 3, >= 3.254.1) + 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) + benchmark (0.5.0) + bigdecimal (4.1.2) + claide (1.1.0) + colored (1.2) + colored2 (3.1.2) + commander (4.6.0) + highline (~> 2.0.0) + csv (3.3.6) + 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) + erb (6.0.7) + excon (1.7.0) + logger + faraday (2.14.3) + faraday-net_http (>= 2.0, < 3.5) + json + logger + faraday-cookie_jar (0.0.8) + faraday (>= 0.8.0) + http-cookie (>= 1.0.0) + faraday-follow_redirects (0.5.0) + faraday (>= 1, < 3) + faraday-multipart (1.2.0) + multipart-post (~> 2.0) + faraday-net_http (3.4.4) + net-http (~> 0.5) + faraday-retry (2.4.0) + faraday (~> 2.0) + fastimage (2.4.1) + fastlane (2.238.0) + CFPropertyList (>= 2.3, < 5.0.0) + abbrev (~> 0.1) + addressable (>= 2.9.0, < 3.0.0) + artifactory (~> 3.0) + aws-sdk-s3 (~> 1.197) + babosa (>= 1.0.3, < 2.0.0) + base64 (~> 0.2) + benchmark (>= 0.1.0) + bundler (>= 2.4.0, < 5.0.0) + colored (~> 1.2) + commander (~> 4.6) + csv (~> 3.3) + dotenv (>= 2.1.1, < 3.0.0) + emoji_regex (>= 0.1, < 4.0) + excon (>= 0.71.0, < 2.0.0) + faraday (~> 2.7) + faraday-cookie_jar (~> 0.0.8) + faraday-follow_redirects (~> 0.3) + faraday-multipart (~> 1.0) + faraday-retry (~> 2.0) + fastimage (>= 2.1.0, < 3.0.0) + fastlane-sirp (>= 1.1.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.3.0) + google-cloud-storage (~> 1.31) + highline (~> 2.0) + http-cookie (~> 1.0.5) + irb (>= 1.8) + json (< 3.0.0) + jwt (>= 2.10.3, < 4) + logger (>= 1.6, < 2.0) + mini_magick (>= 4.9.4, < 5.0.0) + multi_json (~> 1.12) + multipart-post (>= 2.0.0, < 3.0.0) + mutex_m (~> 0.3) + naturally (~> 2.2) + nkf (~> 0.2) + optparse (>= 0.1.1, < 1.0.0) + ostruct (>= 0.1.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 (~> 4) + 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-plugin-firebase_app_distribution (1.0.0) + fastlane (>= 2.232.0) + google-apis-firebaseappdistribution_v1 (>= 0.9.0) + google-apis-firebaseappdistribution_v1alpha (>= 0.12.0) + fastlane-sirp (1.1.0) + gh_inspector (1.1.3) + google-apis-androidpublisher_v3 (0.107.0) + google-apis-core (>= 0.15.0, < 2.a) + google-apis-core (1.2.5) + addressable (~> 2.9) + faraday (~> 2.13) + faraday-follow_redirects (~> 0.3) + googleauth (~> 1.14) + mini_mime (~> 1.1) + multi_json (~> 1.11) + representable (~> 3.0) + retriable (>= 3.1, < 5.0) + google-apis-firebaseappdistribution_v1 (0.22.0) + google-apis-core (>= 0.15.0, < 2.a) + google-apis-firebaseappdistribution_v1alpha (0.30.0) + google-apis-core (>= 0.15.0, < 2.a) + google-apis-iamcredentials_v1 (0.28.0) + google-apis-core (>= 0.15.0, < 2.a) + google-apis-playcustomapp_v1 (0.18.0) + google-apis-core (>= 0.15.0, < 2.a) + google-apis-storage_v1 (0.66.0) + google-apis-core (>= 0.15.0, < 2.a) + google-cloud-core (1.9.0) + google-cloud-env (>= 1.0, < 3.a) + google-cloud-errors (~> 1.0) + google-cloud-env (2.2.2) + base64 (~> 0.2) + faraday (>= 1.0, < 3.a) + google-cloud-errors (1.7.0) + google-cloud-storage (1.62.0) + addressable (~> 2.8) + digest-crc (~> 0.4) + google-apis-core (>= 0.18, < 2) + google-apis-iamcredentials_v1 (~> 0.18) + google-apis-storage_v1 (>= 0.42) + google-cloud-core (~> 1.6) + googleauth (~> 1.9) + mini_mime (~> 1.0) + google-logging-utils (0.2.0) + googleauth (1.17.4) + faraday (>= 1.0, < 3.a) + google-cloud-env (~> 2.2) + google-logging-utils (~> 0.1) + jwt (>= 1.4, < 4.0) + os (>= 0.9, < 2.0) + pstore (~> 0.1) + signet (>= 0.16, < 2.a) + highline (2.0.3) + http-cookie (1.0.8) + domain_name (~> 0.5) + io-console (0.9.2) + irb (1.18.0) + pp (>= 0.6.0) + prism (>= 1.3.0) + rdoc (>= 4.0.0) + reline (>= 0.4.2) + jmespath (1.6.2) + json (2.21.2) + jwt (3.2.0) + base64 + logger (1.7.0) + mini_magick (4.13.2) + mini_mime (1.1.5) + multi_json (1.21.1) + multipart-post (2.4.1) + mutex_m (0.3.0) + nanaimo (0.4.0) + naturally (2.3.0) + net-http (0.9.1) + uri (>= 0.11.1) + nkf (0.3.0) + optparse (0.8.1) + os (1.1.4) + ostruct (0.6.3) + plist (3.7.2) + pp (0.6.4) + prettyprint + prettyprint (0.2.0) + prism (1.9.0) + pstore (0.2.1) + public_suffix (7.0.5) + rake (13.4.2) + rbs (4.2.0) + logger + prism (>= 1.6.0) + tsort + rdoc (8.0.0) + erb + prism (>= 1.6.0) + rbs (>= 4.0.0) + tsort + reline (0.7.0) + io-console (~> 0.5) + representable (3.2.0) + declarative (< 0.1.0) + trailblazer-option (>= 0.1.1, < 0.2.0) + uber (< 0.2.0) + retriable (4.2.0) + rexml (3.4.4) + rouge (3.28.0) + rubyzip (2.4.1) + security (0.1.5) + signet (0.22.0) + addressable (~> 2.8) + faraday (>= 0.17.5, < 3.a) + jwt (>= 1.5, < 4.0) + simctl (1.6.10) + CFPropertyList + naturally + terminal-notifier (2.0.0) + terminal-table (4.0.0) + unicode-display_width (>= 1.1.1, < 4) + trailblazer-option (0.1.2) + tsort (0.2.0) + 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 (3.2.0) + unicode-emoji (~> 4.1) + unicode-emoji (4.2.0) + uri (1.1.1) + word_wrap (1.0.0) + xcodeproj (1.28.1) + CFPropertyList (>= 2.3.3, < 4.0) + atomos (~> 0.1.3) + base64 + claide (>= 1.0.2, < 2.0) + colored2 (~> 3.1) + nanaimo (~> 0.4.0) + nkf + 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-25 + ruby + +DEPENDENCIES + fastlane + fastlane-plugin-firebase_app_distribution + +CHECKSUMS + CFPropertyList (3.0.8) sha256=2c99d0d980536d3d7ab252f7bd59ac8be50fbdd1ff487c98c949bb66bb114261 + abbrev (0.1.2) sha256=ad1b4eaaaed4cb722d5684d63949e4bde1d34f2a95e20db93aecfe7cbac74242 + addressable (2.9.0) sha256=7fdf6ac3660f7f4e867a0838be3f6cf722ace541dd97767fa42bc6cfa980c7af + artifactory (3.0.17) sha256=3023d5c964c31674090d655a516f38ca75665c15084140c08b7f2841131af263 + atomos (0.1.3) sha256=7d43b22f2454a36bace5532d30785b06de3711399cb1c6bf932573eda536789f + aws-eventstream (1.4.0) sha256=116bf85c436200d1060811e6f5d2d40c88f65448f2125bc77ffce5121e6e183b + aws-partitions (1.1282.0) sha256=d6c7c4ad2e4f8cd4ca56445bfbb3958fec0deecd959c1e2bcd3a83c9aa308c18 + aws-sdk-core (3.254.1) sha256=518089e32134c3478cd4ec63d07fb966546a45e9e4cbf5f80d2cf16d5699d29b + aws-sdk-kms (1.130.0) sha256=a2e83662ca31b77a2a19c9aa2f40a98165a67270c18c718fe1c70d0cbd7cd749 + aws-sdk-s3 (1.229.0) sha256=7dd11a111875b3505d2ec0a0a383a6aab6c1badb3d4e94e7fbb958a24451f596 + aws-sigv4 (1.12.1) sha256=6973ff95cb0fd0dc58ba26e90e9510a2219525d07620c8babeb70ef831826c00 + babosa (1.0.4) sha256=18dea450f595462ed7cb80595abd76b2e535db8c91b350f6c4b3d73986c5bc99 + base64 (0.3.0) sha256=27337aeabad6ffae05c265c450490628ef3ebd4b67be58257393227588f5a97b + benchmark (0.5.0) sha256=465df122341aedcb81a2a24b4d3bd19b6c67c1530713fd533f3ff034e419236c + bigdecimal (4.1.2) sha256=53d217666027eab4280346fba98e7d5b66baaae1b9c3c1c0ffe89d48188a3fbd + bundler (4.0.19) sha256=b48056e4c77fb3853cc47775b5447ede44aaa4f53c32f168014ab4fe86587d47 + claide (1.1.0) sha256=6d3c5c089dde904d96aa30e73306d0d4bd444b1accb9b3125ce14a3c0183f82e + colored (1.2) sha256=9d82b47ac589ce7f6cab64b1f194a2009e9fd00c326a5357321f44afab2c1d2c + colored2 (3.1.2) sha256=b13c2bd7eeae2cf7356a62501d398e72fde78780bd26aec6a979578293c28b4a + commander (4.6.0) sha256=7d1ddc3fccae60cc906b4131b916107e2ef0108858f485fdda30610c0f2913d9 + csv (3.3.6) sha256=aba61e7e507a66f03d45cb1f3c4b6359861c3504038b422962875dce099e4456 + declarative (0.0.20) sha256=8021dd6cb17ab2b61233c56903d3f5a259c5cf43c80ff332d447d395b17d9ff9 + digest-crc (0.7.0) sha256=64adc23a26a241044cbe6732477ca1b3c281d79e2240bcff275a37a5a0d78c07 + domain_name (0.6.20240107) sha256=5f693b2215708476517479bf2b3802e49068ad82167bcd2286f899536a17d933 + dotenv (2.8.1) sha256=c5944793349ae03c432e1780a2ca929d60b88c7d14d52d630db0508c3a8a17d8 + emoji_regex (3.2.3) sha256=ecd8be856b7691406c6bf3bb3a5e55d6ed683ffab98b4aa531bb90e1ddcc564b + erb (6.0.7) sha256=c5ca6dc25b0ef974a44dc8f59fe847577122483b1968a38dec305c60bf91ee92 + excon (1.7.0) sha256=b8deec2bf978123b7e4b13a103ffec61557c6feaba34706dbc1ce4a94dec33e0 + faraday (2.14.3) sha256=1882247e6766615c8220b4392bf1d27f6ebb63d8e28267587cef1fb0bf37f278 + faraday-cookie_jar (0.0.8) sha256=0140605823f8cc63c7028fccee486aaed8e54835c360cffc1f7c8c07c4299dbb + faraday-follow_redirects (0.5.0) sha256=5cde93c894b30943a5d2b93c2fe9284216a6b756f7af406a1e55f211d97d10ad + faraday-multipart (1.2.0) sha256=7d89a949693714176f612323ca13746a2ded204031a6ba528adee788694ef757 + faraday-net_http (3.4.4) sha256=0e78af151747ed1b00f33e25973b4bc220d7f16c00c39676817c8b12331eb588 + faraday-retry (2.4.0) sha256=7b79c48fb7e56526faf247b12d94a680071ff40c9fda7cf1ec1549439ad11ebe + fastimage (2.4.1) sha256=c64bebd46b6fd8943ab70c1e6e85ff728f970f2e48f92ecd249b6bc3a540ad20 + fastlane (2.238.0) sha256=78e9252df2224c7e427012638e41051edfa29b133a40fda883fd2f1c13a77b98 + fastlane-plugin-firebase_app_distribution (1.0.0) sha256=321f554d16194ea465c71202274c75609cf986f147fc7203901d44a1a6df736f + fastlane-sirp (1.1.0) sha256=10bc94f9682efd8e1badfb31452a76dd8981f1f3a33717c765fde6d75b54d847 + gh_inspector (1.1.3) sha256=04cca7171b87164e053aa43147971d3b7f500fcb58177698886b48a9fc4a1939 + google-apis-androidpublisher_v3 (0.107.0) sha256=b8207eecc89e6a9f7ea11aa88a07bccdcae5ca90121330079db4ef4696a2f3ff + google-apis-core (1.2.5) sha256=e21562b5627a0fc6a0c3165e429c3afed0f6a628eaa514e83f7204dda1adff69 + google-apis-firebaseappdistribution_v1 (0.22.0) sha256=1296d201247cc80ac2f170faa3ab47f85b495853d69f9dcee1b2c8af201e20c0 + google-apis-firebaseappdistribution_v1alpha (0.30.0) sha256=a6f84a9080ca17c7168af1d0b8da9a016e9059760ccd0e62e8f086680a91e1f9 + google-apis-iamcredentials_v1 (0.28.0) sha256=0a92ffe6cc39c569554af2a77a25dfc61519ed8bbb64ab04cffdd352dc5ef106 + google-apis-playcustomapp_v1 (0.18.0) sha256=44b277b9dee4a59ac5e9d98be1485edc5e382d2f9d73c79ae8908a455786a254 + google-apis-storage_v1 (0.66.0) sha256=fdf6d65d3fc77e7046b3494333a818ece9e2afd763bb161e1a7a9e70cf198351 + google-cloud-core (1.9.0) sha256=ab55409f51488e8deefb6edcc1ce4771dfb5da2fe7b3bc075709a030c2b682a4 + google-cloud-env (2.2.2) sha256=94bed40e05a67e9468ce1cb38389fba9a90aa8fc62fc9e173204c1dca59e21e7 + google-cloud-errors (1.7.0) sha256=6e682f42d89aae08689f36f28495de629d756da076bafff0003bac7f8042ebb3 + google-cloud-storage (1.62.0) sha256=e2c3c08bf8fd40d50be92304084942203314d4fc0ee52028e99f9359c3ad1330 + google-logging-utils (0.2.0) sha256=675462b4ea5affa825a3442694ca2d75d0069455a1d0956127207498fca3df7b + googleauth (1.17.4) sha256=acc2cac2a6011048f149c922cbc6c9675719e165aa5000c7fa3876c480fe9ae3 + highline (2.0.3) sha256=2ddd5c127d4692721486f91737307236fe005352d12a4202e26c48614f719479 + http-cookie (1.0.8) sha256=b14fe0445cf24bf9ae098633e9b8d42e4c07c3c1f700672b09fbfe32ffd41aa6 + io-console (0.9.2) sha256=efa74f891dd03c0939a931dfc6e74c2813d904763d456ea9762b0525e748db08 + irb (1.18.0) sha256=de9454a0703a54704b9811a5ef31a60c86949fbf4013fcf244fabc7c775248e3 + jmespath (1.6.2) sha256=238d774a58723d6c090494c8879b5e9918c19485f7e840f2c1c7532cf84ebcb1 + json (2.21.2) sha256=1f1d3b7cf2b3ba1a69beca0bb6db13d5438b80bff3cd54cdaaa620b9b07c1c6a + jwt (3.2.0) sha256=5419b1fe37b1da0982bd07051f573a8b8789ab724c2aa7e785e4784a3ed217d7 + logger (1.7.0) sha256=196edec7cc44b66cfb40f9755ce11b392f21f7967696af15d274dde7edff0203 + mini_magick (4.13.2) sha256=71d6258e0e8a3d04a9a0a09784d5d857b403a198a51dd4f882510435eb95ddd9 + mini_mime (1.1.5) sha256=8681b7e2e4215f2a159f9400b5816d85e9d8c6c6b491e96a12797e798f8bccef + multi_json (1.21.1) sha256=e6126a31808e3b4d19f483c775ceac34df190dffa62adfb63a165ee14ba68080 + multipart-post (2.4.1) sha256=9872d03a8e552020ca096adadbf5e3cb1cd1cdd6acd3c161136b8a5737cdb4a8 + mutex_m (0.3.0) sha256=cfcb04ac16b69c4813777022fdceda24e9f798e48092a2b817eb4c0a782b0751 + nanaimo (0.4.0) sha256=faf069551bab17f15169c1f74a1c73c220657e71b6e900919897a10d991d0723 + naturally (2.3.0) sha256=459923cf76c2e6613048301742363200c3c7e4904c324097d54a67401e179e01 + net-http (0.9.1) sha256=25ba0b67c63e89df626ed8fac771d0ad24ad151a858af2cc8e6a716ca4336996 + nkf (0.3.0) sha256=357a8dbeba38b727b75930f665146546076a394a1c243faf634ff176e3588895 + optparse (0.8.1) sha256=42bea10d53907ccff4f080a69991441d611fbf8733b60ed1ce9ee365ce03bd1a + os (1.1.4) sha256=57816d6a334e7bd6aed048f4b0308226c5fb027433b67d90a9ab435f35108d3f + ostruct (0.6.3) sha256=95a2ed4a4bd1d190784e666b47b2d3f078e4a9efda2fccf18f84ddc6538ed912 + plist (3.7.2) sha256=d37a4527cc1116064393df4b40e1dbbc94c65fa9ca2eec52edf9a13616718a42 + pp (0.6.4) sha256=dfcb0fce700c41456265922884f9fe195d7fbb0674a3578e6c0f69588e82b570 + prettyprint (0.2.0) sha256=2bc9e15581a94742064a3cc8b0fb9d45aae3d03a1baa6ef80922627a0766f193 + prism (1.9.0) sha256=7b530c6a9f92c24300014919c9dcbc055bf4cdf51ec30aed099b06cd6674ef85 + pstore (0.2.1) sha256=03904d0f2c66579e96d1e6704cdabc0c88df7ea8ed8782d9f3569f6f6c702c1a + public_suffix (7.0.5) sha256=1a8bb08f1bbea19228d3bed6e5ed908d1cb4f7c2726d18bd9cadf60bc676f623 + rake (13.4.2) sha256=cb825b2bd5f1f8e91ca37bddb4b9aaf345551b4731da62949be002fa89283701 + rbs (4.2.0) sha256=51f7b886dcc05bc09e10b901daa6a81829f6adc03101d6ca9ea4aac6103e0674 + rdoc (8.0.0) sha256=03bf8c08a9639658855a0cfd77c0abca8325c227693f7f33f82957811348c469 + reline (0.7.0) sha256=5b012d8e55dbf9d450f12bde2cf7d15ff546ae80b3f8f3b30e570d431815583d + representable (3.2.0) sha256=cc29bf7eebc31653586849371a43ffe36c60b54b0a6365b5f7d95ec34d1ebace + retriable (4.2.0) sha256=90c3b257472a1c02f2a3dfa64dd35a420f7a624cef1a5981e20fdac5730f8cdc + rexml (3.4.4) sha256=19e0a2c3425dfbf2d4fc1189747bdb2f849b6c5e74180401b15734bc97b5d142 + rouge (3.28.0) sha256=0d6de482c7624000d92697772ab14e48dca35629f8ddf3f4b21c99183fd70e20 + rubyzip (2.4.1) sha256=8577c88edc1fde8935eb91064c5cb1aef9ad5494b940cf19c775ee833e075615 + security (0.1.5) sha256=3a977a0eca7706e804c96db0dd9619e0a94969fe3aac9680fcfc2bf9b8a833b7 + signet (0.22.0) sha256=b76d495ccb07ad35dbc89f3e920665a9d8ed717141955034005d7843dcfe4780 + simctl (1.6.10) sha256=b99077f4d13ad81eace9f86bf5ba4df1b0b893a4d1b368bd3ed59b5b27f9236b + terminal-notifier (2.0.0) sha256=7a0d2b2212ab9835c07f4b2e22a94cff64149dba1eed203c04835f7991078cea + terminal-table (4.0.0) sha256=f504793203f8251b2ea7c7068333053f0beeea26093ec9962e62ea79f94301d2 + trailblazer-option (0.1.2) sha256=20e4f12ea4e1f718c8007e7944ca21a329eee4eed9e0fa5dde6e8ad8ac4344a3 + tsort (0.2.0) sha256=9650a793f6859a43b6641671278f79cfead60ac714148aabe4e3f0060480089f + tty-cursor (0.7.1) sha256=79534185e6a777888d88628b14b6a1fdf5154a603f285f80b1753e1908e0bf48 + tty-screen (0.8.2) sha256=c090652115beae764336c28802d633f204fb84da93c6a968aa5d8e319e819b50 + tty-spinner (0.9.3) sha256=0e036f047b4ffb61f2aa45f5a770ec00b4d04130531558a94bfc5b192b570542 + uber (0.1.0) sha256=5beeb407ff807b5db994f82fa9ee07cfceaa561dad8af20be880bc67eba935dc + unicode-display_width (3.2.0) sha256=0cdd96b5681a5949cdbc2c55e7b420facae74c4aaf9a9815eee1087cb1853c42 + unicode-emoji (4.2.0) sha256=519e69150f75652e40bf736106cfbc8f0f73aa3fb6a65afe62fefa7f80b0f80f + uri (1.1.1) sha256=379fa58d27ffb1387eaada68c749d1426738bd0f654d812fcc07e7568f5c57c6 + word_wrap (1.0.0) sha256=f556d4224c812e371000f12a6ee8102e0daa724a314c3f246afaad76d82accc7 + xcodeproj (1.28.1) sha256=6f12670f00739d9817ca27ac89d6ef01cc86050e22a0bc08a3131487e5b5cddc + xcpretty (0.4.1) sha256=b14c50e721f6589ee3d6f5353e2c2cfcd8541fa1ea16d6c602807dd7327f3892 + xcpretty-travis-formatter (1.0.1) sha256=aacc332f17cb7b2cba222994e2adc74223db88724fe76341483ad3098e232f93 + +BUNDLED WITH + 4.0.19 diff --git a/fastlane/README.md b/fastlane/README.md index fd2bd29..fa9b392 100644 --- a/fastlane/README.md +++ b/fastlane/README.md @@ -20,11 +20,24 @@ bundle exec fastlane android beta notes:"공유 기능 추가" groups:"testers,q ### 1) fastlane 설치 +시스템 gem 폴더 대신 **프로젝트 로컬(`vendor/bundle`)** 에 설치한다(권한 이슈 회피, `.gitignore` 처리됨). + ```bash gem install bundler +bundle config set --local path 'vendor/bundle' bundle install # Gemfile + fastlane/Pluginfile 의 플러그인까지 설치 ``` +> **권한 오류**(`Permission denied @ rb_sysopen ... /opt/homebrew/.../plugins/rdoc_plugin.rb`)가 나면, +> 위 `bundle config set --local path 'vendor/bundle'` 를 먼저 실행한 뒤 `bundle install` 하면 된다. +> (Homebrew Ruby 전역 폴더에 쓰려다 막히는 문제 — 로컬 설치로 우회) + +설치 확인: + +```bash +bundle exec fastlane lanes +``` + ### 2) Firebase App Distribution 인증 (서비스 계정) CI/자동화에서는 서비스 계정 JSON을 쓴다. From 00f79668021418656d13fd6a0776d45c647511c0 Mon Sep 17 00:00:00 2001 From: oilbeaneda <20221444@sungshin.ac.kr> Date: Wed, 26 Aug 2026 12:03:06 +0900 Subject: [PATCH 5/8] =?UTF-8?q?[setting/#34]=20beta=20=EB=B0=B0=ED=8F=AC?= =?UTF-8?q?=20=EA=B2=80=EC=A6=9D=20=EB=B0=98=EC=98=81=20=E2=80=94=20?= =?UTF-8?q?=EA=B7=B8=EB=A3=B9=20alias(=EB=AA=A8=EC=97=AC=EB=B3=BC)=20?= =?UTF-8?q?=EA=B8=B0=EB=B3=B8=EA=B0=92=C2=B7skip=5Fdocs=C2=B7UTF-8/?= =?UTF-8?q?=ED=8A=B8=EB=9F=AC=EB=B8=94=EC=8A=88=ED=8C=85=20=EB=AC=B8?= =?UTF-8?q?=EC=84=9C=ED=99=94?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- fastlane/Fastfile | 7 +++++-- fastlane/README.md | 22 +++++++++++++++++----- 2 files changed, 22 insertions(+), 7 deletions(-) diff --git a/fastlane/Fastfile b/fastlane/Fastfile index f81ffea..602e0b3 100644 --- a/fastlane/Fastfile +++ b/fastlane/Fastfile @@ -5,6 +5,9 @@ require "json" default_platform(:android) +# fastlane 실행 시 fastlane/README.md 자동 생성/덮어쓰기 방지 (직접 작성한 가이드 유지). +skip_docs + # google-services.json 에서 Firebase App ID를 읽어온다. # (google-services.json 은 .gitignore 대상이라 값을 코드에 하드코딩하지 않는다.) # CI 등에서 파일이 없으면 FIREBASE_APP_ID 환경변수로 대체 가능. @@ -26,7 +29,7 @@ platform :android do end desc "테스터에게 배포 (Firebase App Distribution)\n" \ - "옵션 예) fastlane android beta notes:\"수정 내용\" groups:\"testers,qa\"" + "옵션 예) fastlane android beta notes:\"수정 내용\" groups:\"모여볼\"" lane :beta do |options| # 배포 전 품질 게이트 test @@ -42,7 +45,7 @@ platform :android do android_artifact_type: "APK", android_artifact_path: lane_context[SharedValues::GRADLE_APK_OUTPUT_PATH], service_credentials_file: ENV["FIREBASE_SERVICE_ACCOUNT_JSON"], - groups: options[:groups] || "testers", + groups: options[:groups] || "모여볼", # Firebase App Distribution 그룹 alias (콘솔의 alias와 정확히 일치해야 함) release_notes: options[:notes] || last_git_commit[:message], ) diff --git a/fastlane/README.md b/fastlane/README.md index fa9b392..b66bf55 100644 --- a/fastlane/README.md +++ b/fastlane/README.md @@ -12,8 +12,8 @@ `beta` 옵션: ```bash -# 릴리즈 노트 / 대상 그룹 지정 -bundle exec fastlane android beta notes:"공유 기능 추가" groups:"testers,qa" +# 릴리즈 노트 / 대상 그룹 지정 (groups 미지정 시 기본값 "모여볼") +bundle exec fastlane android beta notes:"공유 기능 추가" groups:"모여볼" ``` ## 최초 1회 세팅 @@ -53,10 +53,13 @@ export FIREBASE_SERVICE_ACCOUNT_JSON="/절대/경로/service-account.json" > 로컬에서 잠깐 테스트만 할 땐 `firebase login` 후 `FIREBASE_SERVICE_ACCOUNT_JSON` 없이 실행해도 된다(대화형 인증). -### 3) 테스터 그룹 생성 +### 3) 테스터 그룹 -Firebase 콘솔 → App Distribution → **테스터 및 그룹** → `testers` 그룹 생성 후 테스터 추가. -(다른 그룹명을 쓰면 `groups:` 옵션으로 지정) +Firebase 콘솔 → App Distribution → **테스터 및 그룹** 에서 그룹을 만들고 테스터 이메일을 추가한다. + +> ⚠️ **`groups:` 값은 그룹의 표시 이름이 아니라 `alias`(별칭)와 정확히 일치해야 한다.** +> 현재 프로젝트 그룹 alias는 **`모여볼`** 이고, `Fastfile`의 기본값도 이걸로 설정돼 있다. +> (다른 그룹을 쓰려면 콘솔에서 alias를 확인해 `groups:""` 로 지정) ## 참고 @@ -64,6 +67,15 @@ Firebase 콘솔 → App Distribution → **테스터 및 그룹** → `testers` - 현재 `beta`는 **debug 서명 APK**를 배포한다(별도 upload keystore 불필요). 릴리즈 서명(Play 앱 서명 + upload keystore)이 준비되면 `Fastfile`의 `build_type`을 `"Release"`로 바꾼다. - Google Play 스토어 직접 배포(`supply`)는 upload keystore + Play Console 서비스 계정이 준비되면 `Fastfile`의 주석 처리된 `deploy` 레인을 활성화한다. (#34 후속) +## 문제 해결 + +- **`[!] Invalid request (Google::Apis::ClientError)`** — 업로드는 됐는데 "Distributing release"에서 실패하면, `groups:` 의 **그룹 alias가 콘솔과 불일치**하는 경우다. 콘솔에서 alias를 확인해 정확히 맞춘다. (현재 alias: `모여볼`) +- **`fastlane requires your locale to be set to UTF-8`** / 한글 릴리즈 노트 깨짐 — 로케일을 UTF-8로 설정한다: + ```bash + export LANG=en_US.UTF-8 LC_ALL=en_US.UTF-8 + ``` +- `fastlane/README.md` 는 `Fastfile` 상단의 `skip_docs` 로 자동 덮어쓰기를 막아둔 상태다(직접 작성한 가이드 유지). + ## CI (GitHub Actions) 연동 예시 ```yaml From e69c1dc66a3d7e527ee4c9d778d8e39fa19209b7 Mon Sep 17 00:00:00 2001 From: oilbeaneda <20221444@sungshin.ac.kr> Date: Wed, 26 Aug 2026 12:09:29 +0900 Subject: [PATCH 6/8] =?UTF-8?q?[setting/#34]=20=EB=A6=B4=EB=A6=AC=EC=A6=88?= =?UTF-8?q?=20=EC=84=9C=EB=AA=85=20=EC=84=A4=EC=A0=95=20=EB=B0=B0=EC=84=A0?= =?UTF-8?q?=20(local.properties/env=EC=9D=98=20upload=20keystore)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/build.gradle.kts | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/app/build.gradle.kts b/app/build.gradle.kts index d8f8b9a..058e70a 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -37,6 +37,12 @@ val naverLoginClientName = properties.getProperty("naver_login_client_name") ?: System.getenv("NAVER_LOGIN_CLIENT_NAME") ?: "" +// 릴리즈 서명(upload keystore) — local.properties 또는 CI 환경변수에서 읽는다. 키 파일/비밀번호는 git 커밋 금지. +val releaseStoreFile = properties.getProperty("keystore.file") ?: System.getenv("KEYSTORE_FILE") +val releaseStorePassword = properties.getProperty("keystore.password") ?: System.getenv("KEYSTORE_PASSWORD") +val releaseKeyAlias = properties.getProperty("key.alias") ?: System.getenv("KEY_ALIAS") +val releaseKeyPassword = properties.getProperty("key.password") ?: System.getenv("KEY_PASSWORD") + android { namespace = "com.moball.app" compileSdk = libs.versions.compileSdk @@ -45,7 +51,13 @@ android { signingConfigs { create("release") { - // TODO: Add signing config details + // keystore 정보가 갖춰졌을 때만 서명 설정. 없으면 릴리즈는 미서명으로 빌드된다. + if (releaseStoreFile != null && file(releaseStoreFile).exists()) { + storeFile = file(releaseStoreFile) + storePassword = releaseStorePassword + keyAlias = releaseKeyAlias + keyPassword = releaseKeyPassword + } } } From 66cbfcc56138462c3e369a19f0c54f7de9b7f772 Mon Sep 17 00:00:00 2001 From: oilbeaneda <20221444@sungshin.ac.kr> Date: Wed, 26 Aug 2026 12:09:30 +0900 Subject: [PATCH 7/8] =?UTF-8?q?[setting/#34]=20Google=20Play=20=EB=B0=B0?= =?UTF-8?q?=ED=8F=AC=20lane(deploy,=20supply)=20=EC=B6=94=EA=B0=80=20+=20?= =?UTF-8?q?=EC=84=B8=ED=8C=85=20=EA=B0=80=EC=9D=B4=EB=93=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- fastlane/Appfile | 5 ++++- fastlane/Fastfile | 34 +++++++++++++++++++++++++-------- fastlane/README.md | 47 ++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 77 insertions(+), 9 deletions(-) diff --git a/fastlane/Appfile b/fastlane/Appfile index b0f34b0..eb099f3 100644 --- a/fastlane/Appfile +++ b/fastlane/Appfile @@ -1,2 +1,5 @@ -json_key_file("") # Google Play(supply) 배포 시 사용. Firebase 배포에는 불필요. +# Google Play(supply) 인증용 서비스 계정 JSON — 환경변수 PLAY_SERVICE_ACCOUNT_JSON 로 경로 지정. +# (Firebase App Distribution 배포에는 불필요) +json_key_file(ENV["PLAY_SERVICE_ACCOUNT_JSON"]) if ENV["PLAY_SERVICE_ACCOUNT_JSON"] + package_name("com.moball.app") diff --git a/fastlane/Fastfile b/fastlane/Fastfile index 602e0b3..7faa49d 100644 --- a/fastlane/Fastfile +++ b/fastlane/Fastfile @@ -52,12 +52,30 @@ platform :android do UI.success("Firebase App Distribution 업로드 완료 🚀") end - # TODO(#34): 업로드 keystore + Play Console 서비스 계정 준비되면 - # Google Play 배포 레인(supply) 추가. - # desc "Google Play 내부 테스트 트랙 배포" - # lane :deploy do - # gradle(task: "clean") - # gradle(task: "bundle", build_type: "Release") - # upload_to_play_store(track: "internal", aab: lane_context[SharedValues::GRADLE_AAB_OUTPUT_PATH]) - # end + desc "Google Play 업로드 (supply)\n" \ + "옵션 예) fastlane android deploy track:internal\n" \ + "track: internal(기본)/alpha/beta/production, status: completed(기본)/draft" + lane :deploy do |options| + # 배포 전 품질 게이트 + test + + # 릴리즈 서명된 AAB 빌드 (Play 는 APK 가 아니라 AAB) + # ⚠️ local.properties 에 keystore.* 가 설정돼 있어야 서명된다. + gradle(task: "clean") + gradle(task: "bundle", build_type: "Release") + + # Play Console 업로드 (스토어 리스팅/스크린샷은 건드리지 않고 바이너리만 올린다) + # 인증(json_key)은 fastlane/Appfile 의 PLAY_SERVICE_ACCOUNT_JSON 에서 읽는다. + upload_to_play_store( + track: options[:track] || "internal", + aab: lane_context[SharedValues::GRADLE_AAB_OUTPUT_PATH], + release_status: options[:status] || "completed", + skip_upload_apk: true, + skip_upload_metadata: true, + skip_upload_images: true, + skip_upload_screenshots: true, + ) + + UI.success("Google Play (#{options[:track] || "internal"} 트랙) 업로드 완료 🚀") + end end diff --git a/fastlane/README.md b/fastlane/README.md index b66bf55..f0c2cee 100644 --- a/fastlane/README.md +++ b/fastlane/README.md @@ -8,6 +8,7 @@ |------|------|------| | `test` | ktlint + 단위 테스트 | `bundle exec fastlane android test` | | `beta` | 빌드 후 테스터에게 배포 (Firebase App Distribution) | `bundle exec fastlane android beta` | +| `deploy` | 릴리즈 AAB 빌드 후 Google Play 업로드 (supply) | `bundle exec fastlane android deploy` | `beta` 옵션: @@ -67,6 +68,52 @@ Firebase 콘솔 → App Distribution → **테스터 및 그룹** 에서 그룹 - 현재 `beta`는 **debug 서명 APK**를 배포한다(별도 upload keystore 불필요). 릴리즈 서명(Play 앱 서명 + upload keystore)이 준비되면 `Fastfile`의 `build_type`을 `"Release"`로 바꾼다. - Google Play 스토어 직접 배포(`supply`)는 upload keystore + Play Console 서비스 계정이 준비되면 `Fastfile`의 주석 처리된 `deploy` 레인을 활성화한다. (#34 후속) +## Google Play 배포(`deploy`) 세팅 + +`deploy` 레인은 **릴리즈 서명된 AAB**를 빌드해 Google Play에 업로드한다(`supply`). 다음 2가지가 필요하다. + +### A) 업로드 keystore (릴리즈 서명) + +> ⚠️ 앱이 이미 Play에 출시돼 있으면 **최초 출시 때 서명한 그 upload keystore를 그대로** 써야 한다. 다른 키로 서명하면 Play가 업데이트를 거부한다. (새 키 생성 ❌) + +`local.properties` 에 keystore 정보를 추가한다 (이 파일은 git에 안 올라감): + +```properties +keystore.file=/절대/경로/moball-upload.jks +keystore.password=**** +key.alias=**** +key.password=**** +``` + +> 값이 없으면 릴리즈는 **미서명**으로 빌드된다(=Play 업로드 불가). CI에서는 같은 이름의 환경변수(`KEYSTORE_FILE` 등)로 대체 가능. + +### B) Play Console 서비스 계정 (업로드 권한) + +Firebase 것과 **별개**다. + +1. [Play Console](https://play.google.com/console) → **설정 → API 액세스** → Google Cloud 프로젝트 연결 +2. 서비스 계정 생성 → JSON 키 다운로드 +3. Play Console에서 그 서비스 계정에 **앱 접근 권한**(최소 "출시 관리자"/"버전 업로드") 부여 +4. 환경변수로 경로 지정: + +```bash +export PLAY_SERVICE_ACCOUNT_JSON="/절대/경로/play-service-account.json" +``` + +### 사용 + +```bash +# 내부 테스트 트랙(기본)에 업로드 +bundle exec fastlane android deploy + +# 트랙/상태 지정: track = internal(기본)/alpha/beta/production, status = completed(기본)/draft +bundle exec fastlane android deploy track:internal +bundle exec fastlane android deploy track:production status:draft +``` + +> ⚠️ **매 업로드마다 `versionCode`를 올려야 한다.** Play는 같은 versionCode 재업로드를 거부한다. +> 현재는 `gradle/libs.versions.toml` 의 `versionCode` 를 수동으로 증가시킨다. + ## 문제 해결 - **`[!] Invalid request (Google::Apis::ClientError)`** — 업로드는 됐는데 "Distributing release"에서 실패하면, `groups:` 의 **그룹 alias가 콘솔과 불일치**하는 경우다. 콘솔에서 alias를 확인해 정확히 맞춘다. (현재 alias: `모여볼`) From 98af40db4d257b8227232cdacce915400e3fd6fc Mon Sep 17 00:00:00 2001 From: oilbeaneda <20221444@sungshin.ac.kr> Date: Wed, 26 Aug 2026 13:58:31 +0900 Subject: [PATCH 8/8] =?UTF-8?q?[fix/#34]=20=EB=A6=B4=EB=A6=AC=EC=A6=88=20?= =?UTF-8?q?=EC=84=9C=EB=AA=85=EA=B0=92=204=EA=B0=9C+keystore=20=ED=8C=8C?= =?UTF-8?q?=EC=9D=BC=20=EB=AA=A8=EB=91=90=20=EA=B0=96=EC=B6=B0=EC=A1=8C?= =?UTF-8?q?=EC=9D=84=20=EB=95=8C=EB=A7=8C=20signingConfig=20=EC=97=B0?= =?UTF-8?q?=EA=B2=B0=20(=EB=B6=88=EC=99=84=EC=A0=84=20=EC=8B=9C=20?= =?UTF-8?q?=EB=AF=B8=EC=84=9C=EB=AA=85)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/build.gradle.kts | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 058e70a..f29211b 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -43,6 +43,14 @@ val releaseStorePassword = properties.getProperty("keystore.password") ?: System val releaseKeyAlias = properties.getProperty("key.alias") ?: System.getenv("KEY_ALIAS") val releaseKeyPassword = properties.getProperty("key.password") ?: System.getenv("KEY_PASSWORD") +// 4개 값이 모두 채워지고 keystore 파일이 실제로 존재할 때만 릴리즈 서명을 구성/연결한다. +// (일부만 채워진 채 signingConfig를 연결하면 AGP가 릴리즈 빌드에서 서명 검증 실패를 냄) +val hasReleaseSigning = !releaseStoreFile.isNullOrBlank() && + !releaseStorePassword.isNullOrBlank() && + !releaseKeyAlias.isNullOrBlank() && + !releaseKeyPassword.isNullOrBlank() && + file(releaseStoreFile).isFile + android { namespace = "com.moball.app" compileSdk = libs.versions.compileSdk @@ -52,7 +60,7 @@ android { signingConfigs { create("release") { // keystore 정보가 갖춰졌을 때만 서명 설정. 없으면 릴리즈는 미서명으로 빌드된다. - if (releaseStoreFile != null && file(releaseStoreFile).exists()) { + if (hasReleaseSigning) { storeFile = file(releaseStoreFile) storePassword = releaseStorePassword keyAlias = releaseKeyAlias @@ -100,7 +108,10 @@ android { ) buildConfigField("String", "BASE_URL", "\"${properties.getProperty("prod.base.url") ?: ""}\"") buildConfigField("Boolean", "USE_MOCK_SERVER", "false") - signingConfig = signingConfigs.getByName("release") + // keystore가 완전히 갖춰졌을 때만 릴리즈 서명 연결. 없으면 미서명으로 빌드(디버그/CI에서 검증 실패 방지). + if (hasReleaseSigning) { + signingConfig = signingConfigs.getByName("release") + } } } compileOptions {