diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml new file mode 100644 index 0000000..d02df72 --- /dev/null +++ b/.github/workflows/CI.yml @@ -0,0 +1,58 @@ +# 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: CI + +on: + pull_request: + branches: [ "develop" ] + +jobs: + build: + runs-on: macos-15 + + steps: + - name: 1️⃣ Checkout Project + uses: actions/checkout@v4 + + - name: 2️⃣ SSH key 설치 + uses: shimataro/ssh-key-action@v2 + with: + key: ${{ secrets.SSH_KEY }} + known_hosts: ${{ secrets.KNOWN_HOSTS }} + + - name: 3️⃣ 보안 설정 파일 가져오기 + env: + CONFIG_REPO: ${{ secrets.PRIVATE_REPO }} + TEMP_DIR: "temp_private_configs" + run: | + git clone $CONFIG_REPO $TEMP_DIR + + SOURCE_PATH="$TEMP_DIR/PrivateFile" + + echo "📁 파일 복사 중: $SOURCE_PATH" + cp "$SOURCE_PATH/Debug.xcconfig" xcconfigs/ + cp "$SOURCE_PATH/Release.xcconfig" xcconfigs/ + cp "$SOURCE_PATH/.env.default" fastlane/ + + rm -rf "$TEMP_DIR" + echo "✅ 보안 파일 배치 완료" + + # mise-action을 써서 tuist, ruby 등 도구 설치 (PATH 자동 등록) + - name: 4️⃣ mise를 통한 도구 설치 (Tuist/Ruby) + uses: jdx/mise-action@v2 + with: + install: true + + - name: 5️⃣ 의존성 설치 및 프로젝트 생성 + run: | + bundle install + tuist install + tuist generate --no-open + + - name: 6️⃣ Build Check & Discord Notification + env: + DISCORD_URL: ${{ secrets.DISCORD_URL }} + MATCH_PASSWORD: ${{ secrets.MATCH_PASSWORD }} + run: | + bundle exec fastlane ci_check diff --git a/.gitignore b/.gitignore index 5183772..9fbec88 100644 --- a/.gitignore +++ b/.gitignore @@ -87,3 +87,6 @@ Tuist/.build # Fastlane.swift runner binary **/fastlane/FastlaneRunner + +# 환경 변수 +**/fastlane/.env.default diff --git a/.ruby-version b/.ruby-version new file mode 100644 index 0000000..be94e6f --- /dev/null +++ b/.ruby-version @@ -0,0 +1 @@ +3.2.2 diff --git a/.swiftlint.yml b/.swiftlint.yml new file mode 100644 index 0000000..1cae64d --- /dev/null +++ b/.swiftlint.yml @@ -0,0 +1,49 @@ +# 검사에서 제외할 디렉토리 지정 +excluded: + - Tuist + - Tuist/Dependencies + - Carthage + - Pods + - .build + - Derived + - fastlane + - "**/*.generated.swift" + - "**/Derived/**" + - "**/*.tuist.generated.swift" + - "**/TuistBundle+*.swift" + - "**/TuistAssets+*.swift" + - "**/Project.swift" + - Scripts + +# 추가로 활성화할 규칙들 +opt_in_rules: + - empty_count # count == 0 대신 isEmpty 사용 권장 + - empty_string # "" 대신 String() 사용 권장 + - fatal_error_message # fatalError 메시지 필수 + - first_where # filter().first 대신 first(where:) 사용 권장 + - force_unwrapping # 강제 언래핑(!) 사용 제한 + - implicit_return # 암시적 return 사용 제한 + - modifier_order # 수정자 순서 강제 + - operator_usage_whitespace # 연산자 주변 공백 + - sorted_imports # import 알파벳 순 정렬 + +# 비활성화할 일반적인 규칙 +disabled_rules: + - trailing_whitespace + - todo + - trailing_newline + - multiple_closures_with_trailing_closure + - leading_whitespace + +# 라인 길이 설정 +line_length: + warning: 120 + error: 300 + +# 타입 본문 길이 설정 +type_body_length: + warning: 400 + error: 500 + +analyzer_rules: + - unused_import # 사용하지 않는 import diff --git a/Gemfile b/Gemfile new file mode 100644 index 0000000..f80c3f6 --- /dev/null +++ b/Gemfile @@ -0,0 +1,6 @@ +source "https://rubygems.org" + +gem "fastlane", "~> 2.230" + +plugins_path = File.join(File.dirname(__FILE__), 'fastlane', 'Pluginfile') +eval_gemfile(plugins_path) if File.exist?(plugins_path) diff --git a/Gemfile.lock b/Gemfile.lock new file mode 100644 index 0000000..ec5668b --- /dev/null +++ b/Gemfile.lock @@ -0,0 +1,330 @@ +GEM + remote: https://rubygems.org/ + specs: + CFPropertyList (3.0.8) + abbrev (0.1.2) + addressable (2.8.8) + public_suffix (>= 2.0.2, < 8.0) + artifactory (3.0.17) + atomos (0.1.3) + aws-eventstream (1.4.0) + aws-partitions (1.1206.0) + aws-sdk-core (3.241.4) + 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.121.0) + aws-sdk-core (~> 3, >= 3.241.4) + aws-sigv4 (~> 1.5) + aws-sdk-s3 (1.212.0) + aws-sdk-core (~> 3, >= 3.241.4) + 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.2.0) + bigdecimal (4.0.1) + claide (1.1.0) + colored (1.2) + colored2 (3.1.2) + commander (4.6.0) + highline (~> 2.0.0) + csv (3.3.5) + 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.8) + 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.2.0) + 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.231.0) + CFPropertyList (>= 2.3, < 4.0.0) + abbrev (~> 0.1.2) + addressable (>= 2.8, < 3.0.0) + artifactory (~> 3.0) + aws-sdk-s3 (~> 1.0) + babosa (>= 1.0.3, < 2.0.0) + base64 (~> 0.2.0) + bundler (>= 1.17.3, < 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, < 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) + logger (>= 1.6, < 2.0) + mini_magick (>= 4.9.4, < 5.0.0) + multipart-post (>= 2.0.0, < 3.0.0) + mutex_m (~> 0.3.0) + naturally (~> 2.2) + nkf (~> 0.2.0) + 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.18.0) + jwt (2.10.2) + base64 + logger (1.7.0) + mini_magick (4.13.2) + mini_mime (1.1.5) + multi_json (1.19.1) + multipart-post (2.4.1) + mutex_m (0.3.0) + nanaimo (0.4.0) + naturally (2.3.0) + nkf (0.2.0) + optparse (0.8.1) + os (1.1.4) + plist (3.7.2) + public_suffix (7.0.2) + rake (13.3.1) + 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-24 + ruby + +DEPENDENCIES + fastlane (~> 2.230) + +CHECKSUMS + CFPropertyList (3.0.8) sha256=2c99d0d980536d3d7ab252f7bd59ac8be50fbdd1ff487c98c949bb66bb114261 + abbrev (0.1.2) sha256=ad1b4eaaaed4cb722d5684d63949e4bde1d34f2a95e20db93aecfe7cbac74242 + addressable (2.8.8) sha256=7c13b8f9536cf6364c03b9d417c19986019e28f7c00ac8132da4eb0fe393b057 + artifactory (3.0.17) sha256=3023d5c964c31674090d655a516f38ca75665c15084140c08b7f2841131af263 + atomos (0.1.3) sha256=7d43b22f2454a36bace5532d30785b06de3711399cb1c6bf932573eda536789f + aws-eventstream (1.4.0) sha256=116bf85c436200d1060811e6f5d2d40c88f65448f2125bc77ffce5121e6e183b + aws-partitions (1.1206.0) sha256=9016c2d4f5d633aeb95d4cfeb936b5de3d752a5dbae4838bc19d53f202749301 + aws-sdk-core (3.241.4) sha256=a42ccba8c24ea9800e7b6c40aa201c967458f7c460044a6eebf64fbf1226e4fd + aws-sdk-kms (1.121.0) sha256=d563c1cfb4b5754efbc671216c8eca875338748adad0f42518c28dfa0a2d01e0 + aws-sdk-s3 (1.212.0) sha256=1e8ec4eee6914818dc79840e05c04fba9e61e8aa4822eef283fe593b93397c19 + aws-sigv4 (1.12.1) sha256=6973ff95cb0fd0dc58ba26e90e9510a2219525d07620c8babeb70ef831826c00 + babosa (1.0.4) sha256=18dea450f595462ed7cb80595abd76b2e535db8c91b350f6c4b3d73986c5bc99 + base64 (0.2.0) sha256=0f25e9b21a02a0cc0cea8ef92b2041035d39350946e8789c562b2d1a3da01507 + bigdecimal (4.0.1) sha256=8b07d3d065a9f921c80ceaea7c9d4ae596697295b584c296fe599dd0ad01c4a7 + 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.5) sha256=6e5134ac3383ef728b7f02725d9872934f523cb40b961479f69cf3afa6c8e73f + 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 + excon (0.112.0) sha256=daf9ac3a4c2fc9aa48383a33da77ecb44fa395111e973084d5c52f6f214ae0f0 + faraday (1.10.4) sha256=a384c541cde688d68bf85055723aecb4100c3fa41b53beb2011b245960ab2f19 + faraday-cookie_jar (0.0.8) sha256=0140605823f8cc63c7028fccee486aaed8e54835c360cffc1f7c8c07c4299dbb + faraday-em_http (1.0.0) sha256=7a3d4c7079789121054f57e08cd4ef7e40ad1549b63101f38c7093a9d6c59689 + faraday-em_synchrony (1.0.1) sha256=bf3ce45dcf543088d319ab051f80985ea6d294930635b7a0b966563179f81750 + faraday-excon (1.1.0) sha256=b055c842376734d7f74350fe8611542ae2000c5387348d9ba9708109d6e40940 + faraday-httpclient (1.0.1) sha256=4c8ff1f0973ff835be8d043ef16aaf54f47f25b7578f6d916deee8399a04d33b + faraday-multipart (1.2.0) sha256=7d89a949693714176f612323ca13746a2ded204031a6ba528adee788694ef757 + faraday-net_http (1.0.2) sha256=63992efea42c925a20818cf3c0830947948541fdcf345842755510d266e4c682 + faraday-net_http_persistent (1.2.0) sha256=0b0cbc8f03dab943c3e1cc58d8b7beb142d9df068b39c718cd83e39260348335 + faraday-patron (1.0.0) sha256=dc2cd7b340bb3cc8e36bcb9e6e7eff43d134b6d526d5f3429c7a7680ddd38fa7 + faraday-rack (1.0.0) sha256=ef60ec969a2bb95b8dbf24400155aee64a00fc8ba6c6a4d3968562bcc92328c0 + faraday-retry (1.0.3) sha256=add154f4f399243cbe070806ed41b96906942e7f5259bb1fe6daf2ec8f497194 + faraday_middleware (1.2.1) sha256=d45b78c8ee864c4783fbc276f845243d4a7918a67301c052647bacabec0529e9 + fastimage (2.4.0) sha256=5fce375e27d3bdbb46c18dbca6ba9af29d3304801ae1eb995771c4796c5ac7e8 + fastlane (2.231.0) sha256=69523272108f1212d0c6a6e6985f22341d0828c11ebc62a7a55a08f595d7721c + fastlane-sirp (1.0.0) sha256=66478f25bcd039ec02ccf65625373fca29646fa73d655eb533c915f106c5e641 + gh_inspector (1.1.3) sha256=04cca7171b87164e053aa43147971d3b7f500fcb58177698886b48a9fc4a1939 + google-apis-androidpublisher_v3 (0.54.0) sha256=8970a72839c8dfa87d290bdf935c641bf18cbd4323bf71d182adc04c0108d210 + google-apis-core (0.11.3) sha256=43217013b129d7d52c31ebf94146646c55f463ed25e68ad7523fb644d5a9cc97 + google-apis-iamcredentials_v1 (0.17.0) sha256=9a6525cfd6ef1c9a355f593bfef11bd0fb30e1d785ef9e5c9da51c3817a0517b + google-apis-playcustomapp_v1 (0.13.0) sha256=959e51f90454b51adc72e5c322b4b4a573f869520c4d7c7c20efd1d262e48fd6 + google-apis-storage_v1 (0.31.0) sha256=03e8cc775e12403e5878899dd4bfb90957e7281e894937f03641c3857262db8f + google-cloud-core (1.8.0) sha256=e572edcbf189cfcab16590628a516cec3f4f63454b730e59f0b36575120281cf + google-cloud-env (1.6.0) sha256=6179acb946975892c7908748df5722a4ebadfc8cf5bb7b0d8d933ca67183fa15 + google-cloud-errors (1.5.0) sha256=b56be28b8c10628125214dde571b925cfcebdbc58619e598250c37a2114f7b4b + google-cloud-storage (1.47.0) sha256=b543d01a9c83495149accd2da77b2cf365d5b6aac21bf3fa21e69dfc8f1eeb76 + googleauth (1.8.1) sha256=814adadaaa1221dce72a67131e3ecbd6d23491a161ec84fb15fd353b87d8c9e7 + highline (2.0.3) sha256=2ddd5c127d4692721486f91737307236fe005352d12a4202e26c48614f719479 + http-cookie (1.0.8) sha256=b14fe0445cf24bf9ae098633e9b8d42e4c07c3c1f700672b09fbfe32ffd41aa6 + httpclient (2.9.0) sha256=4b645958e494b2f86c2f8a2f304c959baa273a310e77a2931ddb986d83e498c8 + jmespath (1.6.2) sha256=238d774a58723d6c090494c8879b5e9918c19485f7e840f2c1c7532cf84ebcb1 + json (2.18.0) sha256=b10506aee4183f5cf49e0efc48073d7b75843ce3782c68dbeb763351c08fd505 + jwt (2.10.2) sha256=31e1ee46f7359883d5e622446969fe9c118c3da87a0b1dca765ce269c3a0c4f4 + logger (1.7.0) sha256=196edec7cc44b66cfb40f9755ce11b392f21f7967696af15d274dde7edff0203 + mini_magick (4.13.2) sha256=71d6258e0e8a3d04a9a0a09784d5d857b403a198a51dd4f882510435eb95ddd9 + mini_mime (1.1.5) sha256=8681b7e2e4215f2a159f9400b5816d85e9d8c6c6b491e96a12797e798f8bccef + multi_json (1.19.1) sha256=7aefeff8f2c854bf739931a238e4aea64592845e0c0395c8a7d2eea7fdd631b7 + 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 + nkf (0.2.0) sha256=fbc151bda025451f627fafdfcb3f4f13d0b22ae11f58c6d3a2939c76c5f5f126 + optparse (0.8.1) sha256=42bea10d53907ccff4f080a69991441d611fbf8733b60ed1ce9ee365ce03bd1a + os (1.1.4) sha256=57816d6a334e7bd6aed048f4b0308226c5fb027433b67d90a9ab435f35108d3f + plist (3.7.2) sha256=d37a4527cc1116064393df4b40e1dbbc94c65fa9ca2eec52edf9a13616718a42 + public_suffix (7.0.2) sha256=9114090c8e4e7135c1fd0e7acfea33afaab38101884320c65aaa0ffb8e26a857 + rake (13.3.1) sha256=8c9e89d09f66a26a01264e7e3480ec0607f0c497a861ef16063604b1b08eb19c + representable (3.2.0) sha256=cc29bf7eebc31653586849371a43ffe36c60b54b0a6365b5f7d95ec34d1ebace + retriable (3.1.2) sha256=0a5a5d0ca4ba61a76fb31a17ab8f7f80281beb040c329d34dfc137a1398688e0 + rexml (3.4.4) sha256=19e0a2c3425dfbf2d4fc1189747bdb2f849b6c5e74180401b15734bc97b5d142 + rouge (3.28.0) sha256=0d6de482c7624000d92697772ab14e48dca35629f8ddf3f4b21c99183fd70e20 + ruby2_keywords (0.0.5) sha256=ffd13740c573b7301cf7a2e61fc857b2a8e3d3aff32545d6f8300d8bae10e3ef + rubyzip (2.4.1) sha256=8577c88edc1fde8935eb91064c5cb1aef9ad5494b940cf19c775ee833e075615 + security (0.1.5) sha256=3a977a0eca7706e804c96db0dd9619e0a94969fe3aac9680fcfc2bf9b8a833b7 + signet (0.21.0) sha256=d617e9fbf24928280d39dcfefba9a0372d1c38187ffffd0a9283957a10a8cd5b + simctl (1.6.10) sha256=b99077f4d13ad81eace9f86bf5ba4df1b0b893a4d1b368bd3ed59b5b27f9236b + sysrandom (1.0.5) sha256=5ac1ac3c2ec64ef76ac91018059f541b7e8f437fbda1ccddb4f2c56a9ccf1e75 + terminal-notifier (2.0.0) sha256=7a0d2b2212ab9835c07f4b2e22a94cff64149dba1eed203c04835f7991078cea + terminal-table (3.0.2) sha256=f951b6af5f3e00203fb290a669e0a85c5dd5b051b3b023392ccfd67ba5abae91 + trailblazer-option (0.1.2) sha256=20e4f12ea4e1f718c8007e7944ca21a329eee4eed9e0fa5dde6e8ad8ac4344a3 + 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 (2.6.0) sha256=12279874bba6d5e4d2728cef814b19197dbb10d7a7837a869bab65da943b7f5a + word_wrap (1.0.0) sha256=f556d4224c812e371000f12a6ee8102e0daa724a314c3f246afaad76d82accc7 + xcodeproj (1.27.0) sha256=8cc7a73b4505c227deab044dce118ede787041c702bc47636856a2e566f854d3 + xcpretty (0.4.1) sha256=b14c50e721f6589ee3d6f5353e2c2cfcd8541fa1ea16d6c602807dd7327f3892 + xcpretty-travis-formatter (1.0.1) sha256=aacc332f17cb7b2cba222994e2adc74223db88724fe76341483ad3098e232f93 + +BUNDLED WITH + 4.0.3 diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..4baa2bf --- /dev/null +++ b/Makefile @@ -0,0 +1,31 @@ +generate: + tuist install + tuist generate + +clean: + rm -rf **/**/**/*.xcodeproj + rm -rf **/**/*.xcodeproj + rm -rf **/*.xcodeproj + rm -rf *.xcworkspace + +reset: + tuist clean + make clean + +regenerate: + make clean + tuist generate + +# Feature 모듈 생성 +feature: + @swift Scripts/GenerateModule.swift + tuist edit + +# 팀원이 파일을 모두 배치한 후 실행 +setup: + @chmod +x Scripts/Onboarding.sh + @/bin/bash Scripts/Onboarding.sh + +# 디바이스 추가 +device: + bundle exec fastlane register_new_device diff --git a/Plugins/ConfigPlugin/Plugin.swift b/Plugins/ConfigPlugin/Plugin.swift new file mode 100644 index 0000000..ff1a242 --- /dev/null +++ b/Plugins/ConfigPlugin/Plugin.swift @@ -0,0 +1,10 @@ +// +// Plugin.swift +// EnvPlugin +// +// Created by 최안용 on 1/13/26. +// + +import ProjectDescription + +let configPlugin = Plugin(name: "ConfigPlugin") diff --git a/Plugins/ConfigPlugin/ProjectDescriptionHelpers/Configurations.swift b/Plugins/ConfigPlugin/ProjectDescriptionHelpers/Configurations.swift new file mode 100644 index 0000000..68683c9 --- /dev/null +++ b/Plugins/ConfigPlugin/ProjectDescriptionHelpers/Configurations.swift @@ -0,0 +1,35 @@ +// +// Configurations.swift +// EnvPlugin +// +// Created by 최안용 on 1/13/26. +// + +import ProjectDescription + +public struct XCConfig { + public struct Path { + static var debug: ProjectDescription.Path { + .relativeToRoot("xcconfigs/Debug.xcconfig") + } + + static var release: ProjectDescription.Path { + .relativeToRoot("xcconfigs/Release.xcconfig") + } + } + + public static let framework: [Configuration] = [ + .debug(name: "Debug", xcconfig: Path.debug), + .release(name: "Release", xcconfig: Path.release) + ] + + public static let demo: [Configuration] = [ + .debug(name: "Debug", xcconfig: Path.debug), + .release(name: "Release", xcconfig: Path.release) + ] + + public static let prod: [Configuration] = [ + .debug(name: "Debug", xcconfig: Path.debug), + .release(name: "Release", xcconfig: Path.release) + ] +} diff --git a/Plugins/DependencyPlugin/Plugin.swift b/Plugins/DependencyPlugin/Plugin.swift new file mode 100644 index 0000000..2798840 --- /dev/null +++ b/Plugins/DependencyPlugin/Plugin.swift @@ -0,0 +1,10 @@ +// +// Plugin.swift +// EnvPlugin +// +// Created by 최안용 on 1/13/26. +// + +import ProjectDescription + +let dependencyPlugin = Plugin(name: "DependencyPlugin") diff --git a/Plugins/DependencyPlugin/ProjectDescriptionHelpers/Dependency+Project.swift b/Plugins/DependencyPlugin/ProjectDescriptionHelpers/Dependency+Project.swift new file mode 100644 index 0000000..4e65b76 --- /dev/null +++ b/Plugins/DependencyPlugin/ProjectDescriptionHelpers/Dependency+Project.swift @@ -0,0 +1,44 @@ +// +// Dependency+Project.swift +// EnvPlugin +// +// Created by 최안용 on 1/13/26. +// + +import ProjectDescription + +public extension TargetDependency { + struct Features { + public struct Home {} + } + + struct Modules {} +} + +public extension TargetDependency { + static let data = TargetDependency.project(target: "Data", path: .data) + static let domain = TargetDependency.project(target: "Domain", path: .domain) + static let core = TargetDependency.project(target: "Core", path: .core) +} + +public extension TargetDependency.Modules { + static let dsKit = TargetDependency.project(target: "DSKit", path: .relativeToModules("DSKit")) + static let networks = TargetDependency.project(target: "Networks", path: .relativeToModules("Networks")) + static let thirdPartyLibs = TargetDependency.project(target: "ThirdPartyLibs", path: .relativeToModules("ThirdPartyLibs")) +} + +public extension TargetDependency.Features { + static func project(name: String, group: String) -> TargetDependency { + .project(target: "\(group)\(name)", path: .relativeToFeature("\(group)\(name)")) + } + + static let baseFeatureDependency = TargetDependency.project(target: "BaseFeatureDependency", path: .relativeToFeature("BaseFeatureDependency")) + + static let rootFeature = TargetDependency.project(target: "RootFeature", path: .relativeToFeature("RootFeature")) +} + +public extension TargetDependency.Features.Home { + static let group = "Home" + + static let feature = TargetDependency.Features.project(name: "Feature", group: group) +} diff --git a/Plugins/DependencyPlugin/ProjectDescriptionHelpers/Dependency+SPM.swift b/Plugins/DependencyPlugin/ProjectDescriptionHelpers/Dependency+SPM.swift new file mode 100644 index 0000000..d4083ed --- /dev/null +++ b/Plugins/DependencyPlugin/ProjectDescriptionHelpers/Dependency+SPM.swift @@ -0,0 +1,21 @@ +// +// Dependency+SPM.swift +// EnvPlugin +// +// Created by 최안용 on 1/13/26. +// + +import ProjectDescription + +public extension TargetDependency { + enum SPM {} +} + +public extension TargetDependency.SPM { + static let SnapKit = TargetDependency.external(name: "SnapKit") + static let Then = TargetDependency.external(name: "Then") + static let Kingfisher = TargetDependency.external(name: "Kingfisher") + static let Moya = TargetDependency.external(name: "Moya") + static let RxSwift = TargetDependency.external(name: "RxSwift") + static let RIBs = TargetDependency.external(name: "RIBs") +} diff --git a/Plugins/DependencyPlugin/ProjectDescriptionHelpers/Path+.swift b/Plugins/DependencyPlugin/ProjectDescriptionHelpers/Path+.swift new file mode 100644 index 0000000..65c3b68 --- /dev/null +++ b/Plugins/DependencyPlugin/ProjectDescriptionHelpers/Path+.swift @@ -0,0 +1,35 @@ +// +// Path+.swift +// EnvPlugin +// +// Created by 최안용 on 1/13/26. +// + +import ProjectDescription + +public extension ProjectDescription.Path { + static func relativeToFeature(_ path: String) -> Self { + return .relativeToRoot("Projects/Features/\(path)") + } + + static func relativeToModules(_ path: String) -> Self { + return .relativeToRoot("Projects/Modules/\(path)") + } + + static var app: Self { + return .relativeToRoot("Projects/App") + } + + static var data: Self { + return .relativeToRoot("Projects/Data") + } + + static var domain: Self { + return .relativeToRoot("Projects/Domain") + } + + static var core: Self { + return .relativeToRoot("Projects/Core") + } +} + diff --git a/Plugins/EnvPlugin/Plugin.swift b/Plugins/EnvPlugin/Plugin.swift new file mode 100644 index 0000000..fe0d2a3 --- /dev/null +++ b/Plugins/EnvPlugin/Plugin.swift @@ -0,0 +1,10 @@ +// +// Plugin.swift +// 27th-App-Team-1-iOSManifests +// +// Created by 최안용 on 1/13/26. +// + +import ProjectDescription + +let envPlugin = Plugin(name: "EnvPlugin") diff --git a/Plugins/EnvPlugin/ProjectDescriptionHelpers/Environment.swift b/Plugins/EnvPlugin/ProjectDescriptionHelpers/Environment.swift new file mode 100644 index 0000000..bd6bfb3 --- /dev/null +++ b/Plugins/EnvPlugin/ProjectDescriptionHelpers/Environment.swift @@ -0,0 +1,29 @@ +// +// Environment.swift +// 27th-App-Team-1-iOSManifests +// +// Created by 최안용 on 1/13/26. +// + +import ProjectDescription + +public enum Environment { + public static let workspaceName = "NDGL-iOS" + public static let deploymentTarget = "17.0" + public static let bundleId = "org.yapp.NDGL" + + public struct App { + public static let displayName = "나도갈래" + public static let version = "1.0.0" + public static let buildNumber = "1" + } +} + +public extension Project { + enum Environment { + public static let organizationName = "NDGL-iOS" + public static let deploymentTarget = DeploymentTargets.iOS("17.0") + public static let bundleId = "org.yapp.NDGL" + public static let appName = "NDGL" + } +} diff --git a/Plugins/EnvPlugin/ProjectDescriptionHelpers/InfoPlist.swift b/Plugins/EnvPlugin/ProjectDescriptionHelpers/InfoPlist.swift new file mode 100644 index 0000000..73a7f17 --- /dev/null +++ b/Plugins/EnvPlugin/ProjectDescriptionHelpers/InfoPlist.swift @@ -0,0 +1,66 @@ +// +// InfoPlist.swift +// 27th-App-Team-1-iOSManifests +// +// Created by 최안용 on 1/13/26. +// + +import ProjectDescription + +public extension Project { + static let appInfoPlist: [String: Plist.Value] = [ + "CFBundleShortVersionString": .string("1.0.0"), + "CFBundleDevelopmentRegion": .string("ko"), + "CFBundleVersion": .string("1"), + "UIUserInterfaceStyle": "Light", + "UISupportedInterfaceOrientations": ["UIInterfaceOrientationPortrait"], + "CFBundleIdentifier": .string("\(Environment.bundleId).App"), + "CFBundleDisplayName": .string("$(APP_DISPLAY_NAME)"), + "UILaunchStoryboardName": .string("LaunchScreen"), + "UIApplicationSceneManifest": .dictionary([ + "UIApplicationSupportsMultipleScenes": .boolean(false), + "UISceneConfigurations": .dictionary([ + "UIWindowSceneSessionRoleApplication": .array([ + .dictionary([ + "UISceneConfigurationName": .string("Default Configuration"), + "UISceneDelegateClassName": .string("$(PRODUCT_MODULE_NAME).SceneDelegate") + ]) + ]) + ]) + ]), + "NSAppTransportSecurity": .dictionary([ + "NSAllowsArbitraryLoads": .boolean(true) + ]), + "ITSAppUsesNonExemptEncryption": .boolean(false) + ] + + static let demoInfoPlist: [String: Plist.Value] = [ + "CFBundleShortVersionString": .string("1.0.0"), + "CFBundleDevelopmentRegion": .string("ko"), + "CFBundleVersion": .string("1"), + "UIUserInterfaceStyle": "Light", + "UISupportedInterfaceOrientations": ["UIInterfaceOrientationPortrait"], + "CFBundleIdentifier": .string("\(Environment.bundleId)"), + "CFBundleDisplayName": .string("$(APP_DISPLAY_NAME)"), + "UILaunchStoryboardName": .string("LaunchScreen"), + "UIApplicationSceneManifest": .dictionary([ + "UIApplicationSupportsMultipleScenes": .boolean(false), + "UISceneConfigurations": .dictionary([ + "UIWindowSceneSessionRoleApplication": .array([ + .dictionary([ + "UISceneConfigurationName": .string("Default Configuration"), + "UISceneDelegateClassName": .string("$(PRODUCT_MODULE_NAME).SceneDelegate") + ]) + ]) + ]) + ]), + "NSAppTransportSecurity": .dictionary([ + "NSAllowsArbitraryLoads": .boolean(true) + ]), + "ITSAppUsesNonExemptEncryption": .boolean(false) + ] + + static let framework: InfoPlist = .extendingDefault(with: [ + "CFBundlePackageType": "FMWK" + ]) +} diff --git a/Plugins/EnvPlugin/ProjectDescriptionHelpers/Settings+Extension.swift b/Plugins/EnvPlugin/ProjectDescriptionHelpers/Settings+Extension.swift new file mode 100644 index 0000000..1af74fa --- /dev/null +++ b/Plugins/EnvPlugin/ProjectDescriptionHelpers/Settings+Extension.swift @@ -0,0 +1,86 @@ +// +// Settings+Extension.swift +// EnvPlugin +// +// Created by 최안용 on 1/13/26. +// + +import ProjectDescription + +public extension Settings { + /// 프레임워크용 기본 설정 + static let frameworkSettings: Settings = .settings( + base: [ + "SKIP_INSTALL": "YES", + "BUILD_LIBRARY_FOR_DISTRIBUTION": "YES", + "DEFINES_MODULE": "YES", + "ENABLE_BITCODE": "NO", + "IPHONEOS_DEPLOYMENT_TARGET": .string(Environment.deploymentTarget), + "SWIFT_VERSION": "6.0", + "CLANG_ENABLE_MODULES": "YES", + "ENABLE_USER_SCRIPT_SANDBOXING": "NO", + "ENABLE_MODULE_VERIFIER": "YES", + "MODULE_VERIFIER_SUPPORTED_LANGUAGES": "objective-c objective-c++" + ] + ) + + /// 앱용 설정 + static func appSettings() -> Settings { + let baseSettings: [String: SettingValue] = [ + "APP_NAME": .string(Environment.App.displayName), + "APP_DISPLAY_NAME": .string(Environment.App.displayName), + "CODE_SIGN_STYLE": "Manual", + "DEVELOPMENT_TEAM": SettingValue(stringLiteral: "$(DEVELOPMENT_TEAM)"), + "MARKETING_VERSION": .string(Environment.App.version), + "CURRENT_PROJECT_VERSION": .string(Environment.App.buildNumber), + "ENABLE_BITCODE": "NO", + "IPHONEOS_DEPLOYMENT_TARGET": .string(Environment.deploymentTarget), + "SWIFT_VERSION": "6.0", + "CLANG_ENABLE_MODULES": "YES", + "ENABLE_USER_SCRIPT_SANDBOXING": "NO", + "ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS": "YES" + ] + + let debugSettings: [String: SettingValue] = [ + "APP_DISPLAY_NAME": .string("\(Environment.App.displayName)-Dev"), + "PRODUCT_NAME": .string("\(Environment.App.displayName)"), + "PROVISIONING_PROFILE_SPECIFIER": SettingValue(stringLiteral: "$(APP_PROVISIONING_PROFILE)"), + "CODE_SIGN_IDENTITY": SettingValue(stringLiteral: "$(CODE_SIGN_IDENTITY)"), + "ENABLE_TESTABILITY": "YES", + "GCC_OPTIMIZATION_LEVEL": "0", + "SWIFT_OPTIMIZATION_LEVEL": "-Onone", + "DEBUG_INFORMATION_FORMAT": "dwarf", + "GCC_PREPROCESSOR_DEFINITIONS": .array(["DEBUG=1"]) + ] + + let releaseSettings: [String: SettingValue] = [ + "APP_DISPLAY_NAME": .string(Environment.App.displayName), + "PRODUCT_NAME": .string("\(Environment.App.displayName)"), + "PROVISIONING_PROFILE_SPECIFIER": SettingValue(stringLiteral: "$(APP_PROVISIONING_PROFILE)"), + "CODE_SIGN_IDENTITY": SettingValue(stringLiteral: "$(CODE_SIGN_IDENTITY)"), + "SWIFT_OPTIMIZATION_LEVEL": "-O", + "ENABLE_TESTABILITY": "NO", + "DEBUG_INFORMATION_FORMAT": "dwarf-with-dsym", + "SWIFT_COMPILATION_MODE": "wholemodule" + ] + + return .settings( + base: baseSettings, + configurations: [ + .debug(name: .debug, settings: debugSettings), + .release(name: .release, settings: releaseSettings) + ] + ) + } + + /// 데모 앱용 설정 + static let demoAppSettings: Settings = .settings( + base: [ + "CODE_SIGN_STYLE": "Automatic", + "DEVELOPMENT_TEAM": SettingValue(stringLiteral: "$(DEVELOPMENT_TEAM)"), + "IPHONEOS_DEPLOYMENT_TARGET": .string(Environment.deploymentTarget), + "SWIFT_VERSION": "6.0", + "ENABLE_TESTABILITY": "YES" + ] + ) +} diff --git a/Plugins/EnvPlugin/ProjectDescriptionHelpers/Target+Extension.swift b/Plugins/EnvPlugin/ProjectDescriptionHelpers/Target+Extension.swift new file mode 100644 index 0000000..19f7d80 --- /dev/null +++ b/Plugins/EnvPlugin/ProjectDescriptionHelpers/Target+Extension.swift @@ -0,0 +1,67 @@ +// +// Target+Extension.swift +// EnvPlugin +// +// Created by 최안용 on 1/13/26. +// + +import ProjectDescription + +//MARK: - App Target 생성 +public extension Target { + static func makeAppTarget( + name: String, + deploymentTargetsVersion: String = Environment.App.version, + infoPlist: [String : Plist.Value], + entitlements: String? = nil, + scripts: [TargetScript], + dependencies: [TargetDependency], + settings: Settings + ) -> Target { + let appTaget: Target = .target( + name: name, + destinations: [.iPhone], + product: .app, + bundleId: "\(Environment.bundleId).\(name)", + deploymentTargets: .iOS(deploymentTargetsVersion), + infoPlist: .extendingDefault(with: infoPlist), + sources: ["Sources/**"], + resources: ["Resources/**"], + entitlements: entitlements.map { + .file(path: .relativeToRoot($0)) + }, + scripts: scripts, + dependencies: dependencies, + settings: settings + ) + + return appTaget + } +} + +//MARK: - Framework Target 생성 +public extension Target { + static func makeFrameworkTarget( + name: String, + infoPlist: [String : Plist.Value] = [:], + sources: SourceFilesList = ["Sources/**"], + dependencies: [TargetDependency], + scripts: [TargetScript], + isStatic: Bool = false, + hasResources: Bool = true + ) -> Target { + return .target( + name: name, + destinations: .iOS, + product: isStatic ? .staticFramework : .framework, + bundleId: "\(Environment.bundleId).\(name)", + deploymentTargets: .iOS(Environment.deploymentTarget), + infoPlist: .extendingDefault(with: infoPlist), + sources: sources, + resources: hasResources ? ["Resources/**"] : nil, + scripts: scripts, + dependencies: dependencies, + settings: .frameworkSettings + ) + } +} diff --git a/Plugins/TemplatePlugin/Plugin.swift b/Plugins/TemplatePlugin/Plugin.swift new file mode 100644 index 0000000..6812e2b --- /dev/null +++ b/Plugins/TemplatePlugin/Plugin.swift @@ -0,0 +1,11 @@ +// +// Plugin.swift +// ConfigPlugin +// +// Created by 최안용 on 1/15/26. +// + +import ProjectDescription + +let templatePlugin = Plugin(name: "TemplatePlugin") + diff --git a/Plugins/TemplatePlugin/Templates/Feature/Empty.stencil b/Plugins/TemplatePlugin/Templates/Feature/Empty.stencil new file mode 100644 index 0000000..fd5453b --- /dev/null +++ b/Plugins/TemplatePlugin/Templates/Feature/Empty.stencil @@ -0,0 +1,8 @@ +// +// Empty.swift +// Templates +// +// Created by {{ author }} on {{ current_date }} +// + +import Foundation diff --git a/Plugins/TemplatePlugin/Templates/Feature/Feature.swift b/Plugins/TemplatePlugin/Templates/Feature/Feature.swift new file mode 100644 index 0000000..4510b1e --- /dev/null +++ b/Plugins/TemplatePlugin/Templates/Feature/Feature.swift @@ -0,0 +1,50 @@ +// +// Feature.swift +// ConfigPlugin +// +// Created by 최안용 on 1/15/26. +// + +import ProjectDescription + +let nameAttribute: Template.Attribute = .required("name") +let author: Template.Attribute = .required("author") +let currentDate: Template.Attribute = .required("current_date") + +let template = Template( + description: "Creates a new feature module", + attributes: [ + nameAttribute, + author, + currentDate + ], + items: ModuleTemplate.allCases.flatMap{ $0.item } +) + +enum ModuleTemplate: CaseIterable { + case main, sources // 추후 tests/demo 등 추가 + + var path: String { + switch self { + case .main: + return .basePath + case .sources: + return .basePath + "/Sources" + } + } + + var item: [Template.Item] { + switch self { + case .main: + return [.file(path: path + "/Project.swift", templatePath: "Project.stencil")] + case .sources: + return [.file(path: path + "/Empty.swift", templatePath: "Empty.stencil")] + } + } +} + +extension String { + static var basePath: String { + return "Projects/Features/\(nameAttribute)Feature" + } +} diff --git a/Plugins/TemplatePlugin/Templates/Feature/Project.stencil b/Plugins/TemplatePlugin/Templates/Feature/Project.stencil new file mode 100644 index 0000000..5caad42 --- /dev/null +++ b/Plugins/TemplatePlugin/Templates/Feature/Project.stencil @@ -0,0 +1,25 @@ +// +// Project.swift +// ProjectDescriptionHelpers +// +// Created by {{ author }} on {{ current_date }}. +// + +import ProjectDescription +import ProjectDescriptionHelpers +import DependencyPlugin + +let project = Project.makeModule( + name: "{{name}}Feature", + targets: [ + .makeFrameworkTarget( + name: "{{name}}Feature", + dependencies: [ + .Features.baseFeatureDependency + ], + scripts: [.swiftLint], + isStatic: true, + hasResources: false + ) + ] +) diff --git a/Projects/App/Project.swift b/Projects/App/Project.swift new file mode 100644 index 0000000..eb201ca --- /dev/null +++ b/Projects/App/Project.swift @@ -0,0 +1,29 @@ +// +// Project.swift +// 27th-App-Team-1-iOSManifests +// +// Created by 최안용 on 1/13/26. +// + +import ProjectDescription +import ProjectDescriptionHelpers +import EnvPlugin +import DependencyPlugin +import ConfigPlugin + + +let project = Project.makeModule( + name: "App", + targets: [ + .makeAppTarget( + name: "App", + infoPlist: Project.appInfoPlist, + scripts: [.swiftLint], + dependencies: [ + .data, + .Features.rootFeature + ], + settings: .appSettings() + ) + ] +) diff --git a/Projects/App/Resources/Assets.xcassets/AppIcon.appiconset/AppIcon~ios-marketing.png b/Projects/App/Resources/Assets.xcassets/AppIcon.appiconset/AppIcon~ios-marketing.png new file mode 100644 index 0000000..105677a Binary files /dev/null and b/Projects/App/Resources/Assets.xcassets/AppIcon.appiconset/AppIcon~ios-marketing.png differ diff --git a/Projects/App/Resources/Assets.xcassets/AppIcon.appiconset/Contents.json b/Projects/App/Resources/Assets.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 0000000..45047c5 --- /dev/null +++ b/Projects/App/Resources/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -0,0 +1,36 @@ +{ + "images" : [ + { + "filename" : "AppIcon~ios-marketing.png", + "idiom" : "universal", + "platform" : "ios", + "size" : "1024x1024" + }, + { + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "dark" + } + ], + "idiom" : "universal", + "platform" : "ios", + "size" : "1024x1024" + }, + { + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "tinted" + } + ], + "idiom" : "universal", + "platform" : "ios", + "size" : "1024x1024" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Projects/App/Resources/Assets.xcassets/Contents.json b/Projects/App/Resources/Assets.xcassets/Contents.json new file mode 100644 index 0000000..73c0059 --- /dev/null +++ b/Projects/App/Resources/Assets.xcassets/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Projects/App/Resources/LaunchScreen.storyboard b/Projects/App/Resources/LaunchScreen.storyboard new file mode 100644 index 0000000..08b3fa0 --- /dev/null +++ b/Projects/App/Resources/LaunchScreen.storyboard @@ -0,0 +1,48 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Projects/App/Sources/Application/AppDelegate.swift b/Projects/App/Sources/Application/AppDelegate.swift new file mode 100644 index 0000000..3f64da5 --- /dev/null +++ b/Projects/App/Sources/Application/AppDelegate.swift @@ -0,0 +1,40 @@ +// +// AppDelegate.swift +// App +// +// Created by 최안용 on 1/14/26. +// Copyright © 2026 NDGL-iOS. All rights reserved. +// + +import UIKit + +@main +class AppDelegate: UIResponder, UIApplicationDelegate { + func application( + _ application: UIApplication, + didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? + ) -> Bool { + return true + } + + // MARK: UISceneSession Lifecycle + + func application( + _ application: UIApplication, + configurationForConnecting connectingSceneSession: UISceneSession, + options: UIScene.ConnectionOptions + ) -> UISceneConfiguration { + return UISceneConfiguration( + name: "Default Configuration", + sessionRole: connectingSceneSession.role + ) + } + + func application( + _ application: UIApplication, + didDiscardSceneSessions sceneSessions: Set + ) { + + } +} + diff --git a/Projects/App/Sources/Application/SceneDelegate.swift b/Projects/App/Sources/Application/SceneDelegate.swift new file mode 100644 index 0000000..7d847a1 --- /dev/null +++ b/Projects/App/Sources/Application/SceneDelegate.swift @@ -0,0 +1,49 @@ +// +// SceneDelegate.swift +// App +// +// Created by 최안용 on 1/14/26. +// Copyright © 2026 NDGL-iOS. All rights reserved. +// + +import UIKit + +import Core +import RootFeature +import BaseFeatureDependency + +class SceneDelegate: UIResponder, UIWindowSceneDelegate { + var window: UIWindow? + + func scene( + _ scene: UIScene, + willConnectTo session: UISceneSession, + options connectionOptions: UIScene.ConnectionOptions + ) { + guard let windowScene = (scene as? UIWindowScene) else { return } + window = UIWindow(windowScene: windowScene) + window?.rootViewController = RootVC() + window?.makeKeyAndVisible() + } + + func sceneDidDisconnect(_ scene: UIScene) { + + } + + func sceneDidBecomeActive(_ scene: UIScene) { + + } + + func sceneWillResignActive(_ scene: UIScene) { + + } + + func sceneWillEnterForeground(_ scene: UIScene) { + + } + + func sceneDidEnterBackground(_ scene: UIScene) { + + } +} + diff --git a/Projects/Core/Project.swift b/Projects/Core/Project.swift new file mode 100644 index 0000000..12cd60a --- /dev/null +++ b/Projects/Core/Project.swift @@ -0,0 +1,24 @@ +// +// Project.swift +// 27th-App-Team-1-iOSManifests +// +// Created by 최안용 on 1/13/26. +// + +import ProjectDescription +import ProjectDescriptionHelpers +import DependencyPlugin + +let project = Project.makeModule( + name: "Core", + targets: [ + .makeFrameworkTarget( + name: "Core", + dependencies: [ + .Modules.thirdPartyLibs + ], + scripts: [.swiftLint], + hasResources: false + ) + ] +) diff --git a/Projects/Core/Sources/Extensions/UIKit+/Empty.swift b/Projects/Core/Sources/Extensions/UIKit+/Empty.swift new file mode 100644 index 0000000..3423f2b --- /dev/null +++ b/Projects/Core/Sources/Extensions/UIKit+/Empty.swift @@ -0,0 +1,8 @@ +// +// Empty.swift +// 27th-App-Team-1-iOSManifests +// +// Created by 최안용 on 1/13/26. +// + +import UIKit diff --git a/Projects/Data/Project.swift b/Projects/Data/Project.swift new file mode 100644 index 0000000..e59eccf --- /dev/null +++ b/Projects/Data/Project.swift @@ -0,0 +1,27 @@ +// +// Project.swift +// 27th-App-Team-1-iOSManifests +// +// Created by 최안용 on 1/13/26. +// + +import ProjectDescription +import ProjectDescriptionHelpers +import DependencyPlugin + +let project = Project.makeModule( + name: "Data", + targets: [ + .makeFrameworkTarget( + name: "Data", + dependencies: [ + .domain, + .Modules.networks + ], + scripts: [.swiftLint], + isStatic: true, + hasResources: false + ) + ] +) + diff --git a/Projects/Data/Sources/Repository/RepoEmpty.swift b/Projects/Data/Sources/Repository/RepoEmpty.swift new file mode 100644 index 0000000..17afcba --- /dev/null +++ b/Projects/Data/Sources/Repository/RepoEmpty.swift @@ -0,0 +1,8 @@ +// +// RepoEmpty.swift +// 27th-App-Team-1-iOSManifests +// +// Created by 최안용 on 1/13/26. +// + +import Foundation diff --git a/Projects/Data/Sources/Transform/TransEmpty.swift b/Projects/Data/Sources/Transform/TransEmpty.swift new file mode 100644 index 0000000..f83abee --- /dev/null +++ b/Projects/Data/Sources/Transform/TransEmpty.swift @@ -0,0 +1,8 @@ +// +// TransEmpty.swift +// 27th-App-Team-1-iOSManifests +// +// Created by 최안용 on 1/13/26. +// + +import Foundation diff --git a/Projects/Domain/Project.swift b/Projects/Domain/Project.swift new file mode 100644 index 0000000..e6bd7da --- /dev/null +++ b/Projects/Domain/Project.swift @@ -0,0 +1,24 @@ +// +// Project.swift +// 27th-App-Team-1-iOSManifests +// +// Created by 최안용 on 1/13/26. +// + +import ProjectDescription +import ProjectDescriptionHelpers +import DependencyPlugin + +let project = Project.makeModule( + name: "Domain", + targets: [ + .makeFrameworkTarget( + name: "Domain", + dependencies: [ + .core + ], + scripts: [.swiftLint], + hasResources: false + ) + ] +) diff --git a/Projects/Domain/Sources/Interface/InterfaceEmpty.swift b/Projects/Domain/Sources/Interface/InterfaceEmpty.swift new file mode 100644 index 0000000..c2e3ea8 --- /dev/null +++ b/Projects/Domain/Sources/Interface/InterfaceEmpty.swift @@ -0,0 +1,8 @@ +// +// InterfaceEmpty.swift +// 27th-App-Team-1-iOSManifests +// +// Created by 최안용 on 1/13/26. +// + +import Foundation diff --git a/Projects/Domain/Sources/Model/ModelEmpty.swift b/Projects/Domain/Sources/Model/ModelEmpty.swift new file mode 100644 index 0000000..a2f2190 --- /dev/null +++ b/Projects/Domain/Sources/Model/ModelEmpty.swift @@ -0,0 +1,8 @@ +// +// ModelEmpty.swift +// 27th-App-Team-1-iOSManifests +// +// Created by 최안용 on 1/13/26. +// + +import Foundation diff --git a/Projects/Domain/Sources/UseCase/UsecaseEmpty.swift b/Projects/Domain/Sources/UseCase/UsecaseEmpty.swift new file mode 100644 index 0000000..42a520b --- /dev/null +++ b/Projects/Domain/Sources/UseCase/UsecaseEmpty.swift @@ -0,0 +1,8 @@ +// +// UsecaseEmpty.swift +// 27th-App-Team-1-iOSManifests +// +// Created by 최안용 on 1/13/26. +// + +import Foundation diff --git a/Projects/Features/BaseFeatureDependency/Project.swift b/Projects/Features/BaseFeatureDependency/Project.swift new file mode 100644 index 0000000..584bfc9 --- /dev/null +++ b/Projects/Features/BaseFeatureDependency/Project.swift @@ -0,0 +1,26 @@ +// +// Project.swift +// 27th-App-Team-1-iOSManifests +// +// Created by 최안용 on 1/15/26. +// + +import ProjectDescription +import ProjectDescriptionHelpers +import DependencyPlugin + +let project = Project.makeModule( + name: "BaseFeatureDependency", + targets: [ + .makeFrameworkTarget( + name: "BaseFeatureDependency", + dependencies: [ + .domain, + .Modules.dsKit + ], + scripts: [.swiftLint], + hasResources: false + ) + ] +) + diff --git a/Projects/Features/BaseFeatureDependency/Sources/BaseEmpty.swift b/Projects/Features/BaseFeatureDependency/Sources/BaseEmpty.swift new file mode 100644 index 0000000..6d09b52 --- /dev/null +++ b/Projects/Features/BaseFeatureDependency/Sources/BaseEmpty.swift @@ -0,0 +1,8 @@ +// +// BaseEmpty.swift +// 27th-App-Team-1-iOSManifests +// +// Created by 최안용 on 1/15/26. +// + +import Foundation diff --git a/Projects/Features/HomeFeature/Project.swift b/Projects/Features/HomeFeature/Project.swift new file mode 100644 index 0000000..4c3baad --- /dev/null +++ b/Projects/Features/HomeFeature/Project.swift @@ -0,0 +1,26 @@ +// +// Project.swift +// 27th-App-Team-1-iOSManifests +// +// Created by 최안용 on 1/14/26. +// + +import ProjectDescription +import ProjectDescriptionHelpers +import DependencyPlugin + +let project = Project.makeModule( + name: "HomeFeature", + targets: [ + .makeFrameworkTarget( + name: "HomeFeature", + dependencies: [ + .Features.baseFeatureDependency + ], + scripts: [.swiftLint], + isStatic: true, + hasResources: false + ) + ] +) + diff --git a/Projects/Features/HomeFeature/Sources/HomeVC.swift b/Projects/Features/HomeFeature/Sources/HomeVC.swift new file mode 100644 index 0000000..c2ebe59 --- /dev/null +++ b/Projects/Features/HomeFeature/Sources/HomeVC.swift @@ -0,0 +1,37 @@ +// +// HomeVC.swift +// HomeFeature +// +// Created by 최안용 on 1/15/26. +// Copyright © 2026 NDGL-iOS. All rights reserved. +// + +import UIKit + +import Core +import Domain +import DSKit + +import BaseFeatureDependency + +// Coordinator 도입시 public 제거 후 Coordinator 클래스를 통해 접근 +public final class HomeVC: UIViewController { + + public override func viewDidLoad() { + super.viewDidLoad() + + view.backgroundColor = .red + } + + + /* + // MARK: - Navigation + + // In a storyboard-based application, you will often want to do a little preparation before navigation + override func prepare(for segue: UIStoryboardSegue, sender: Any?) { + // Get the new view controller using segue.destination. + // Pass the selected object to the new view controller. + } + */ + +} diff --git a/Projects/Features/RootFeature/Project.swift b/Projects/Features/RootFeature/Project.swift new file mode 100644 index 0000000..cc68da1 --- /dev/null +++ b/Projects/Features/RootFeature/Project.swift @@ -0,0 +1,26 @@ +// +// Project.swift +// 27th-App-Team-1-iOSManifests +// +// Created by 최안용 on 1/13/26. +// + +import ProjectDescription +import ProjectDescriptionHelpers +import DependencyPlugin + +let project = Project.makeModule( + name: "RootFeature", + targets: [ + .makeFrameworkTarget( + name: "RootFeature", + dependencies: [ + .Features.Home.feature + ], + scripts: [.swiftLint], + isStatic: true, + hasResources: false + ) + ] +) + diff --git a/Projects/Features/RootFeature/Sources/RootVC.swift b/Projects/Features/RootFeature/Sources/RootVC.swift new file mode 100644 index 0000000..6e2290b --- /dev/null +++ b/Projects/Features/RootFeature/Sources/RootVC.swift @@ -0,0 +1,33 @@ +// +// RootVC.swift +// RootFeature +// +// Created by 최안용 on 1/15/26. +// Copyright © 2026 NDGL-iOS. All rights reserved. +// + +import UIKit + +import Core +import BaseFeatureDependency +import HomeFeature + + +public final class RootVC: UIViewController { + + public override func viewDidLoad() { + super.viewDidLoad() + + let homeNavigationController = UINavigationController() + homeNavigationController.viewControllers = [HomeVC()] + + addChild(homeNavigationController) + + view.addSubview(homeNavigationController.view) + + homeNavigationController.view.frame = view.bounds + homeNavigationController.view.autoresizingMask = [.flexibleWidth, .flexibleHeight] + + homeNavigationController.didMove(toParent: self) + } +} diff --git a/Projects/Modules/DSKit/Project.swift b/Projects/Modules/DSKit/Project.swift new file mode 100644 index 0000000..f993ce1 --- /dev/null +++ b/Projects/Modules/DSKit/Project.swift @@ -0,0 +1,24 @@ +// +// Project.swift +// 27th-App-Team-1-iOSManifests +// +// Created by 최안용 on 1/13/26. +// + +import ProjectDescription +import ProjectDescriptionHelpers +import DependencyPlugin + +let project = Project.makeModule( + name: "DSKit", + targets: [ + .makeFrameworkTarget( + name: "DSKit", + dependencies: [ + .core + ], + scripts: [.swiftLint], + hasResources: false + ) + ] +) diff --git a/Projects/Modules/DSKit/Sources/Component/DSCEmpty.swift b/Projects/Modules/DSKit/Sources/Component/DSCEmpty.swift new file mode 100644 index 0000000..de7d7d8 --- /dev/null +++ b/Projects/Modules/DSKit/Sources/Component/DSCEmpty.swift @@ -0,0 +1,8 @@ +// +// DSCEmpty.swift +// 27th-App-Team-1-iOSManifests +// +// Created by 최안용 on 1/13/26. +// + +import Foundation diff --git a/Projects/Modules/DSKit/Sources/DSEmpty.swift b/Projects/Modules/DSKit/Sources/DSEmpty.swift new file mode 100644 index 0000000..a9b76b7 --- /dev/null +++ b/Projects/Modules/DSKit/Sources/DSEmpty.swift @@ -0,0 +1,8 @@ +// +// DSEmpty.swift +// 27th-App-Team-1-iOSManifests +// +// Created by 최안용 on 1/13/26. +// + +import Foundation diff --git a/Projects/Modules/Networks/Project.swift b/Projects/Modules/Networks/Project.swift new file mode 100644 index 0000000..2414eb4 --- /dev/null +++ b/Projects/Modules/Networks/Project.swift @@ -0,0 +1,25 @@ +// +// Project.swift +// 27th-App-Team-1-iOSManifests +// +// Created by 최안용 on 1/13/26. +// + +import ProjectDescription +import ProjectDescriptionHelpers +import DependencyPlugin + +let project = Project.makeModule( + name: "Networks", + targets: [ + .makeFrameworkTarget( + name: "Networks", + dependencies: [ + .core + ], + scripts: [.swiftLint], + isStatic: true, + hasResources: false + ) + ] +) diff --git a/Projects/Modules/Networks/Sources/API/APIEmpty.swift b/Projects/Modules/Networks/Sources/API/APIEmpty.swift new file mode 100644 index 0000000..cc161f1 --- /dev/null +++ b/Projects/Modules/Networks/Sources/API/APIEmpty.swift @@ -0,0 +1,8 @@ +// +// APIEmpty.swift +// 27th-App-Team-1-iOSManifests +// +// Created by 최안용 on 1/13/26. +// + +import Foundation diff --git a/Projects/Modules/Networks/Sources/Base/NBaseEmpty.swift b/Projects/Modules/Networks/Sources/Base/NBaseEmpty.swift new file mode 100644 index 0000000..c0b8f52 --- /dev/null +++ b/Projects/Modules/Networks/Sources/Base/NBaseEmpty.swift @@ -0,0 +1,8 @@ +// +// NBaseEmpty.swift +// 27th-App-Team-1-iOSManifests +// +// Created by 최안용 on 1/13/26. +// + +import Foundation diff --git a/Projects/Modules/Networks/Sources/Entity/EntityEmpty.swift b/Projects/Modules/Networks/Sources/Entity/EntityEmpty.swift new file mode 100644 index 0000000..be4230d --- /dev/null +++ b/Projects/Modules/Networks/Sources/Entity/EntityEmpty.swift @@ -0,0 +1,8 @@ +// +// EntityEmpty.swift +// 27th-App-Team-1-iOSManifests +// +// Created by 최안용 on 1/13/26. +// + +import Foundation diff --git a/Projects/Modules/Networks/Sources/Service/ServiceEmpty.swift b/Projects/Modules/Networks/Sources/Service/ServiceEmpty.swift new file mode 100644 index 0000000..a45ef69 --- /dev/null +++ b/Projects/Modules/Networks/Sources/Service/ServiceEmpty.swift @@ -0,0 +1,8 @@ +// +// ServiceEmpty.swift +// 27th-App-Team-1-iOSManifests +// +// Created by 최안용 on 1/13/26. +// + +import Foundation diff --git a/Projects/Modules/ThirdPartyLibs/Project.swift b/Projects/Modules/ThirdPartyLibs/Project.swift new file mode 100644 index 0000000..411b945 --- /dev/null +++ b/Projects/Modules/ThirdPartyLibs/Project.swift @@ -0,0 +1,29 @@ +// +// Project.swift +// 27th-App-Team-1-iOSManifests +// +// Created by 최안용 on 1/13/26. +// + +import ProjectDescription +import ProjectDescriptionHelpers +import DependencyPlugin + +let project = Project.makeModule( + name: "ThirdPartyLibs", + targets: [ + .makeFrameworkTarget( + name: "ThirdPartyLibs", + dependencies: [ + .SPM.Kingfisher, + .SPM.Moya, + .SPM.RIBs, + .SPM.RxSwift, + .SPM.SnapKit, + .SPM.Then + ], + scripts: [], + hasResources: false + ) + ] +) diff --git a/Projects/Modules/ThirdPartyLibs/Sources/Empty.swift b/Projects/Modules/ThirdPartyLibs/Sources/Empty.swift new file mode 100644 index 0000000..7063f0b --- /dev/null +++ b/Projects/Modules/ThirdPartyLibs/Sources/Empty.swift @@ -0,0 +1,8 @@ +// +// Empty.swift +// 27th-App-Team-1-iOSManifests +// +// Created by 최안용 on 1/13/26. +// + +import Foundation diff --git a/Scripts/GenerateModule.swift b/Scripts/GenerateModule.swift new file mode 100644 index 0000000..3ab4e6b --- /dev/null +++ b/Scripts/GenerateModule.swift @@ -0,0 +1,60 @@ +#!/user/bin/swift +import Foundation + +// 1. 유틸리티 함수: 날짜 가져오기 +func getCurrentDate() -> String { + let formatter = DateFormatter() + formatter.dateFormat = "yyyy/MM/dd" + return formatter.string(from: Date()) +} + +// 2. 입력 받기 +print("🚀 새 Feature 모듈 생성을 시작합니다.") + +print("입력: 모듈 이름 (예: Home, MyPage)", terminator: " : ") +guard let name = readLine(), !name.isEmpty else { + print("❌ 모듈 이름은 필수입니다."); exit(1) +} + +print("입력: 작성자 이름", terminator: " : ") +guard let author = readLine(), !author.isEmpty else { + print("❌ 작성자 이름은 필수입니다."); exit(1) +} + +let currentDate = getCurrentDate() + +// 3. 실행할 tuist scaffold 명령어 구성 +// 템플릿 이름이 'feature'라고 가정합니다. (Template.swift가 있는 폴더명) +let command = [ + "tuist", "scaffold", "Feature", + "--name", name, + "--author", author, + "--current_date", currentDate +] + +print("\n-------------------------------------------") +print("생성 정보 확인") +print("- 모듈명: \(name)Feature") +print("- 작성자: \(author)") +print("- 날짜: \(currentDate)") +print("-------------------------------------------\n") + +// 4. 프로세스 실행 +let process = Process() +process.executableURL = URL(fileURLWithPath: "/usr/bin/env") +process.currentDirectoryURL = URL(fileURLWithPath: FileManager.default.currentDirectoryPath) +process.arguments = command + +do { + try process.run() + process.waitUntilExit() + + if process.terminationStatus == 0 { + print("\n✅ \(name)Feature 모듈이 성공적으로 생성되었습니다!") + print("💡 'tuist generate'를 실행하여 프로젝트를 갱신하세요.") + } else { + print("\n❌ 모듈 생성 중 오류가 발생했습니다. (Status: \(process.terminationStatus))") + } +} catch { + print("❌ 명령어 실행 실패: \(error)") +} diff --git a/Scripts/Onboarding.sh b/Scripts/Onboarding.sh new file mode 100755 index 0000000..672fc56 --- /dev/null +++ b/Scripts/Onboarding.sh @@ -0,0 +1,137 @@ +#!/bin/bash + +# 에러 발생 시 즉시 중단 +set -e + +echo -e "🚀 Onboarding Start\n" + +# 0. 실행 경로 고정 (프로젝트 루트) +cd "$(dirname "$0")/.." + +# --------------------------------------------------------- +# 1. PrivateFile 레포지토리를 통한 보안 파일 자동 세팅 +# --------------------------------------------------------- + +PRIVATE_REPO_URL="https://github.com/ChoiAnYong/fastlane-match.git" +TEMP_DIR="temp_private_configs" + +echo "🔐 보안 설정 파일을 가져오는 중 (PrivateFile 레포지토리)..." + +# 기존에 폴더가 있다면 삭제 후 새로 클론 +rm -rf "$TEMP_DIR" +git clone "$PRIVATE_REPO_URL" "$TEMP_DIR" + +# 레포지토리 내 PrivateFile 폴더가 있는지 확인 후 경로 설정 +SOURCE_PATH="$TEMP_DIR/PrivateFile" +if [ ! -d "$SOURCE_PATH" ]; then + SOURCE_PATH="$TEMP_DIR" +fi + +echo "📁 설정 파일 배치 중..." +mkdir -p xcconfigs +mkdir -p fastlane + +cp "$SOURCE_PATH/Debug.xcconfig" xcconfigs/ 2>/dev/null || echo "⚠️ Debug.xcconfig missing" +cp "$SOURCE_PATH/Release.xcconfig" xcconfigs/ 2>/dev/null || echo "⚠️ Release.xcconfig missing" +cp "$SOURCE_PATH/.env.default" fastlane/ 2>/dev/null || echo "⚠️ .env.default missing" +# entitlements 파일이 있다면 추가 (필요시 주석 해제) +# cp "$SOURCE_PATH/NDGL.entitlements" xcconfigs/ 2>/dev/null || echo "⚠️ NDGL.entitlements missing" + +# 임시 폴더 삭제 +rm -rf "$TEMP_DIR" + +# --------------------------------------------------------- +# 2. 필수 설정 파일 검증 +# --------------------------------------------------------- +REQUIRED_FILES=( + "fastlane/.env.default" + "xcconfigs/Debug.xcconfig" + "xcconfigs/Release.xcconfig" +) + +echo "🔍 가져온 파일 검증 중..." +MISSING_FILES=() + +for FILE in "${REQUIRED_FILES[@]}"; do + if [ ! -f "$FILE" ]; then + MISSING_FILES+=("$FILE") + fi +done + +if [ ${#MISSING_FILES[@]} -ne 0 ]; then + echo -e "\n❌ [Error] 아래 필수 파일이 누락되었습니다:" + for MISSING in "${MISSING_FILES[@]}"; do + echo " - $MISSING" + done + exit 1 +fi + +echo -e "✅ 모든 설정 파일이 정상적으로 배치되었습니다.\n" + +# --------------------------------------------------------- +# 3. 환경 변수 로드 +# --------------------------------------------------------- +export $(grep -v '^#' fastlane/.env.default | xargs) + +# --------------------------------------------------------- +# 4. mise 설치 및 활성화 (Bash 버전) +# --------------------------------------------------------- +if ! command -v mise &> /dev/null; then + echo -e "[mise] Installing mise..." + brew install mise +fi +# 중요: zsh가 아닌 bash로 활성화 +eval "$(mise activate bash)" + +# 5. 도구 설치 +if [ -f .mise.toml ]; then + echo -e "[mise] Installing tools from .mise.toml..." + mise install +fi + +# --------------------------------------------------------- +# 6. rbenv 및 Ruby 설정 (Bash 버전) +# --------------------------------------------------------- +if ! command -v rbenv &> /dev/null; then + echo -e "[rbenv] Installing rbenv..." + brew install rbenv +fi +export PATH="$HOME/.rbenv/bin:$PATH" +# 중요: bash로 초기화 +eval "$(rbenv init - bash)" + +RUBY_VERSION=$(cat .ruby-version 2>/dev/null || echo "3.2.2") +if ! rbenv versions | grep -q "$RUBY_VERSION"; then + echo -e "[rbenv] Installing Ruby $RUBY_VERSION..." + rbenv install "$RUBY_VERSION" +fi +rbenv local "$RUBY_VERSION" +rbenv rehash + +# --------------------------------------------------------- +# 7. Bundler 및 의존성 설치 +# --------------------------------------------------------- +echo -e "[Bundler] Installing gems..." +gem install bundler --no-document +bundle install + +# --------------------------------------------------------- +# 8. Tuist 설정 +# --------------------------------------------------------- +echo -e "\n📦 Tuist Setting..." +if [ -f Makefile ]; then + # Makefile의 generate 타겟 실행 (tuist install/generate 포함 권장) + make generate +else + tuist install + tuist generate +fi + +# --------------------------------------------------------- +# 9. Fastlane Match +# --------------------------------------------------------- +echo -e "\n🔐 Installing Certificates..." +bundle exec fastlane match development --readonly +bundle exec fastlane match appstore --readonly + +echo -e "\n✅ 온보딩 완료! 이제 Xcode에서 프로젝트를 빌드할 수 있습니다." diff --git a/Tuist.swift b/Tuist.swift new file mode 100644 index 0000000..eefebbb --- /dev/null +++ b/Tuist.swift @@ -0,0 +1,11 @@ +import ProjectDescription + +let tuist = Tuist(project: .tuist( + plugins: [ + .local(path: .relativeToRoot("Plugins/DependencyPlugin")), + .local(path: .relativeToRoot("Plugins/ConfigPlugin")), + .local(path: .relativeToRoot("Plugins/EnvPlugin")), + .local(path: .relativeToRoot("Plugins/TemplatePlugin")) + ], + generationOptions: .options() +)) diff --git a/Tuist/Package.resolved b/Tuist/Package.resolved new file mode 100644 index 0000000..d5a5d8f --- /dev/null +++ b/Tuist/Package.resolved @@ -0,0 +1,78 @@ +{ + "originHash" : "d7518f58a4cae139e84fb7d8fe5dd3abeb7b75bb9b7260d32354d7473b9a6b84", + "pins" : [ + { + "identity" : "alamofire", + "kind" : "remoteSourceControl", + "location" : "https://github.com/Alamofire/Alamofire.git", + "state" : { + "revision" : "7be73f6c2b5cd90e40798b06ebd5da8f9f79cf88", + "version" : "5.11.0" + } + }, + { + "identity" : "kingfisher", + "kind" : "remoteSourceControl", + "location" : "https://github.com/onevcat/Kingfisher.git", + "state" : { + "revision" : "d30a5fad881137e2267f96a8e3fc35c58999bb94", + "version" : "8.6.2" + } + }, + { + "identity" : "moya", + "kind" : "remoteSourceControl", + "location" : "https://github.com/Moya/Moya.git", + "state" : { + "revision" : "c263811c1f3dbf002be9bd83107f7cdc38992b26", + "version" : "15.0.3" + } + }, + { + "identity" : "reactiveswift", + "kind" : "remoteSourceControl", + "location" : "https://github.com/ReactiveCocoa/ReactiveSwift.git", + "state" : { + "revision" : "c43bae3dac73fdd3cb906bd5a1914686ca71ed3c", + "version" : "6.7.0" + } + }, + { + "identity" : "ribs-ios", + "kind" : "remoteSourceControl", + "location" : "https://github.com/uber/RIBs-iOS.git", + "state" : { + "revision" : "53f7cb391d48c385730df6ce1fb95029bca9d25a", + "version" : "1.0.0" + } + }, + { + "identity" : "rxswift", + "kind" : "remoteSourceControl", + "location" : "https://github.com/ReactiveX/RxSwift.git", + "state" : { + "revision" : "5004a18539bd68905c5939aa893075f578f4f03d", + "version" : "6.9.1" + } + }, + { + "identity" : "snapkit", + "kind" : "remoteSourceControl", + "location" : "https://github.com/SnapKit/SnapKit.git", + "state" : { + "revision" : "2842e6e84e82eb9a8dac0100ca90d9444b0307f4", + "version" : "5.7.1" + } + }, + { + "identity" : "then", + "kind" : "remoteSourceControl", + "location" : "https://github.com/devxoul/Then", + "state" : { + "revision" : "e421a7b3440a271834337694e6050133a3958bc7", + "version" : "2.7.0" + } + } + ], + "version" : 3 +} diff --git a/Tuist/Package.swift b/Tuist/Package.swift new file mode 100644 index 0000000..69ab405 --- /dev/null +++ b/Tuist/Package.swift @@ -0,0 +1,26 @@ +// swift-tools-version: 6.0 +import PackageDescription + +#if TUIST + import struct ProjectDescription.PackageSettings + + let packageSettings = PackageSettings( + // Customize the product types for specific package product + // Default is .staticFramework + // productTypes: ["Alamofire": .framework,] + productTypes: [:] + ) +#endif + +let package = Package( + name: "NDGL-iOS", + dependencies: [ + .package(url: "https://github.com/Moya/Moya.git", from: "15.0.0"), + .package(url: "https://github.com/SnapKit/SnapKit.git", from: "5.0.1"), + .package(url: "https://github.com/devxoul/Then", from: "2.0.0"), + .package(url: "https://github.com/ReactiveX/RxSwift.git", from: "6.0.0"), + .package(url: "https://github.com/onevcat/Kingfisher.git", from: "8.0.0"), + .package(url: "https://github.com/uber/RIBs-iOS.git", from: "1.0.0") + ] +) + diff --git a/Tuist/ProjectDescriptionHelpers/Project+MakeModule.swift b/Tuist/ProjectDescriptionHelpers/Project+MakeModule.swift new file mode 100644 index 0000000..4221ef3 --- /dev/null +++ b/Tuist/ProjectDescriptionHelpers/Project+MakeModule.swift @@ -0,0 +1,49 @@ +// +// Project+MakeModule.swift +// 27th-App-Team-1-iOSManifests +// +// Created by 최안용 on 1/13/26. +// + +import ProjectDescription +import EnvPlugin +import ConfigPlugin + +extension Project { + public static func makeModule( + name: String, + organizationName: String = Environment.organizationName, + packages: [Package] = [], + settings: Settings? = nil, + targets: [Target] = [], + schemes: [Scheme]? = nil, + fileHeaderTemplate: FileHeaderTemplate? = nil, + additionalFiles: [FileElement] = [], + resourceSynthesizers: [ResourceSynthesizer] = .default + ) -> Project { + if let schemes = schemes { + return Project( + name: name, + organizationName: organizationName, + packages: packages, + settings: .settings(configurations: XCConfig.prod), + targets: targets, + schemes: schemes, + fileHeaderTemplate: fileHeaderTemplate, + additionalFiles: additionalFiles, + resourceSynthesizers: resourceSynthesizers + ) + } else { + return Project( + name: name, + organizationName: organizationName, + packages: packages, + settings: .settings(configurations: XCConfig.prod), + targets: targets, + fileHeaderTemplate: fileHeaderTemplate, + additionalFiles: additionalFiles, + resourceSynthesizers: resourceSynthesizers + ) + } + } +} diff --git a/Tuist/ProjectDescriptionHelpers/TargetScript+Extension.swift b/Tuist/ProjectDescriptionHelpers/TargetScript+Extension.swift new file mode 100644 index 0000000..bc0725b --- /dev/null +++ b/Tuist/ProjectDescriptionHelpers/TargetScript+Extension.swift @@ -0,0 +1,39 @@ +// +// TargetScript+Extension.swift +// ProjectDescriptionHelpers +// +// Created by 최안용 on 1/15/26. +// + +import ProjectDescription + +public extension TargetScript { + static let swiftLint = TargetScript.pre( + script: """ + export PATH="/opt/homebrew/bin:/usr/local/bin:$PATH" + + SWIFTLINT="$(command -v swiftlint || true)" + if [ -z "$SWIFTLINT" ]; then + echo "warning: SwiftLint not installed (skipping)" + exit 0 + fi + + ROOT="$SRCROOT" + while [ "$ROOT" != "/" ] && [ ! -f "$ROOT/.swiftlint.yml" ]; do + ROOT="$(dirname "$ROOT")" + done + + if [ ! -f "$ROOT/.swiftlint.yml" ]; then + echo "warning: .swiftlint.yml not found (skipping)" + exit 0 + fi + + echo "SwiftLint: $SWIFTLINT" + echo "Config: $ROOT/.swiftlint.yml" + + "$SWIFTLINT" lint --config "$ROOT/.swiftlint.yml" + """, + name: "SwiftLint", + basedOnDependencyAnalysis: false + ) +} diff --git a/Workspace.swift b/Workspace.swift new file mode 100644 index 0000000..8f9c68f --- /dev/null +++ b/Workspace.swift @@ -0,0 +1,8 @@ +import ProjectDescription + +let workspace = Workspace( + name: "NDGL-iOS", + projects: [ + "Projects/**" + ] +) diff --git a/fastlane/Appfile b/fastlane/Appfile new file mode 100644 index 0000000..42682db --- /dev/null +++ b/fastlane/Appfile @@ -0,0 +1,8 @@ +app_identifier(ENV['APP_IDENTIFIER']) # The bundle identifier of your app +apple_id(ENV['APPLE_ID']) # Your Apple Developer Portal username + +itc_team_id(ENV['FASTLANE_ITC_TEAM_ID']) # App Store Connect Team ID +team_id(ENV['TEAM_ID']) # Developer Portal 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..ee0346e --- /dev/null +++ b/fastlane/Fastfile @@ -0,0 +1,92 @@ +# 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 + +default_platform(:ios) + +platform :ios do + + # --------------------------------------------------------- + # 1. CI 전용 레인 (PR 시 빌드 확인용) + # --------------------------------------------------------- + desc "Check Build (CI)" + lane :ci_check do + begin + xcodebuild( + workspace: "NDGL-iOS.xcworkspace", + scheme: "App", + configuration: "Debug", + sdk: "iphonesimulator", + xcargs: "CODE_SIGNING_ALLOWED=NO" + ) + # 성공 시 CI용 메시지 전송 + send_discord_message(message: "CI 빌드 체크 성공!", success: true, type: "CI") + rescue => exception + # 실패 시 에러 내용과 함께 알림 + send_discord_message(message: "CI 빌드 실패: #{exception}", success: false, type: "CI") + raise exception + end + end + + # --------------------------------------------------------- + # 2. 배포 전용 레인 (App Store / TestFlight 업로드) + # --------------------------------------------------------- + desc "Push a new release build to the App Store" + lane :release do + begin + # match(type: "appstore", readonly: true) # CI 환경이라면 추가 권장 + build_app(scheme: "NDGL-iOS-Workspace") + upload_to_app_store(skip_metadata: true, skip_screenshots: true) + + send_discord_message(message: "TestFlight 업로드 완료! 테스터 알림이 전송되었습니다. 🚀", success: true, type: "배포") + rescue => exception + send_discord_message(message: "배포 실패 에러: #{exception}", success: false, type: "배포") + raise exception + end + end + + # --------------------------------------------------------- + # 3. 기기 관리 (로컬용) + # --------------------------------------------------------- + desc "Register Devices" + lane :register_new_device do |options| + device_name = prompt(text: "Enter the device name: ") + device_udid = prompt(text: "Enter the device UDID: ") + register_devices(devices: { device_name => device_udid }) + match(type: "development", force_for_new_devices: true) + end +end + +# --------------------------------------------------------- +# 🔔 Discord 메시지 전송 함수 (공통) +# --------------------------------------------------------- +# --------------------------------------------------------- +# 🔔 Discord 메시지 전송 함수 (공통) +# --------------------------------------------------------- +def send_discord_message(message:, success:, type: "CI") + project_path = "./Projects/App/App.xcodeproj" + + version = get_version_number(xcodeproj: project_path) + build_number = get_build_number(xcodeproj: project_path) + + emoji = success ? "✅" : "🚨" + title_text = success ? "#{type} 성공" : "#{type} 실패" + + discord_notifier( + webhook_url: ENV["DISCORD_URL"], + title: "#{emoji} #{title_text} — v#{version} (#{build_number})", + description: message, + success: success + ) +end \ No newline at end of file diff --git a/fastlane/Matchfile b/fastlane/Matchfile new file mode 100644 index 0000000..604f000 --- /dev/null +++ b/fastlane/Matchfile @@ -0,0 +1,13 @@ +git_url(ENV['REPOSITORY']) + +type "development" # The default type, can be: appstore, adhoc or development + +app_identifier(ENV['APP_IDENTIFIER']) +team_id(ENV['TEAM_ID']) +git_branch(ENV['BRANCH']) + +# app_identifier ["tools.fastlane.app", "tools.fastlane.app2"] +# username "user@fastlane.tools" # Your Apple Developer Portal username + +# For all available options run `match --help` +# Remove the # in the beginning of the line to enable the other options diff --git a/fastlane/Pluginfile b/fastlane/Pluginfile new file mode 100644 index 0000000..7ef2b02 --- /dev/null +++ b/fastlane/Pluginfile @@ -0,0 +1,5 @@ +# Autogenerated by fastlane +# +# Ensure this file is checked in to source control! + +gem 'fastlane-plugin-discord_notifier' diff --git a/fastlane/README.md b/fastlane/README.md new file mode 100644 index 0000000..0b13b9b --- /dev/null +++ b/fastlane/README.md @@ -0,0 +1,27 @@ +fastlane documentation +================ +# Installation +``` +sudo gem install fastlane +``` +# Available Actions +### register_new_device +``` +fastlane register_new_device +``` +Register Devices + +---- + +## iOS +### ios release +``` +fastlane ios release +``` +Push a new release build to the App Store + +---- + +This README.md is auto-generated and will be re-generated every time to run [fastlane](https://fastlane.tools). +More information about fastlane can be found on [https://fastlane.tools](https://fastlane.tools). +The documentation of fastlane can be found on [GitHub](https://github.com/fastlane/fastlane/tree/master/fastlane). \ No newline at end of file diff --git a/mise.toml b/mise.toml new file mode 100644 index 0000000..eb98974 --- /dev/null +++ b/mise.toml @@ -0,0 +1,2 @@ +[tools] +tuist = "4.118.1" diff --git a/xcconfigs/NDGL.entitlements b/xcconfigs/NDGL.entitlements new file mode 100644 index 0000000..98331b3 --- /dev/null +++ b/xcconfigs/NDGL.entitlements @@ -0,0 +1 @@ +dk