project-auto-wizard 0.1.25 → 0.1.31

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -17,7 +17,7 @@ npx project-auto-wizard
17
17
  [![node](https://img.shields.io/badge/node-%3E%3D20.12-brightgreen)](package.json)
18
18
 
19
19
  <!-- AUTO-VERSION-SECTION: DO NOT EDIT MANUALLY -->
20
- ## 최신 버전 : v0.1.24 (2026-08-06)
20
+ ## 최신 버전 : v0.1.30 (2026-08-08)
21
21
 
22
22
  [전체 버전 기록 보기](CHANGELOG.md)
23
23
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "project-auto-wizard",
3
- "version": "0.1.25",
3
+ "version": "0.1.31",
4
4
  "description": "One command DevOps: npx wizard that installs GitHub-native AI Release Automation into any project",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -221,6 +221,13 @@ def get_version_code():
221
221
 
222
222
 
223
223
  def set_version_code(new_code):
224
+ current = read_scalar_key("version_code", None)
225
+ if current not in (None, "", "null"):
226
+ try:
227
+ if int(new_code) < int(current):
228
+ log(f"WARNING: version_code {new_code} is lower than current {current} — writing anyway (regression?)")
229
+ except ValueError:
230
+ pass
224
231
  text = read_text()
225
232
  pattern = re.compile(r'^version_code:[ \t]*.*$', re.MULTILINE)
226
233
  replacement = f'version_code: {new_code} # app build number'
@@ -112,7 +112,11 @@ jobs:
112
112
  restore-keys: ${{ runner.os }}-flutter-pub-
113
113
 
114
114
  - name: Install dependencies
115
- run: flutter pub get
115
+ run: |
116
+ flutter pub get
117
+ if grep -q "build_runner" pubspec.yaml; then
118
+ dart run build_runner build --delete-conflicting-outputs
119
+ fi
116
120
 
117
121
  # version_manager.py를 사용하여 현재 버전 정보 가져오기
118
122
  - name: 현재 버전 정보 가져오기
@@ -265,7 +269,11 @@ jobs:
265
269
  restore-keys: ${{ runner.os }}-flutter-pub-
266
270
 
267
271
  - name: Install dependencies
268
- run: flutter pub get
272
+ run: |
273
+ flutter pub get
274
+ if grep -q "build_runner" pubspec.yaml; then
275
+ dart run build_runner build --delete-conflicting-outputs
276
+ fi
269
277
 
270
278
  - name: Set up Java
271
279
  uses: actions/setup-java@v5
@@ -328,6 +336,9 @@ jobs:
328
336
  echo "🔄 Flutter clean 및 의존성 재설치..."
329
337
  flutter clean
330
338
  flutter pub get
339
+ if grep -q "build_runner" pubspec.yaml; then
340
+ dart run build_runner build --delete-conflicting-outputs
341
+ fi
331
342
 
332
343
  # local.properties에 버전 정보 추가 (Flutter 플래그가 무시되는 문제 해결)
333
344
  echo "🔧 local.properties에 버전 정보 설정..."
@@ -47,7 +47,7 @@ env:
47
47
  PROJECT_TYPE: "flutter"
48
48
  # Flutter 루트 경로(레포 루트 기준). 단일레포면 ".", 모노레포면 "app" 등.
49
49
  # 통합 시점에 integrator의 flutter-root resolver가 project_paths.flutter 값으로 1회 치환.
50
- FLUTTER_ROOT: "." # @wizard auto:flutter-root
50
+ FLUTTER_PROJECT_DIR: "." # @wizard auto:flutter-root
51
51
  # 배포 모드 (store_only | store_prepare | store_submit)
52
52
  DEPLOY_MODE: ${{ github.event.inputs.deploy_mode || vars.ANDROID_DEPLOY_MODE || 'store_only' }}
53
53
 
@@ -57,7 +57,7 @@ jobs:
57
57
  runs-on: ubuntu-latest
58
58
  defaults:
59
59
  run:
60
- working-directory: ${{ env.FLUTTER_ROOT }}
60
+ working-directory: ${{ env.FLUTTER_PROJECT_DIR }}
61
61
  # CHANGELOG 워크플로우가 성공한 경우에만 실행
62
62
  if: ${{ github.event.workflow_run.conclusion == 'success' || github.event_name != 'workflow_run' }}
63
63
 
@@ -97,7 +97,11 @@ jobs:
97
97
  restore-keys: ${{ runner.os }}-flutter-pub-
98
98
 
99
99
  - name: Install dependencies
100
- run: flutter pub get
100
+ run: |
101
+ flutter pub get
102
+ if grep -q "build_runner" pubspec.yaml; then
103
+ dart run build_runner build --delete-conflicting-outputs
104
+ fi
101
105
 
102
106
  # version_manager.py를 사용하여 현재 버전 정보 가져오기
103
107
  - name: 현재 버전 정보 가져오기
@@ -167,6 +171,7 @@ jobs:
167
171
  name: release-notes
168
172
  path: final_release_notes.txt
169
173
  retention-days: 1
174
+ if-no-files-found: error
170
175
 
171
176
  # 프로젝트 파일들을 아티팩트로 업로드
172
177
  # .env는 보안상 아티팩트에 포함하지 않고, 각 job에서 secrets로 재생성
@@ -179,6 +184,7 @@ jobs:
179
184
  lib/
180
185
  assets/
181
186
  retention-days: 1
187
+ if-no-files-found: error
182
188
 
183
189
  build-android:
184
190
  name: Android AAB 빌드
@@ -186,7 +192,7 @@ jobs:
186
192
  needs: prepare-build
187
193
  defaults:
188
194
  run:
189
- working-directory: ${{ env.FLUTTER_ROOT }}
195
+ working-directory: ${{ env.FLUTTER_PROJECT_DIR }}
190
196
 
191
197
  steps:
192
198
  - name: Checkout repository
@@ -255,7 +261,11 @@ jobs:
255
261
  restore-keys: ${{ runner.os }}-flutter-pub-
256
262
 
257
263
  - name: Install dependencies
258
- run: flutter pub get
264
+ run: |
265
+ flutter pub get
266
+ if grep -q "build_runner" pubspec.yaml; then
267
+ dart run build_runner build --delete-conflicting-outputs
268
+ fi
259
269
 
260
270
  - name: Set up Java
261
271
  uses: actions/setup-java@v5
@@ -318,6 +328,9 @@ jobs:
318
328
  echo "🔄 Flutter clean 및 의존성 재설치..."
319
329
  flutter clean
320
330
  flutter pub get
331
+ if grep -q "build_runner" pubspec.yaml; then
332
+ dart run build_runner build --delete-conflicting-outputs
333
+ fi
321
334
 
322
335
  # local.properties에 버전 정보 추가 (Flutter 플래그가 무시되는 문제 해결)
323
336
  echo "🔧 local.properties에 버전 정보 설정..."
@@ -503,8 +516,9 @@ jobs:
503
516
  uses: actions/upload-artifact@v7
504
517
  with:
505
518
  name: android-aab
506
- path: ${{ env.FLUTTER_ROOT }}/build/app/outputs/bundle/release/app-release.aab
519
+ path: ${{ env.FLUTTER_PROJECT_DIR }}/build/app/outputs/bundle/release/app-release.aab
507
520
  retention-days: 1
521
+ if-no-files-found: error
508
522
 
509
523
  deploy-playstore:
510
524
  name: Play Store 내부 테스트 배포
@@ -512,7 +526,7 @@ jobs:
512
526
  needs: [prepare-build, build-android]
513
527
  defaults:
514
528
  run:
515
- working-directory: ${{ env.FLUTTER_ROOT }}
529
+ working-directory: ${{ env.FLUTTER_PROJECT_DIR }}
516
530
 
517
531
  steps:
518
532
  - name: Checkout repository
@@ -533,7 +547,7 @@ jobs:
533
547
  uses: actions/download-artifact@v8
534
548
  with:
535
549
  name: android-aab
536
- path: ${{ env.FLUTTER_ROOT }}/build/app/outputs/bundle/release/
550
+ path: ${{ env.FLUTTER_PROJECT_DIR }}/build/app/outputs/bundle/release/
537
551
 
538
552
  # 릴리즈 노트 다운로드
539
553
  - name: Download release notes
@@ -549,7 +563,7 @@ jobs:
549
563
  ruby-version: "3.4.1"
550
564
 
551
565
  - name: Install Fastlane
552
- working-directory: ${{ env.FLUTTER_ROOT }}/android
566
+ working-directory: ${{ env.FLUTTER_PROJECT_DIR }}/android
553
567
  run: |
554
568
  # multi_json: google-apis transitive 의존성이 선언 누락한 upstream 버그 회피 (gemspec 미선언 → Gem::LoadError)
555
569
  printf 'source "https://rubygems.org"\ngem "fastlane"\ngem "multi_json"\n' > Gemfile
@@ -650,7 +664,7 @@ jobs:
650
664
 
651
665
  if [ -f "$GITHUB_WORKSPACE/final_release_notes.txt" ]; then
652
666
  # Google Play release notes 한도(500 글자)에 맞춰 절단 (마진 두어 480자)
653
- # final_release_notes.txt·truncate 스크립트는 레포 루트 기준 — working-directory가 FLUTTER_ROOT이므로 절대경로 사용
667
+ # final_release_notes.txt·truncate 스크립트는 레포 루트 기준 — working-directory가 FLUTTER_PROJECT_DIR이므로 절대경로 사용
654
668
  bash "$GITHUB_WORKSPACE/.github/scripts/truncate_release_notes.sh" "$GITHUB_WORKSPACE/final_release_notes.txt" 480 char
655
669
  cp "$GITHUB_WORKSPACE/final_release_notes.txt" "$CHANGELOG_DIR/${VERSION_CODE}.txt"
656
670
  echo "✅ Changelog 준비 완료 (버전 코드: $VERSION_CODE)"
@@ -134,6 +134,9 @@ jobs:
134
134
  - name: Install dependencies
135
135
  run: |
136
136
  flutter pub get
137
+ if grep -q "build_runner" pubspec.yaml; then
138
+ dart run build_runner build --delete-conflicting-outputs
139
+ fi
137
140
  echo "Dependencies installed"
138
141
  ls -la
139
142
 
@@ -333,11 +333,11 @@ jobs:
333
333
  KEY_PASSWORD: ${{ secrets.RELEASE_KEY_PASSWORD }}
334
334
  run: |
335
335
  cat > android/key.properties << EOF
336
- storeFile=keystore/key.jks
337
- storePassword=$STORE_PASSWORD
338
- keyAlias=$KEY_ALIAS
339
- keyPassword=$KEY_PASSWORD
340
- EOF
336
+ storeFile=keystore/key.jks
337
+ storePassword=$STORE_PASSWORD
338
+ keyAlias=$KEY_ALIAS
339
+ keyPassword=$KEY_PASSWORD
340
+ EOF
341
341
  echo "✅ key.properties 생성 완료"
342
342
 
343
343
  # google-services.json 생성 (선택적)
@@ -388,6 +388,9 @@ EOF
388
388
  - name: Install dependencies
389
389
  run: |
390
390
  flutter pub get
391
+ if grep -q "build_runner" pubspec.yaml; then
392
+ dart run build_runner build --delete-conflicting-outputs
393
+ fi
391
394
  echo "✅ Dependencies installed"
392
395
 
393
396
  # Gradle 셋업
@@ -634,24 +637,24 @@ EOF
634
637
  BUILD_DATE=$(date '+%Y-%m-%d %H:%M:%S')
635
638
 
636
639
  cat > build-info.txt << EOF
637
- 테스트 APK 빌드 정보
640
+ 테스트 APK 빌드 정보
638
641
 
639
- 빌드 번호: #$BUILD_NUMBER
640
- EOF
642
+ 빌드 번호: #$BUILD_NUMBER
643
+ EOF
641
644
 
642
645
  if [ -n "$PR_NUMBER" ]; then
643
646
  cat >> build-info.txt << EOF
644
- PR 번호: #$PR_NUMBER
645
- EOF
647
+ PR 번호: #$PR_NUMBER
648
+ EOF
646
649
  fi
647
650
 
648
651
  cat >> build-info.txt << EOF
649
- 브랜치: $BRANCH_NAME
650
- 커밋: $COMMIT_SHORT
651
- 전체 커밋 해시: $COMMIT_SHA
652
- 빌드 날짜: $BUILD_DATE
652
+ 브랜치: $BRANCH_NAME
653
+ 커밋: $COMMIT_SHORT
654
+ 전체 커밋 해시: $COMMIT_SHA
655
+ 빌드 날짜: $BUILD_DATE
653
656
 
654
- EOF
657
+ EOF
655
658
 
656
659
  # 이슈 정보가 있으면 추가
657
660
  if [ -n "${{ needs.prepare-test-build.outputs.issue_number }}" ]; then
@@ -660,11 +663,11 @@ EOF
660
663
  ISSUE_URL="${{ needs.prepare-test-build.outputs.issue_url }}"
661
664
 
662
665
  cat >> build-info.txt << EOF
663
- 관련 이슈:
664
- - #$ISSUE_NUMBER: $ISSUE_TITLE
665
- - URL: $ISSUE_URL
666
+ 관련 이슈:
667
+ - #$ISSUE_NUMBER: $ISSUE_TITLE
668
+ - URL: $ISSUE_URL
666
669
 
667
- EOF
670
+ EOF
668
671
  fi
669
672
 
670
673
  echo "📋 빌드 정보 파일 생성 완료:"
@@ -674,13 +677,13 @@ EOF
674
677
  - name: Create build metadata file
675
678
  run: |
676
679
  cat > build-metadata.json << EOF
677
- {
678
- "pr_number": "${{ needs.prepare-test-build.outputs.pr_number }}",
679
- "build_number": "${{ needs.prepare-test-build.outputs.build_number }}",
680
- "issue_number": "${{ needs.prepare-test-build.outputs.issue_number }}",
681
- "branch_name": "${{ github.event_name == 'repository_dispatch' && github.event.client_payload.branch_name || github.ref_name }}"
682
- }
683
- EOF
680
+ {
681
+ "pr_number": "${{ needs.prepare-test-build.outputs.pr_number }}",
682
+ "build_number": "${{ needs.prepare-test-build.outputs.build_number }}",
683
+ "issue_number": "${{ needs.prepare-test-build.outputs.issue_number }}",
684
+ "branch_name": "${{ github.event_name == 'repository_dispatch' && github.event.client_payload.branch_name || github.ref_name }}"
685
+ }
686
+ EOF
684
687
  cat build-metadata.json
685
688
 
686
689
  # APK와 빌드 정보를 아티팩트로 업로드 (브랜치명 포함)
@@ -278,6 +278,9 @@ jobs:
278
278
  - name: Install dependencies
279
279
  run: |
280
280
  flutter pub get
281
+ if grep -q "build_runner" pubspec.yaml; then
282
+ dart run build_runner build --delete-conflicting-outputs
283
+ fi
281
284
  echo "✅ Dependencies installed"
282
285
 
283
286
  # Flutter Analyze 실행
@@ -377,6 +380,9 @@ jobs:
377
380
  - name: Install dependencies
378
381
  run: |
379
382
  flutter pub get
383
+ if grep -q "build_runner" pubspec.yaml; then
384
+ dart run build_runner build --delete-conflicting-outputs
385
+ fi
380
386
  echo "✅ Dependencies installed"
381
387
 
382
388
  # Java 설정
@@ -398,12 +404,12 @@ jobs:
398
404
  chmod +x gradlew
399
405
  echo "✅ Gradle wrapper permissions set"
400
406
 
401
- # APK 빌드
407
+ # APK 빌드 (CI는 빌드 검증 목적이므로 서명 불필요한 debug 빌드 사용)
402
408
  - name: Build APK
403
409
  id: build
404
410
  run: |
405
- echo "📦 Flutter APK 빌드 시작..."
406
- flutter build apk --release
411
+ echo "📦 Flutter APK 빌드 시작 (debug)..."
412
+ flutter build apk --debug
407
413
  echo "✅ APK 빌드 완료"
408
414
  ls -la ./build/app/outputs/flutter-apk/ || true
409
415
 
@@ -456,6 +462,23 @@ jobs:
456
462
  - name: Select Xcode version
457
463
  run: sudo xcode-select -s /Applications/Xcode_${{ env.XCODE_VERSION }}.app/Contents/Developer
458
464
 
465
+ - name: Install iOS device platform
466
+ env:
467
+ # xcode-select만으로는 서브프로세스에 전파되지 않아 명시적으로 지정
468
+ DEVELOPER_DIR: /Applications/Xcode_${{ env.XCODE_VERSION }}.app/Contents/Developer
469
+ run: |
470
+ echo "=== iPhoneOS.platform check ==="
471
+ ls "$DEVELOPER_DIR/Platforms/iPhoneOS.platform/" 2>/dev/null && echo "iPhoneOS.platform EXISTS" || echo "iPhoneOS.platform NOT FOUND"
472
+ echo "=== Installing MobileDevice packages ==="
473
+ sudo installer -pkg /Applications/Xcode_${{ env.XCODE_VERSION }}.app/Contents/Resources/Packages/MobileDevice.pkg -target /
474
+ sudo installer -pkg /Applications/Xcode_${{ env.XCODE_VERSION }}.app/Contents/Resources/Packages/MobileDeviceDevelopment.pkg -target /
475
+ echo "=== Accepting Xcode license ==="
476
+ sudo xcodebuild -license accept
477
+ echo "=== Initializing Xcode components ==="
478
+ xcrun simctl list >/dev/null 2>&1 || true
479
+ echo "=== Downloading iOS platform ==="
480
+ xcodebuild -downloadPlatform iOS
481
+
459
482
  # .env 파일 생성
460
483
  - name: Create .env file
461
484
  run: |
@@ -488,6 +511,9 @@ jobs:
488
511
  - name: Install dependencies
489
512
  run: |
490
513
  flutter pub get
514
+ if grep -q "build_runner" pubspec.yaml; then
515
+ dart run build_runner build --delete-conflicting-outputs
516
+ fi
491
517
  echo "✅ Dependencies installed"
492
518
 
493
519
  # Ruby 및 CocoaPods 설정
@@ -319,13 +319,13 @@ jobs:
319
319
  echo "commit_hash=$COMMIT_SHORT" >> $GITHUB_OUTPUT
320
320
 
321
321
  cat > final_release_notes.txt << EOF
322
- 테스트 빌드 #$BUILD_NUMBER
322
+ 테스트 빌드 #$BUILD_NUMBER
323
323
 
324
- 브랜치: $BRANCH_NAME
325
- 커밋: $COMMIT_SHORT
326
- 날짜: $BUILD_DATE
324
+ 브랜치: $BRANCH_NAME
325
+ 커밋: $COMMIT_SHORT
326
+ 날짜: $BUILD_DATE
327
327
 
328
- EOF
328
+ EOF
329
329
 
330
330
  # 이슈 정보가 있으면 추가
331
331
  if [ -n "${{ steps.issue_info.outputs.issue_number }}" ]; then
@@ -334,11 +334,11 @@ EOF
334
334
  ISSUE_URL="${{ steps.issue_info.outputs.issue_url }}"
335
335
 
336
336
  cat >> final_release_notes.txt << EOF
337
- 관련 이슈:
338
- - #$ISSUE_NUMBER: $ISSUE_TITLE
339
- - URL: $ISSUE_URL
337
+ 관련 이슈:
338
+ - #$ISSUE_NUMBER: $ISSUE_TITLE
339
+ - URL: $ISSUE_URL
340
340
 
341
- EOF
341
+ EOF
342
342
  fi
343
343
 
344
344
  echo "📋 생성된 릴리즈 노트:"
@@ -542,7 +542,11 @@ EOF
542
542
  restore-keys: ${{ runner.os }}-flutter-pub-
543
543
 
544
544
  - name: Install dependencies
545
- run: flutter pub get
545
+ run: |
546
+ flutter pub get
547
+ if grep -q "build_runner" pubspec.yaml; then
548
+ dart run build_runner build --delete-conflicting-outputs
549
+ fi
546
550
 
547
551
  - name: Set up Ruby
548
552
  uses: ruby/setup-ruby@v1
@@ -618,13 +622,13 @@ EOF
618
622
  - name: Create build metadata file
619
623
  run: |
620
624
  cat > build-metadata.json << EOF
621
- {
622
- "pr_number": "${{ needs.prepare-test-build.outputs.pr_number }}",
623
- "build_number": "${{ needs.prepare-test-build.outputs.build_number }}",
624
- "issue_number": "${{ needs.prepare-test-build.outputs.issue_number }}",
625
- "branch_name": "${{ github.event_name == 'repository_dispatch' && github.event.client_payload.branch_name || github.ref_name }}"
626
- }
627
- EOF
625
+ {
626
+ "pr_number": "${{ needs.prepare-test-build.outputs.pr_number }}",
627
+ "build_number": "${{ needs.prepare-test-build.outputs.build_number }}",
628
+ "issue_number": "${{ needs.prepare-test-build.outputs.issue_number }}",
629
+ "branch_name": "${{ github.event_name == 'repository_dispatch' && github.event.client_payload.branch_name || github.ref_name }}"
630
+ }
631
+ EOF
628
632
  cat build-metadata.json
629
633
 
630
634
  - name: Upload IPA artifact
@@ -75,7 +75,7 @@ on:
75
75
  # 🔧 프로젝트별 설정 (아래 값들을 수정하세요)
76
76
  # ============================================
77
77
  env:
78
- FLUTTER_ROOT: "." # @wizard auto:flutter-root
78
+ FLUTTER_PROJECT_DIR: "." # @wizard auto:flutter-root
79
79
  FLUTTER_VERSION: "3.35.5"
80
80
  XCODE_VERSION: "26.0"
81
81
  PROJECT_TYPE: "flutter"
@@ -102,7 +102,7 @@ jobs:
102
102
  runs-on: macos-26
103
103
  defaults:
104
104
  run:
105
- working-directory: ${{ env.FLUTTER_ROOT }}
105
+ working-directory: ${{ env.FLUTTER_PROJECT_DIR }}
106
106
  if: ${{ github.event.workflow_run.conclusion == 'success' || github.event_name != 'workflow_run' }}
107
107
 
108
108
  outputs:
@@ -154,7 +154,11 @@ jobs:
154
154
  restore-keys: ${{ runner.os }}-flutter-pub-
155
155
 
156
156
  - name: Install dependencies
157
- run: flutter pub get
157
+ run: |
158
+ flutter pub get
159
+ if grep -q "build_runner" pubspec.yaml; then
160
+ dart run build_runner build --delete-conflicting-outputs
161
+ fi
158
162
 
159
163
  - name: Set up Ruby
160
164
  uses: ruby/setup-ruby@v1
@@ -207,6 +211,7 @@ jobs:
207
211
  name: release-notes
208
212
  path: final_release_notes.txt
209
213
  retention-days: 1
214
+ if-no-files-found: error
210
215
 
211
216
  - name: Upload project files
212
217
  uses: actions/upload-artifact@v7
@@ -219,6 +224,7 @@ jobs:
219
224
  lib/
220
225
  assets/
221
226
  retention-days: 1
227
+ if-no-files-found: error
222
228
 
223
229
  # ============================================
224
230
  # iOS 빌드 (xcodebuild 직접 사용)
@@ -228,7 +234,7 @@ jobs:
228
234
  runs-on: macos-26
229
235
  defaults:
230
236
  run:
231
- working-directory: ${{ env.FLUTTER_ROOT }}
237
+ working-directory: ${{ env.FLUTTER_PROJECT_DIR }}
232
238
  needs: prepare-build
233
239
 
234
240
  steps:
@@ -290,7 +296,11 @@ jobs:
290
296
  restore-keys: ${{ runner.os }}-flutter-pub-
291
297
 
292
298
  - name: Install dependencies
293
- run: flutter pub get
299
+ run: |
300
+ flutter pub get
301
+ if grep -q "build_runner" pubspec.yaml; then
302
+ dart run build_runner build --delete-conflicting-outputs
303
+ fi
294
304
 
295
305
  - name: Set up Ruby
296
306
  uses: ruby/setup-ruby@v1
@@ -361,8 +371,9 @@ jobs:
361
371
  uses: actions/upload-artifact@v7
362
372
  with:
363
373
  name: ios-ipa
364
- path: ${{ env.FLUTTER_ROOT }}/ios/build/ipa/*.ipa
374
+ path: ${{ env.FLUTTER_PROJECT_DIR }}/ios/build/ipa/*.ipa
365
375
  retention-days: 1
376
+ if-no-files-found: error
366
377
 
367
378
  # ============================================
368
379
  # TestFlight 배포 (마법사 생성 Fastfile 사용)
@@ -372,7 +383,7 @@ jobs:
372
383
  runs-on: macos-26
373
384
  defaults:
374
385
  run:
375
- working-directory: ${{ env.FLUTTER_ROOT }}
386
+ working-directory: ${{ env.FLUTTER_PROJECT_DIR }}
376
387
  needs: [prepare-build, build-ios]
377
388
 
378
389
  steps:
@@ -391,7 +402,7 @@ jobs:
391
402
  uses: actions/download-artifact@v8
392
403
  with:
393
404
  name: ios-ipa
394
- path: ${{ env.FLUTTER_ROOT }}/ios/build/ipa/
405
+ path: ${{ env.FLUTTER_PROJECT_DIR }}/ios/build/ipa/
395
406
 
396
407
  - name: Download release notes
397
408
  uses: actions/download-artifact@v8
@@ -410,7 +421,7 @@ jobs:
410
421
  echo "✅ Fastfile 확인됨: ios/fastlane/Fastfile"
411
422
 
412
423
  - name: Install Fastlane
413
- working-directory: ${{ env.FLUTTER_ROOT }}/ios
424
+ working-directory: ${{ env.FLUTTER_PROJECT_DIR }}/ios
414
425
  run: |
415
426
  # multi_json: google-apis transitive 의존성이 선언 누락한 upstream 버그 회피 (gemspec 미선언 → Gem::LoadError)
416
427
  printf 'source "https://rubygems.org"\ngem "fastlane"\ngem "multi_json"\n' > Gemfile
@@ -424,14 +435,14 @@ jobs:
424
435
  SKIP_WAITING_FOR_BUILD_PROCESSING: ${{ env.SKIP_WAITING_FOR_BUILD_PROCESSING }}
425
436
  run: |
426
437
  # IPA 절대경로 탐색
427
- IPA_PATH=$(find "$GITHUB_WORKSPACE/${FLUTTER_ROOT}/ios/build/ipa" -name "*.ipa" | head -1)
438
+ IPA_PATH=$(find "$GITHUB_WORKSPACE/${FLUTTER_PROJECT_DIR}/ios/build/ipa" -name "*.ipa" | head -1)
428
439
  echo "📦 Found IPA at: $IPA_PATH"
429
440
 
430
441
  if [ ! -f "$IPA_PATH" ]; then
431
442
  echo "❌ IPA 파일을 찾을 수 없습니다!"; ls -la ios/build/ipa/ || true; exit 1
432
443
  fi
433
444
 
434
- # Release notes 준비 (final_release_notes.txt·truncate 스크립트는 레포 루트 기준 — working-directory가 FLUTTER_ROOT이므로 절대경로 사용)
445
+ # Release notes 준비 (final_release_notes.txt·truncate 스크립트는 레포 루트 기준 — working-directory가 FLUTTER_PROJECT_DIR이므로 절대경로 사용)
435
446
  if [ -f "$GITHUB_WORKSPACE/final_release_notes.txt" ]; then
436
447
  # App Store Connect "What to Test" 한도(4000 바이트)에 맞춰 절단 (마진 두어 3800byte)
437
448
  bash "$GITHUB_WORKSPACE/.github/scripts/truncate_release_notes.sh" "$GITHUB_WORKSPACE/final_release_notes.txt" 3800 byte
@@ -5,7 +5,7 @@
5
5
  import { join } from "node:path";
6
6
  import { existsSync, readFileSync } from "node:fs";
7
7
  import { resolvePayloadRoot, assertPayload, readTemplateVersion } from "../core/assets.js";
8
- import { detectTypes, detectVersion, detectDefaultBranch, detectRepoName, makeResolvers } from "../core/detect-fs.js";
8
+ import { detectTypes, detectVersion, detectDefaultBranch, detectRepoName, makeResolvers, detectBuildNumber } from "../core/detect-fs.js";
9
9
  import { parseExisting } from "../core/version-yml.js";
10
10
  import { runBreakingCheck } from "../core/breaking-check.js";
11
11
  import { resolveProjectPaths } from "../core/paths-resolve.js";
@@ -85,7 +85,6 @@ export async function runInteractive(baseCtx, { cwd = process.cwd(), payloadRoot
85
85
  let version = (existing?.version) || detectVersion(cwd);
86
86
  let branch = detectDefaultBranch(cwd);
87
87
  const repoName = detectRepoName(cwd);
88
- const versionCode = existing?.versionCode ?? 1; // 기존 빌드번호 보존
89
88
  // 선택 워크플로우 초기값: version.yml 저장 옵션 (.sh read_template_options L2361 등가)
90
89
  let includeNexus = existing?.options?.nexus ?? false;
91
90
  let includeSecretBackup = existing?.options?.secretBackup ?? false;
@@ -166,6 +165,8 @@ export async function runInteractive(baseCtx, { cwd = process.cwd(), payloadRoot
166
165
  }
167
166
  }
168
167
 
168
+ const versionCode = existing?.versionCode ?? detectBuildNumber(cwd, { types }) ?? 1; // 기존 빌드번호 보존, 신규 통합 시 프로젝트 파일에서 감지 (이슈 #41)
169
+
169
170
  // 신규 질문 ① — 브랜치 설정 (DESIGN-SPEC §4). full/workflows만 질문, version은 기본값 기록.
170
171
  // 저장값(version.yml metadata.template.branches)이 있으면 재질문 없이 재사용 (업데이트 모드).
171
172
  let branches = existing?.branches
@@ -250,7 +251,7 @@ export async function runInteractive(baseCtx, { cwd = process.cwd(), payloadRoot
250
251
 
251
252
  // 완료 요약 (.sh print_summary L5438)
252
253
  io.summary?.({
253
- mode, types, version, branches,
254
+ mode, types, version, versionCode, branches,
254
255
  copiedFiles: result?.workflows?.copiedFiles ?? [],
255
256
  gitignoreUpdated: result?.gitignoreUpdated === true,
256
257
  });
@@ -3,7 +3,7 @@
3
3
  import { existsSync, readFileSync, readdirSync } from "node:fs";
4
4
  import { join, basename } from "node:path";
5
5
  import { execFileSync } from "node:child_process";
6
- import { detectTypesFromMarkers, detectVersionFromFiles } from "./detect.js";
6
+ import { detectTypesFromMarkers, detectVersionFromFiles, detectBuildNumberFromFiles } from "./detect.js";
7
7
  import { parseExisting } from "./version-yml.js";
8
8
 
9
9
  const hasFile = (root) => (rel) => existsSync(join(root, rel));
@@ -35,6 +35,13 @@ export function detectVersion(root, { warn = (m) => console.error(m) } = {}) {
35
35
  return detectVersionFromFiles({ read, readJson, gitTag, warn });
36
36
  }
37
37
 
38
+ // 빌드 번호 감지 — 신규 통합 시 pubspec.yaml/build.gradle/app.json에서 실제 빌드 번호를 읽는다 (이슈 #41).
39
+ export function detectBuildNumber(root, { types = [], warn = (m) => console.error(m) } = {}) {
40
+ const read = readFile(root);
41
+ const readJson = (rel) => { const c = read(rel); try { return c ? JSON.parse(c) : null; } catch { return null; } };
42
+ return detectBuildNumberFromFiles({ types, read, readJson, warn });
43
+ }
44
+
38
45
  // 기본 브랜치 감지 — symbolic-ref → remote show → main.
39
46
  export function detectDefaultBranch(root) {
40
47
  let b = gitOut(root, ["symbolic-ref", "refs/remotes/origin/HEAD"]);
@@ -62,3 +62,42 @@ export function markerForType(type) {
62
62
  export function extraMarkers(type) {
63
63
  return { python: ["setup.py", "requirements.txt"], spring: ["build.gradle.kts", "pom.xml"] }[type] || [];
64
64
  }
65
+
66
+ // 빌드 번호 감지 (이슈 #41) — 신규 통합 시 pubspec.yaml/build.gradle/app.json에 이미 기록된
67
+ // 빌드 번호를 읽어 version_code가 항상 1로 초기화되는 걸 막는다. types 배열에서 먼저 매칭되는
68
+ // 첫 타입만 사용한다(다른 감지 로직의 types[0]=primary 관례와 동일). read(rel)=>string|null,
69
+ // readJson(rel)=>object|null 로 주입.
70
+ export function detectBuildNumberFromFiles({ types = [], read, readJson, warn }) {
71
+ const tryFlutter = () => {
72
+ const content = read("pubspec.yaml");
73
+ if (content == null) return null;
74
+ const m = content.match(/^version:\s*\d+\.\d+\.\d+\+(\d+)/m);
75
+ if (m) return parseInt(m[1], 10);
76
+ warn?.("⚠️ pubspec.yaml에 빌드 번호(+N)가 없어 version_code를 감지하지 못했습니다 — 기본값 1을 사용합니다. 실제 빌드 번호를 확인하세요.");
77
+ return null;
78
+ };
79
+ const tryReactNative = () => {
80
+ const content = read("android/app/build.gradle");
81
+ if (content == null) return null;
82
+ // 앵커 + m 플래그로 한 줄 전체가 "versionCode N"인 라인만 매칭 — 주석 처리된
83
+ // "// versionCode 2"나 다른 블록의 versionCode 참조에 오매칭되지 않도록 함.
84
+ const m = content.match(/^\s*versionCode\s+(\d+)\s*$/m);
85
+ if (m) return parseInt(m[1], 10);
86
+ warn?.("⚠️ android/app/build.gradle에 versionCode가 없어 version_code를 감지하지 못했습니다 — 기본값 1을 사용합니다. 실제 빌드 번호를 확인하세요.");
87
+ return null;
88
+ };
89
+ const tryExpo = () => {
90
+ const data = readJson?.("app.json");
91
+ if (data == null) return null;
92
+ const code = data?.expo?.android?.versionCode;
93
+ if (Number.isInteger(code)) return code;
94
+ warn?.("⚠️ app.json의 expo.android.versionCode가 없어 version_code를 감지하지 못했습니다 — 기본값 1을 사용합니다. 실제 빌드 번호를 확인하세요.");
95
+ return null;
96
+ };
97
+ for (const t of types) {
98
+ if (t === "flutter") return tryFlutter();
99
+ if (t === "react-native") return tryReactNative();
100
+ if (t === "react-native-expo") return tryExpo();
101
+ }
102
+ return null;
103
+ }
package/src/index.js CHANGED
@@ -9,7 +9,7 @@ import { parseArgs, parsePathsCsv, CliError } from "./cli/args.js";
9
9
  import { HELP_TEXT } from "./cli/help.js";
10
10
  import { createContext } from "./context.js";
11
11
  import { resolvePayloadRoot, assertPayload, readTemplateVersion } from "./core/assets.js";
12
- import { detectTypes, detectVersion, detectDefaultBranch, detectRepoName, makeResolvers } from "./core/detect-fs.js";
12
+ import { detectTypes, detectVersion, detectDefaultBranch, detectRepoName, makeResolvers, detectBuildNumber } from "./core/detect-fs.js";
13
13
  import { parseExisting } from "./core/version-yml.js";
14
14
  import { runBreakingCheck } from "./core/breaking-check.js";
15
15
  import { resolveProjectPaths } from "./core/paths-resolve.js";
@@ -237,7 +237,7 @@ export async function run(argv, {
237
237
  const types = opts.types.length ? opts.types : detectTypes(cwd);
238
238
  // version: 기존 version.yml 최우선(SSoT — 재실행 시 덮어쓰기 방지) → CLI 지정 → 파일 감지
239
239
  const version = (existing?.version) || opts.version || detectVersion(cwd);
240
- const versionCode = existing?.versionCode ?? 1; // 기존 빌드번호 보존 (.sh L2208~2221)
240
+ const versionCode = existing?.versionCode ?? detectBuildNumber(cwd, { types }) ?? 1; // 기존 빌드번호 보존, 신규 통합 시 프로젝트 파일에서 감지 (.sh L2208~2221, 이슈 #41)
241
241
  const branch = detectDefaultBranch(cwd);
242
242
  const repoName = detectRepoName(cwd);
243
243
  // 경로 확정 (.sh resolve_project_paths 비대화형 경로 — --paths 우선 → 저장값 → 후보 1개 자동 → 에러)
@@ -314,7 +314,7 @@ export async function run(argv, {
314
314
 
315
315
  // 완료 요약 (.sh print_summary — CLI 모드에서도 출력)
316
316
  printSummary({
317
- mode: opts.mode, types, version, branches,
317
+ mode: opts.mode, types, version, versionCode, branches,
318
318
  copiedFiles: result?.workflows?.copiedFiles ?? [],
319
319
  gitignoreUpdated: result?.gitignoreUpdated === true,
320
320
  });
package/src/ui/summary.js CHANGED
@@ -6,7 +6,7 @@ import { paint, A, colorEnabled } from "./ansi.js";
6
6
  const SEPARATOR = "────────────────────────────────────────";
7
7
 
8
8
  export function printSummary(ctx) {
9
- const { mode, types = [], version = "", copiedFiles = [], branches = null, gitignoreUpdated = false } = ctx || {};
9
+ const { mode, types = [], version = "", versionCode = null, copiedFiles = [], branches = null, gitignoreUpdated = false } = ctx || {};
10
10
  const err = (s = "") => process.stderr.write(`${s}\n`);
11
11
  // 색상은 ansi.js의 공용 가드로 통일 (NO_COLOR + stderr TTY 여부)
12
12
  const enabled = colorEnabled(process.stderr);
@@ -56,6 +56,10 @@ export function printSummary(ctx) {
56
56
  err("");
57
57
  err("추가된 파일:");
58
58
  err(` 📄 version.yml (버전: ${version}, 타입: ${types.join(",")})`);
59
+ const BUILD_NUMBER_TYPES = new Set(["flutter", "react-native", "react-native-expo"]);
60
+ if (versionCode != null && types.some((t) => BUILD_NUMBER_TYPES.has(t))) {
61
+ err(` 빌드 번호: ${versionCode}`);
62
+ }
59
63
  err(" 📝 README.md (버전 섹션 추가)");
60
64
  err("");
61
65
  err("추가된 워크플로우:");