project-auto-wizard 0.10.0 → 0.12.0
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 +104 -18
- package/package.json +2 -2
- package/payload/config/breaking-changes.json +24 -1
- package/payload/flutter-app/android/fastlane/Fastfile.playstore +86 -0
- package/payload/flutter-app/ios/ExportOptions.plist +31 -0
- package/payload/flutter-app/ios/fastlane/Fastfile +117 -0
- package/payload/scripts/__pycache__/changelog_manager.cpython-314.pyc +0 -0
- package/payload/scripts/__pycache__/issue_helper.cpython-314.pyc +0 -0
- package/payload/scripts/__pycache__/version_manager.cpython-314.pyc +0 -0
- package/payload/scripts/changelog_manager.py +83 -30
- package/payload/version.yml.template +5 -0
- package/payload/workflows/common/PROJECT-COMMON-AI-PR-SUMMARY.yaml +30 -7
- package/payload/workflows/common/PROJECT-COMMON-AUTO-CHANGELOG-CONTROL.yaml +36 -8
- package/payload/workflows/common/PROJECT-COMMON-RELEASE-PUBLISH.yaml +25 -1
- package/payload/workflows/flutter/PROJECT-FLUTTER-ANDROID-FIREBASE-CICD.yaml +48 -20
- package/payload/workflows/flutter/PROJECT-FLUTTER-ANDROID-PLAYSTORE-CICD.yaml +44 -19
- package/payload/workflows/flutter/PROJECT-FLUTTER-ANDROID-SELFHOSTED-CICD.yaml +28 -36
- package/payload/workflows/flutter/PROJECT-FLUTTER-ANDROID-TEST-APK.yaml +32 -45
- package/payload/workflows/flutter/PROJECT-FLUTTER-CI.yaml +123 -32
- package/payload/workflows/flutter/PROJECT-FLUTTER-IOS-TEST-TESTFLIGHT.yaml +56 -41
- package/payload/workflows/flutter/PROJECT-FLUTTER-IOS-TESTFLIGHT.yaml +61 -40
- package/payload/workflows/go/PROJECT-GO-CI.yaml +47 -0
- package/payload/workflows/next/PROJECT-NEXT-CI.yaml +47 -0
- package/payload/workflows/python/PROJECT-PYTHON-CI.yaml +47 -0
- package/payload/workflows/react/PROJECT-REACT-CI.yaml +47 -0
- package/payload/workflows/spring/nexus/PROJECT-SPRING-GITHUB-PACKAGES-PUBLISH.yml +1 -0
- package/payload/workflows/spring/nexus/PROJECT-SPRING-NEXUS-CI.yml +47 -0
- package/payload/workflows/spring/nexus/PROJECT-SPRING-NEXUS-PUBLISH.yml +1 -0
- package/src/cli/args.js +44 -0
- package/src/cli/help.js +6 -1
- package/src/commands/doctor.js +59 -5
- package/src/commands/dry-run.js +10 -0
- package/src/commands/full.js +28 -3
- package/src/commands/interactive-flutter.js +95 -0
- package/src/commands/interactive.js +60 -5
- package/src/commands/status.js +21 -2
- package/src/context.js +7 -0
- package/src/core/breaking.js +7 -3
- package/src/core/copy/flutter-app.js +42 -0
- package/src/core/copy/workflows.js +21 -9
- package/src/core/detect-fs.js +9 -1
- package/src/core/flutter-options.js +118 -0
- package/src/core/installed-stores.js +12 -0
- package/src/core/verify.js +1 -1
- package/src/core/version-yml.js +50 -6
- package/src/core/wizard-env.js +26 -4
- package/src/index.js +34 -1
- package/src/ui/env-plan.js +8 -5
- package/src/ui/prompts.js +88 -3
- package/src/ui/status-cards.js +13 -1
- package/src/ui/summary.js +28 -9
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# project-auto-wizard
|
|
2
2
|
|
|
3
|
-
> **One command DevOps** — `npx` 한 줄로 어떤 프로젝트든 GitHub-native AI
|
|
3
|
+
> **One command DevOps** — `npx` 한 줄로 어떤 프로젝트든 GitHub-native 릴리스 자동화(GitHub Copilot AI 요약은 선택)를 설치하는 마법사
|
|
4
4
|
|
|
5
5
|
- 새 프로젝트를 시작하면 코드를 작성하기 전부터 버전 관리, 배포 자동화, 변경 기록 작성처럼 먼저 정해야 할 일이 많다. project-auto-wizard는 이 준비 과정을 명령 한 번으로 구성하고, 프로젝트 상태 관리가 GitHub 안에서 자동으로 동작하도록 해 개발자가 기능 구현에 집중하도록 돕는 오픈소스 도구
|
|
6
6
|
|
|
@@ -17,7 +17,7 @@ npx project-auto-wizard
|
|
|
17
17
|
[](package.json)
|
|
18
18
|
|
|
19
19
|
<!-- AUTO-VERSION-SECTION: DO NOT EDIT MANUALLY -->
|
|
20
|
-
## 최신 버전 : v0.
|
|
20
|
+
## 최신 버전 : v0.11.0 (2026-09-23)
|
|
21
21
|
|
|
22
22
|
[전체 버전 기록 보기](CHANGELOG.md)
|
|
23
23
|
|
|
@@ -38,7 +38,7 @@ npx project-auto-wizard --mode full --force --type spring,react # CI에서 비
|
|
|
38
38
|
| 축 | 내용 |
|
|
39
39
|
|---|---|
|
|
40
40
|
| ① **npx 마법사** | 마커 파일로 프로젝트 타입 자동 감지 — **10타입 + 멀티타입 + 모노레포 경로**까지. 질문은 최소한만 |
|
|
41
|
-
| ② **GitHub-native
|
|
41
|
+
| ② **GitHub-native Release Automation** | 릴리스 PR을 열면: 버전 확정 → **릴리스 노트 작성**(기본은 규칙 기반, GitHub Copilot AI는 선택) → CHANGELOG 갱신 → automerge → tag + GitHub Release. **API 키 0개** |
|
|
42
42
|
| ③ **타입별 CI/CD 워크플로우** | Spring(무중단 배포 포함)·Flutter(스토어 배포)·React·Next·Python·Go 등 타입에 맞는 GitHub Actions 자동 배치 |
|
|
43
43
|
|
|
44
44
|
### 지원 프로젝트 타입
|
|
@@ -66,7 +66,7 @@ flutter.APP_ARTIFACT_NAME:
|
|
|
66
66
|
|
|
67
67
|
`spring`/`flutter`는 아래처럼 단일 CI 이상으로 깊게 구성되어 있습니다:
|
|
68
68
|
|
|
69
|
-
- **flutter**: Android(Firebase/Playstore/Selfhosted/TestAPK 배포), iOS(TestFlight/Test-TestFlight), CI, Lab 트리거까지 8종
|
|
69
|
+
- **flutter**: Android(Firebase/Playstore/Selfhosted/TestAPK 배포), iOS(TestFlight/Test-TestFlight), CI, Lab 트리거까지 8종 — 스토어 배포(Play Store·TestFlight)는 고른 플랫폼만 설치되고 fastlane 파일도 함께 생성됩니다. 환경변수 방식·배포 모드 등 자세한 내용은 아래 "Flutter 워크플로우 상세"를 참고하세요.
|
|
70
70
|
- **spring**: 서버 배포 1종(단일 서버 / 무중단 Nginx / 무중단 Traefik / 배포 안 함 중 택1) + PR 프리뷰 + 라이브러리 publish 2종(Nexus·GitHub Packages, `--nexus` opt-in)
|
|
71
71
|
- 서버 배포 워크플로우는 **서로 대체재**라 하나만 설치합니다. 대화형에서 고르면 그것만 깔리고 **`push` 트리거까지 켜진 채로** 설치됩니다. 비대화형은 `--deploy-style simple|nginx|traefik|none` (기본: `simple`).
|
|
72
72
|
- 고른 방식은 `version.yml`에 기록되므로 다시 실행해도 묻지 않습니다. 방식을 바꾸면 **이전 워크플로우를 마법사가 정리합니다** — 손대지 않은 파일은 삭제하고, 수정한 파일은 `.bak`으로 옮겨 내용을 보존합니다. 남겨두면 배포가 두 번 돕니다.
|
|
@@ -75,6 +75,81 @@ flutter.APP_ARTIFACT_NAME:
|
|
|
75
75
|
- **python**: CI / PR 프리뷰 / SimpleCICD
|
|
76
76
|
- **go**: CI(Dockerfile 불필요, go test/vet/build/lint) / PR 프리뷰 / SimpleCICD(Dockerfile 있는 프로젝트만 해당)
|
|
77
77
|
|
|
78
|
+
<a id="flutter-store"></a>
|
|
79
|
+
|
|
80
|
+
#### Flutter 워크플로우 상세
|
|
81
|
+
|
|
82
|
+
프로젝트 타입에 `flutter`가 포함되면 마법사가 아래 선택지 3개를 묻습니다. 고른 값은 `version.yml`의 `metadata.template.options`(`env_mode`, `flutter_store`, `android_deploy_mode`, `ios_deploy_mode`)에 기록되므로 다시 실행해도 묻지 않고, 나중에 바꾸려면 확인 화면의 **수정하기 > 환경변수 방식 / 스토어 배포 대상 / 배포 모드**를 쓰세요. Flutter가 없는 프로젝트에는 이 질문·저장값이 전혀 나타나지 않습니다.
|
|
83
|
+
|
|
84
|
+
| 선택지 | 값 | 기본값 | CLI 플래그 |
|
|
85
|
+
|---|---|---|---|
|
|
86
|
+
| 환경변수 방식 | `dart-define` / `dotenv` | 신규 설치는 `dart-define`. `version.yml`이 이미 있고 저장값이 없는 설치는 기존 동작을 보존하려고 `dotenv` 유지 | `--flutter-env-mode` |
|
|
87
|
+
| 스토어 배포 대상 | Android(Play Store) / iOS(TestFlight) 다중 선택 | 대화형 신규 설치는 아무것도 선택하지 않은 상태, 비대화형·플래그 미지정은 현행 동작(둘 다 설치). 저장값이 없는 기존 설치는 이미 설치된 스토어 워크플로우에서 초기 선택을 추론 | `--flutter-store android,ios,none` |
|
|
88
|
+
| 배포 모드 | 고른 플랫폼별 `store_only` / `store_prepare` / `store_submit` | `store_only` | `--android-deploy-mode`, `--ios-deploy-mode` |
|
|
89
|
+
|
|
90
|
+
**설치되는 구성**
|
|
91
|
+
|
|
92
|
+
- 항상 설치: `CI`, `ANDROID-FIREBASE-CICD`, `ANDROID-SELFHOSTED-CICD`, `ANDROID-TEST-APK`, `APP-BUILD-TRIGGER`
|
|
93
|
+
- Android를 고르면: `ANDROID-PLAYSTORE-CICD` + `android/fastlane/Fastfile.playstore`
|
|
94
|
+
- iOS를 고르면: `IOS-TESTFLIGHT` + `IOS-TEST-TESTFLIGHT` + `ios/fastlane/Fastfile` + `ios/ExportOptions.plist`
|
|
95
|
+
- fastlane은 위 스토어 배포 워크플로우에서만 씁니다. `SELFHOSTED`와 `TEST-APK`는 `flutter build apk --release`를 직접 실행하며 Ruby·fastlane을 설치하지 않습니다.
|
|
96
|
+
- `Fastfile`과 `ExportOptions.plist`는 **Flutter 루트 기준**으로(모노레포는 `--paths flutter=app`이면 `app/` 아래) **없을 때만 생성**합니다. 이미 있으면 덮어쓰지 않고 설치 요약과 `--dry-run`에 "기존 파일 유지"로 표시하며, `--mode uninstall`도 이 파일들은 건드리지 않습니다(사용자 소유 파일).
|
|
97
|
+
- 스토어 대상을 해제하고 다시 실행하면 배포 방식을 바꿀 때와 같은 규칙으로 정리합니다 — 손대지 않은 워크플로우는 삭제하고, 수정한 것은 `.bak`으로 옮겨 보존합니다. `Fastfile`·`ExportOptions.plist`는 삭제하지 않습니다.
|
|
98
|
+
|
|
99
|
+
**환경변수 방식**
|
|
100
|
+
|
|
101
|
+
두 방식 모두 시크릿 `ENV_FILE`(없으면 `ENV`)에 `.env` 형식으로 값을 넣어 두면 됩니다. 두 방식을 동시에 쓰는 모드는 없습니다.
|
|
102
|
+
|
|
103
|
+
- `dart-define`: `ENV_FILE`을 프로젝트 밖 임시 경로(러너 임시 폴더)에 쓰고 모든 `flutter build`에 `--dart-define-from-file`로 넘깁니다. 프로젝트 루트에는 `.env`를 만들지 않으며, 코드에서는 `String.fromEnvironment('KEY')`로 읽습니다. 값이 아닌 파일 경로를 넘기므로 `--verbose` 로그에도 값이 명령줄에 노출되지 않습니다.
|
|
104
|
+
- `dotenv`: Flutter 루트에 `.env`를 만든 뒤 빌드합니다(`build_runner`가 그 뒤에 실행되는 순서 유지). `flutter_dotenv`·`envied`를 쓰는 프로젝트가 여기에 해당합니다. 이 `.env`에는 아래 파서 제약이 적용되지 않습니다.
|
|
105
|
+
|
|
106
|
+
`dart-define` 방식에서 `ENV_FILE`은 Flutter의 `--dart-define-from-file` 파서가 읽으므로 다음 범위만 지원됩니다 (근거: Flutter 3.47.5 `flutter_tools/lib/src/runner/flutter_command.dart`의 `DotEnvRegex`).
|
|
107
|
+
|
|
108
|
+
- 내용이 `{`로 시작하면 JSON 파일로 해석하고, 그렇지 않으면 `KEY=값` 줄로 해석합니다. 키는 `[a-zA-Z_][a-zA-Z0-9_]*` 형태여야 합니다.
|
|
109
|
+
- `#`로 시작하는 주석 줄과 빈 줄은 무시합니다.
|
|
110
|
+
- 값은 `"…"`, `'…'`, `` `…` `` 따옴표가 벗겨지고 그 뒤의 `# 주석`은 제거됩니다. 따옴표 없는 값은 공백 또는 `#` 앞까지 읽으며, 값 안의 `=`는 허용됩니다.
|
|
111
|
+
- `export KEY=값` 형태와 멀티라인(`"""`) 값은 지원하지 않아 빌드가 오류로 종료됩니다.
|
|
112
|
+
|
|
113
|
+
**배포 모드** — 플랫폼별로 아래처럼 동작합니다(모르는 값은 `store_only`로 취급). 기본값은 워크플로우 표현식의 폴백 자리에 반영되며, 저장소 변수 `ANDROID_DEPLOY_MODE`·`IOS_DEPLOY_MODE`와 `workflow_dispatch` 입력이 항상 우선합니다.
|
|
114
|
+
|
|
115
|
+
| 모드 | Play Store (Android) | TestFlight / App Store (iOS) |
|
|
116
|
+
|---|---|---|
|
|
117
|
+
| `store_only` | internal 트랙에 업로드 | TestFlight 업로드 |
|
|
118
|
+
| `store_prepare` | production 트랙에 draft 상태로 업로드 (Play Console에서 직접 출시) | 앱 버전·메타데이터 준비까지 (심사 제출 안 함) |
|
|
119
|
+
| `store_submit` | production 트랙에 심사 제출 | 심사 제출까지 |
|
|
120
|
+
|
|
121
|
+
> `store_submit`을 고르면 **main push마다 심사가 자동 제출**됩니다.
|
|
122
|
+
|
|
123
|
+
**필요한 Secrets · Variables** — 설치 완료 화면과 실행 로그에도 설치된 워크플로우가 실제로 요구하는 Secret 목록이 출력됩니다.
|
|
124
|
+
|
|
125
|
+
| 대상 | Secrets | Variables |
|
|
126
|
+
|---|---|---|
|
|
127
|
+
| 모든 Flutter 빌드 | `ENV_FILE` (없으면 `ENV`) | — |
|
|
128
|
+
| Android 서명 (`PLAYSTORE`·`FIREBASE`·`TEST-APK`) | `RELEASE_KEYSTORE_BASE64`, `RELEASE_KEYSTORE_PASSWORD`, `RELEASE_KEY_ALIAS`, `RELEASE_KEY_PASSWORD`, `GOOGLE_SERVICES_JSON` | — |
|
|
129
|
+
| Play Store (`PLAYSTORE`) | `GOOGLE_PLAY_SERVICE_ACCOUNT_JSON_BASE64`, `ANDROID_PACKAGE_NAME` | `ANDROID_PACKAGE_NAME`(Secret이 없을 때 대신 사용), `ANDROID_DEPLOY_MODE`(선택) |
|
|
130
|
+
| iOS (`IOS-TESTFLIGHT`·`IOS-TEST-TESTFLIGHT`) | `APP_STORE_CONNECT_API_KEY_BASE64`, `APP_STORE_CONNECT_API_KEY_ID`, `APP_STORE_CONNECT_ISSUER_ID`, `APPLE_CERTIFICATE_BASE64`, `APPLE_CERTIFICATE_PASSWORD`, `APPLE_PROVISIONING_PROFILE_BASE64`, `IOS_PROVISIONING_PROFILE_NAME`, `IOS_BUNDLE_ID`, `SECRETS_XCCONFIG`(선택) | `IOS_BUNDLE_ID`(Secret이 없을 때 대신 사용), `IOS_DEPLOY_MODE`(선택) |
|
|
131
|
+
| Firebase 배포 (`FIREBASE`·`TEST-APK`) | `FIREBASE_SERVICE_ACCOUNT_JSON_BASE64` | — |
|
|
132
|
+
| Selfhosted 배포 (`SELFHOSTED`) | `SERVER_HOST`, `SERVER_USER`, `SERVER_PASSWORD`, `DEBUG_KEYSTORE` | — |
|
|
133
|
+
|
|
134
|
+
`ANDROID_PACKAGE_NAME`은 `PLAYSTORE` 워크플로우가 fastlane에 패키지명으로 넘깁니다(`secrets` 우선, 없으면 `vars`). iOS의 `IOS_BUNDLE_ID`와 같은 플랫폼 접두사 규칙입니다.
|
|
135
|
+
|
|
136
|
+
**`ExportOptions.plist`에 채워야 할 값** — 마법사가 만든 `ios/ExportOptions.plist`에는 아래 플레이스홀더가 들어 있어 실제 값으로 바꿔야 합니다. 채우지 않고 `IOS-TESTFLIGHT`를 실행하면 `xcodebuild`의 알기 어려운 오류 대신 플레이스홀더가 남았다는 메시지로 검증 단계에서 중단되며, `--mode doctor`도 같은 항목을 WARN으로 알려줍니다.
|
|
137
|
+
|
|
138
|
+
| 플레이스홀더 | 채울 값 |
|
|
139
|
+
|---|---|
|
|
140
|
+
| `__TEAM_ID__` | Apple Developer Team ID |
|
|
141
|
+
| `__BUNDLE_ID__` | 앱 번들 ID (`IOS_BUNDLE_ID`와 같은 값) |
|
|
142
|
+
| `__PROVISIONING_PROFILE_NAME__` | 프로비저닝 프로파일 이름 (`IOS_PROVISIONING_PROFILE_NAME`과 같은 값) |
|
|
143
|
+
|
|
144
|
+
**모노레포와 `ci-gate`**
|
|
145
|
+
|
|
146
|
+
- `--paths flutter=app`처럼 Flutter 하위 폴더를 지정하면 모든 Flutter 워크플로우가 그 폴더(`FLUTTER_PROJECT_DIR`)를 기준으로 동작하고, main push로 도는 배포 워크플로우(`PLAYSTORE`·`IOS-TESTFLIGHT`·`SELFHOSTED`·`FIREBASE`)는 `app/**`가 바뀔 때만 실행됩니다(Spring 등 다른 타입과 같은 `paths` 필터).
|
|
147
|
+
- CI 워크플로우(Flutter·Go·Next·Python·React·Spring NEXUS-CI)는 `push`·`pull_request`에서 항상 실행되고, 첫 job `changes`가 변경 파일을 판별해 나머지 job을 건너뜁니다(건너뛴 job은 Success). 마지막 job `ci-gate`는 항상 실행되어 필수 job이 모두 success 또는 skipped이면 통과하고 failure·cancelled가 하나라도 있으면 실패합니다.
|
|
148
|
+
- 브랜치 보호 규칙의 required status check에는 개별 job이 아니라 **`CI Gate`(`ci-gate`) 하나만** 등록하세요. 경로 필터로 워크플로우 자체를 건너뛰면 required check가 Pending에 머물러 머지가 막히지만, job을 건너뛰는 방식은 그렇지 않기 때문입니다.
|
|
149
|
+
- 워크플로우 `paths` 필터는 태그 push에 적용되지 않습니다(GitHub 문서).
|
|
150
|
+
|
|
151
|
+
**Gemfile** — 스토어 배포 워크플로우(`PLAYSTORE`·`IOS-TESTFLIGHT`·`IOS-TEST-TESTFLIGHT`)는 사용자 `Gemfile`에 `fastlane`이 있으면 그것을 쓰고, 없으면 실행할 때 생성합니다. 마법사는 Gemfile 템플릿을 배포하지 않습니다. fastlane 공식 문서가 권장하는 대로 `Gemfile`(`gem "fastlane"`)과 `Gemfile.lock`을 저장소에 커밋해 두면 버전이 고정되어 재현성이 좋아집니다.
|
|
152
|
+
|
|
78
153
|
### 실행 로그 (`.github/.wizard/logs/`)
|
|
79
154
|
|
|
80
155
|
설치·업데이트·삭제를 실행할 때마다 `.github/.wizard/logs/<시각>-<동작>.log`에 실행 추적이 남습니다. 감지 근거, **파일별 처리 결정과 그 사유**, 치환된 값, 미치환 항목, 등록해야 하는 GitHub Secret이 시간순으로 기록되고, 파일 끝에 결과 요약이 붙습니다.
|
|
@@ -108,18 +183,23 @@ npx project-auto-wizard --mode uninstall --force # 워크플로우·스
|
|
|
108
183
|
npx project-auto-wizard --mode uninstall --force --purge-readme --purge-gitignore --purge-version # 완전 삭제
|
|
109
184
|
```
|
|
110
185
|
|
|
111
|
-
##
|
|
186
|
+
## 요약 엔진 체인
|
|
187
|
+
|
|
188
|
+
릴리스 노트는 3단 엔진 체인으로 생성됩니다. **어떤 단계가 실패해도 릴리스는 절대 막히지 않습니다.**
|
|
112
189
|
|
|
113
|
-
|
|
190
|
+
> GitHub Models는 2026-07-30에 종료되어 더 이상 사용하지 않습니다.
|
|
114
191
|
|
|
115
192
|
```mermaid
|
|
116
193
|
flowchart LR
|
|
117
|
-
B["사용자 지정 AI<br/>(AI_API_KEY)"] -->|"
|
|
118
|
-
C -->|"
|
|
194
|
+
B["사용자 지정 AI<br/>(AI_API_KEY + AI_API_BASE_URL + AI_MODEL)"] -->|"미설정/실패"| C["GitHub Copilot CLI<br/>(선택, GITHUB_TOKEN)"]
|
|
195
|
+
C -->|"꺼짐/사용 불가/실패"| D["규칙 기반 fallback<br/>(항상 성공)"]
|
|
119
196
|
```
|
|
120
197
|
|
|
121
|
-
- 기본값은
|
|
122
|
-
-
|
|
198
|
+
- 기본값은 **규칙 기반 요약**입니다. 설치 마법사에서 Copilot을 켜면(`--copilot`, `version.yml`의 `copilot_ai: true`) Actions의 `GITHUB_TOKEN` + `permissions: copilot-requests: write`로 Copilot CLI가 요약을 생성합니다 — 별도 API 키는 필요 없습니다.
|
|
199
|
+
- **Copilot은 GitHub Copilot AI Credits를 소비합니다.** 개인 저장소는 저장소 소유자의 Copilot 좌석에, 조직 저장소는 조직에 과금되며 조직은 "Allow use of Copilot CLI billed to the organization" 정책을 켜야 합니다. 사용할 수 없으면 자동으로 규칙 기반 요약으로 전환됩니다. PR에 푸시할 때마다 요약이 새로 생성되므로 그만큼 크레딧이 소비됩니다.
|
|
200
|
+
- Copilot 모델은 저비용 소형 모델(`claude-haiku-4.5`)로 고정되어 있고, 저장소 변수 `COPILOT_MODEL`로 바꿀 수 있습니다.
|
|
201
|
+
- GitHub은 Copilot CLI를 `run` 스텝에서 직접 호출하기보다 Agentic Workflows를 쓰라고 권고하지만, 이 프로젝트는 직접 호출을 택했습니다. 프롬프트 입력이 PR 제목·커밋 메시지·`git diff --stat`뿐이고, 빈 임시 디렉터리에서 shell/write/url 도구와 내장 MCP를 모두 막은 텍스트 생성 전용으로 호출하며, 포크 PR은 기존 가드로 건너뛰어 프롬프트 인젝션 위험을 낮췄기 때문입니다.
|
|
202
|
+
- `AI_API_KEY`(**Secret**)와 `AI_API_BASE_URL`·`AI_MODEL`(**Variables**)을 **모두** 설정하면 OpenAI-호환 엔드포인트(Groq, Gemini 호환 모드, Ollama 등)를 최우선으로 사용합니다. 셋 중 하나라도 없으면 이 단계는 건너뜁니다.
|
|
123
203
|
- 규칙 fallback 3단: 프로젝트 컨벤션 → Conventional Commits → 무형식 bullet. 커밋 컨벤션이 없어도 동작.
|
|
124
204
|
|
|
125
205
|
## 릴리스 흐름
|
|
@@ -129,7 +209,7 @@ flowchart LR
|
|
|
129
209
|
subgraph pr-flow ["pr-flow (기본)"]
|
|
130
210
|
D1[develop push] --> PR[develop→main 릴리스 PR]
|
|
131
211
|
PR --> V["버전 확정 (patch+1)"]
|
|
132
|
-
V --> AI[
|
|
212
|
+
V --> AI[릴리스 노트]
|
|
133
213
|
AI --> CL[CHANGELOG.json/md 갱신]
|
|
134
214
|
CL --> AM[automerge]
|
|
135
215
|
AM --> TAG["tag vX.Y.Z + GitHub Release"]
|
|
@@ -154,9 +234,14 @@ npx project-auto-wizard [옵션]
|
|
|
154
234
|
--main-branch B 릴리스 브랜치 (기본: 감지된 default branch)
|
|
155
235
|
--develop-branch B 개발 브랜치 (기본: develop)
|
|
156
236
|
--deploy-style S 서버 배포 방식: simple | nginx | traefik | none (기본: simple)
|
|
237
|
+
--flutter-env-mode M Flutter 환경변수 방식: dart-define | dotenv (신규 기본: dart-define, 저장값 없는 기존 설치는 dotenv 유지)
|
|
238
|
+
--flutter-store CSV Flutter 스토어 배포 대상: android,ios,none (미지정 시 둘 다 설치)
|
|
239
|
+
--android-deploy-mode M Play Store 배포 모드: store_only | store_prepare | store_submit (기본: store_only)
|
|
240
|
+
--ios-deploy-mode M iOS 배포 모드: store_only | store_prepare | store_submit (기본: store_only)
|
|
157
241
|
--nexus 라이브러리 publish 워크플로우 포함 (Nexus + GitHub Packages)
|
|
158
242
|
--secret-backup Secret 서버 백업 워크플로우 포함
|
|
159
243
|
--semver-auto 커밋 타입 기반 자동 major/minor/patch 승격 (기본: 사용함, --no-semver-auto로 끔)
|
|
244
|
+
--copilot Copilot으로 AI 요약 생성 (기본: 사용 안 함, GitHub Copilot AI Credits 소비, --no-copilot으로 끔)
|
|
160
245
|
--dry-run 실제 파일 변경 없이 무엇이 바뀔지만 미리 보여줌
|
|
161
246
|
--purge-readme --mode uninstall --force 시 README.md 버전 섹션도 제거
|
|
162
247
|
--purge-gitignore --mode uninstall --force 시 .gitignore 자동 추가 항목도 제거
|
|
@@ -173,8 +258,8 @@ npx project-auto-wizard --mode doctor # 환경 진단 (읽기 전용, 규칙
|
|
|
173
258
|
|
|
174
259
|
| 명령 | 내용 |
|
|
175
260
|
|---|---|
|
|
176
|
-
| `--mode status` | 설치된 버전·타입·브랜치
|
|
177
|
-
| `--mode doctor` | `version.yml` 설치 여부, `gh` CLI 설치/인증 상태, GitHub Actions workflow permissions, `WORKFLOW_PAT` secret 등록 여부, merge commit 허용 설정을 점검합니다. `gh api` 호출을 사용하므로 네트워크 접근이 발생합니다(규칙 기반 점검 — AI 진단 아님) |
|
|
261
|
+
| `--mode status` | 설치된 버전·타입·브랜치 모드·옵션값(Flutter 프로젝트면 환경변수 방식·스토어 배포 대상·배포 모드 포함)과, 설치 시점 대비 사용자가 직접 수정한 워크플로우 파일 목록을 보여줍니다. 네트워크 접근 없음(로컬 파일 비교만) |
|
|
262
|
+
| `--mode doctor` | `version.yml` 설치 여부, `gh` CLI 설치/인증 상태, GitHub Actions workflow permissions, `WORKFLOW_PAT` secret 등록 여부, merge commit 허용 설정을 점검합니다. Flutter 프로젝트에서는 고른 스토어 플랫폼의 필수 파일(`Fastfile`, `ExportOptions.plist` 등)과 `ExportOptions.plist`의 플레이스홀더 잔존 여부도 점검합니다(로컬 파일만 확인, 스토어 시크릿 등록 여부는 점검하지 않음). `gh api` 호출을 사용하므로 네트워크 접근이 발생합니다(규칙 기반 점검 — AI 진단 아님) |
|
|
178
263
|
|
|
179
264
|
`doctor`는 항목마다 **그 설정이 무엇을 담당하는지**를 라벨에 함께 표시하고, 문제가 있는 항목만 `현상 → 그대로 두면 무엇이 안 되는지 → 어디를 눌러 고치는지 → 문서 링크` 순으로 펼쳐 보여줍니다. 정상 항목은 한 줄로 압축됩니다. GitHub 설정 화면에 실제로 표시되는 문자열(`Read and write permissions` 등)은 화면에서 찾을 수 있도록 원문 그대로 출력합니다.
|
|
180
265
|
|
|
@@ -191,9 +276,10 @@ npx project-auto-wizard --mode doctor # 환경 진단 (읽기 전용, 규칙
|
|
|
191
276
|
secret이 없어도 폴백이 자동으로 이어받아 태그·Release까지 진행됩니다 — 실제 병합 후 최대 ~20초 정도 더 걸릴 뿐입니다.
|
|
192
277
|
속도를 더 원한다면 PAT을 등록할 수 있습니다 — 반드시 개인 계정이 아닌 조직 bot/machine 계정으로 발급하세요 (scopes: repo, workflow).
|
|
193
278
|
등록: 레포 Settings → Secrets and variables → Actions → New repository secret · 이름은 WORKFLOW_PAT
|
|
194
|
-
[i]
|
|
195
|
-
|
|
196
|
-
|
|
279
|
+
[i] Copilot AI 요약 — AI 릴리스 노트 생성(선택)
|
|
280
|
+
기본은 꺼져 있습니다 (version.yml의 copilot_ai: false).
|
|
281
|
+
켜면 GitHub Copilot AI Credits가 소비됩니다 — 조직은 'Allow use of Copilot CLI billed to the organization' 정책이 필요합니다.
|
|
282
|
+
꺼져 있거나 사용할 수 없으면 규칙 기반 요약으로 자동 전환되므로 그대로 두셔도 됩니다.
|
|
197
283
|
|
|
198
284
|
✓ 문제를 찾지 못했습니다.
|
|
199
285
|
```
|
|
@@ -217,7 +303,7 @@ npx project-auto-wizard --no-semver-auto # 항상 patch+1 (레거시 동작)
|
|
|
217
303
|
|
|
218
304
|
### 자체 AI PR 요약봇
|
|
219
305
|
|
|
220
|
-
상용 PR 리뷰 SaaS 없이 동작하는 자체 요약봇입니다. 릴리스 브랜치(`--main-branch`)를 대상으로 하는 PR이 열릴 때
|
|
306
|
+
상용 PR 리뷰 SaaS 없이 동작하는 자체 요약봇입니다. 릴리스 브랜치(`--main-branch`)를 대상으로 하는 PR이 열릴 때 요약 엔진 체인(기본은 규칙 기반, Copilot은 선택)으로 요약 코멘트를 자동으로 답니다.
|
|
221
307
|
|
|
222
308
|
기본 설치(pr-flow) 기준으로 일상적인 기능 PR은 `develop`을 대상으로 열리므로, 이 봇은 develop→main 릴리스 PR에서만 실제로 동작합니다 — 해당 PR에서는 `AUTO-CHANGELOG-CONTROL`이 이미 같은 엔진으로 체인지로그 요약을 생성하므로, 이 봇은 그 요약을 PR 코멘트 형태로도 남겨주는 보조 역할입니다. 릴리스 브랜치 = 개발 브랜치인 trunk-based 모드에서는 모든 PR이 곧 릴리스 대상 브랜치를 향하므로 매 PR마다 동작합니다.
|
|
223
309
|
|
|
@@ -229,7 +315,7 @@ npx project-auto-wizard --no-semver-auto # 항상 patch+1 (레거시 동작)
|
|
|
229
315
|
|---|---|
|
|
230
316
|
| **`WORKFLOW_PAT` secret** (선택 — 속도 최적화용) | 없어도 `GITHUB_TOKEN` 폴백이 automerge부터 Release 발행까지 자동으로 이어갑니다(실제 병합 후 최대 ~20초 추가). 더 빠르게 하고 싶다면 Settings → Secrets → Actions에 `WORKFLOW_PAT` (scopes: `repo`, `workflow`) 등록 — 반드시 개인 계정이 아닌 조직 bot/machine 계정으로 발급하세요 |
|
|
231
317
|
| **Workflow permissions** | Settings → Actions → Workflow permissions: **Read and write** |
|
|
232
|
-
| **
|
|
318
|
+
| **Copilot AI 요약** (선택) | 기본 꺼짐. 켜려면 마법사에서 선택하거나 `version.yml`의 `copilot_ai`를 `true`로 — AI Credits가 소비되며 조직은 "Allow use of Copilot CLI billed to the organization" 정책이 필요합니다. 사용할 수 없으면 자동으로 규칙 fallback |
|
|
233
319
|
|
|
234
320
|
## 설계 원칙
|
|
235
321
|
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "project-auto-wizard",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "One command DevOps: npx wizard that installs GitHub-native
|
|
3
|
+
"version": "0.12.0",
|
|
4
|
+
"description": "One command DevOps: npx wizard that installs GitHub-native Release Automation (optional GitHub Copilot AI summaries) into any project",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"bin": {
|
|
@@ -1 +1,24 @@
|
|
|
1
|
-
{
|
|
1
|
+
{
|
|
2
|
+
"0.10.1": [
|
|
3
|
+
{
|
|
4
|
+
"severity": "warning",
|
|
5
|
+
"title": "Flutter SELFHOSTED·TEST-APK가 flutter build를 직접 실행",
|
|
6
|
+
"message": "SELFHOSTED·TEST-APK가 fastlane build 대신 flutter build apk --release를 직접 실행하고 Ruby·fastlane 설치 스텝이 사라집니다. fastlane은 스토어 배포 3종(PLAYSTORE·IOS-TESTFLIGHT·IOS-TEST-TESTFLIGHT)에서만 사용합니다."
|
|
7
|
+
},
|
|
8
|
+
{
|
|
9
|
+
"severity": "warning",
|
|
10
|
+
"title": "Flutter 환경변수 기본 방식이 dart-define으로 변경",
|
|
11
|
+
"message": "신규 설치의 기본값이 dart-define(--dart-define-from-file)으로 바뀝니다. 기존 설치는 version.yml에 저장값이 없으면 dotenv(.env 생성)를 유지하며, 바꾸려면 마법사 '수정하기 > 환경변수 방식' 또는 --flutter-env-mode를 쓰세요."
|
|
12
|
+
},
|
|
13
|
+
{
|
|
14
|
+
"severity": "warning",
|
|
15
|
+
"title": "Flutter 워크플로우가 FLUTTER_PROJECT_DIR 기준으로 동작",
|
|
16
|
+
"message": "FIREBASE·SELFHOSTED·CI·TEST-APK·IOS-TEST-TESTFLIGHT도 PLAYSTORE처럼 Flutter 루트(FLUTTER_PROJECT_DIR)에서 동작합니다. Flutter가 하위 폴더에 있는 모노레포는 --paths flutter=<경로>로 지정하세요."
|
|
17
|
+
},
|
|
18
|
+
{
|
|
19
|
+
"severity": "warning",
|
|
20
|
+
"title": "모노레포 CI required check는 ci-gate 하나만 등록",
|
|
21
|
+
"message": "CI 워크플로우(Flutter·Go·Next·Python·React·Spring NEXUS-CI)는 항상 실행되고 변경이 없으면 job을 건너뜁니다. required check로 쓰려면 개별 job이 아닌 CI Gate(ci-gate) 하나만 등록하세요."
|
|
22
|
+
}
|
|
23
|
+
]
|
|
24
|
+
}
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
# ===================================================================
|
|
2
|
+
# 이 파일은 사용자 소유입니다. 모드별 동작을 여기서 수정하세요.
|
|
3
|
+
# ===================================================================
|
|
4
|
+
#
|
|
5
|
+
# Google Play 배포용 Fastfile 템플릿입니다.
|
|
6
|
+
# PROJECT-FLUTTER-ANDROID-PLAYSTORE-CICD 워크플로우가 이 파일을 android/fastlane/Fastfile로
|
|
7
|
+
# 복사한 뒤 `bundle exec fastlane deploy_internal`을 실행합니다.
|
|
8
|
+
# project-auto-wizard는 이 파일이 이미 있으면 덮어쓰지 않으므로 자유롭게 수정해도 됩니다.
|
|
9
|
+
#
|
|
10
|
+
# 배포 모드 (워크플로우가 DEPLOY_MODE 환경변수로 전달)
|
|
11
|
+
# ┌───────────────┬────────────┬──────────────────┬──────────────────────────────┐
|
|
12
|
+
# │ DEPLOY_MODE │ track │ release_status │ 동작 │
|
|
13
|
+
# ├───────────────┼────────────┼──────────────────┼──────────────────────────────┤
|
|
14
|
+
# │ store_only │ internal │ completed │ 내부 테스트 트랙에 업로드 │
|
|
15
|
+
# │ store_prepare │ production │ draft │ 프로덕션에 초안으로 업로드 │
|
|
16
|
+
# │ │ │ │ (Play Console에서 직접 출시) │
|
|
17
|
+
# │ store_submit │ production │ completed │ 프로덕션 심사 제출까지 진행 │
|
|
18
|
+
# └───────────────┴────────────┴──────────────────┴──────────────────────────────┘
|
|
19
|
+
# 알 수 없는 값이나 빈 값은 store_only로 처리합니다.
|
|
20
|
+
#
|
|
21
|
+
# 워크플로우가 넘기는 환경변수
|
|
22
|
+
# PACKAGE_NAME 앱 패키지명 (ANDROID_PACKAGE_NAME secrets/variables)
|
|
23
|
+
# AAB_PATH 업로드할 .aab 절대경로
|
|
24
|
+
# GOOGLE_PLAY_JSON_KEY 서비스 계정 JSON 키 파일 경로
|
|
25
|
+
# VERSION_NAME 버전 이름 (Play Console의 릴리스 이름)
|
|
26
|
+
# VERSION_CODE 버전 코드 (변경 이력 파일명과 일치)
|
|
27
|
+
# DEPLOY_MODE 위 표의 배포 모드
|
|
28
|
+
#
|
|
29
|
+
# 변경 이력은 워크플로우가 android/fastlane/metadata/android/ko-KR/changelogs/<VERSION_CODE>.txt
|
|
30
|
+
# 로 만들어 두며, upload_to_play_store가 metadata_path 아래에서 버전 코드로 찾아 업로드합니다.
|
|
31
|
+
|
|
32
|
+
default_platform(:android)
|
|
33
|
+
|
|
34
|
+
platform :android do
|
|
35
|
+
desc "AAB를 Google Play에 업로드합니다 (DEPLOY_MODE: store_only | store_prepare | store_submit)"
|
|
36
|
+
lane :deploy_internal do
|
|
37
|
+
package_name = ENV["PACKAGE_NAME"].to_s.strip
|
|
38
|
+
if package_name.empty?
|
|
39
|
+
UI.user_error!("PACKAGE_NAME이 비어 있습니다. ANDROID_PACKAGE_NAME secrets/variables를 등록하세요. (예: com.example.app)")
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
aab_path = ENV["AAB_PATH"].to_s.strip
|
|
43
|
+
json_key = ENV["GOOGLE_PLAY_JSON_KEY"].to_s.strip
|
|
44
|
+
version_name = ENV["VERSION_NAME"].to_s.strip
|
|
45
|
+
version_code = ENV["VERSION_CODE"].to_s.strip
|
|
46
|
+
if aab_path.empty? || json_key.empty? || version_name.empty? || version_code.empty?
|
|
47
|
+
UI.user_error!("AAB_PATH, GOOGLE_PLAY_JSON_KEY, VERSION_NAME, VERSION_CODE 환경변수가 모두 필요합니다. 워크플로우에서 전달하는지 확인하세요.")
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
deploy_mode = ENV["DEPLOY_MODE"].to_s.strip
|
|
51
|
+
track, release_status =
|
|
52
|
+
case deploy_mode
|
|
53
|
+
when "store_prepare"
|
|
54
|
+
["production", "draft"]
|
|
55
|
+
when "store_submit"
|
|
56
|
+
["production", "completed"]
|
|
57
|
+
when "store_only"
|
|
58
|
+
["internal", "completed"]
|
|
59
|
+
else
|
|
60
|
+
UI.important("알 수 없는 DEPLOY_MODE '#{deploy_mode}' 입니다. store_only로 처리합니다.") unless deploy_mode.empty?
|
|
61
|
+
["internal", "completed"]
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
# lane은 Fastfile이 있는 android/fastlane 디렉토리에서 실행되므로,
|
|
65
|
+
# 워크플로우가 변경 이력을 만드는 android/fastlane/metadata/android와 같은 경로를 가리킵니다.
|
|
66
|
+
metadata_path = File.expand_path("metadata/android")
|
|
67
|
+
changelog_file = File.join(metadata_path, "ko-KR", "changelogs", "#{version_code}.txt")
|
|
68
|
+
UI.important("변경 이력 파일이 없습니다: #{changelog_file}") unless File.exist?(changelog_file)
|
|
69
|
+
|
|
70
|
+
UI.message("Play 배포: DEPLOY_MODE=#{deploy_mode.inspect} → track=#{track}, release_status=#{release_status}")
|
|
71
|
+
|
|
72
|
+
upload_to_play_store(
|
|
73
|
+
package_name: package_name,
|
|
74
|
+
json_key: json_key,
|
|
75
|
+
aab: aab_path,
|
|
76
|
+
track: track,
|
|
77
|
+
release_status: release_status,
|
|
78
|
+
version_name: version_name,
|
|
79
|
+
metadata_path: metadata_path,
|
|
80
|
+
skip_upload_apk: true,
|
|
81
|
+
skip_upload_metadata: true,
|
|
82
|
+
skip_upload_images: true,
|
|
83
|
+
skip_upload_screenshots: true
|
|
84
|
+
)
|
|
85
|
+
end
|
|
86
|
+
end
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
|
2
|
+
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
3
|
+
<!--
|
|
4
|
+
이 파일은 사용자 소유입니다. 밑줄 두 개로 감싼 자리표시자 3곳을 실제 값으로 바꾼 뒤 커밋하세요.
|
|
5
|
+
|
|
6
|
+
PROJECT-FLUTTER-IOS-TESTFLIGHT / PROJECT-FLUTTER-IOS-TEST-TESTFLIGHT 워크플로우가
|
|
7
|
+
xcodebuild -exportArchive -exportOptionsPlist ExportOptions.plist 로 IPA를 만들 때 사용합니다.
|
|
8
|
+
자리표시자가 남아 있으면 워크플로우가 중단됩니다.
|
|
9
|
+
(이 주석에는 자리표시자를 적지 않았습니다. 값을 채운 뒤에도 남아 있는지 검사하기 때문입니다.)
|
|
10
|
+
|
|
11
|
+
teamID Apple Developer Team ID (10자리, developer.apple.com 계정의 Membership)
|
|
12
|
+
provisioningProfiles 키는 앱 번들 ID (예: com.example.app),
|
|
13
|
+
값은 프로비저닝 프로파일 이름 (GitHub Secret IOS_PROVISIONING_PROFILE_NAME 값과 같게)
|
|
14
|
+
-->
|
|
15
|
+
<plist version="1.0">
|
|
16
|
+
<dict>
|
|
17
|
+
<key>method</key>
|
|
18
|
+
<string>app-store-connect</string>
|
|
19
|
+
<key>teamID</key>
|
|
20
|
+
<string>__TEAM_ID__</string>
|
|
21
|
+
<key>signingStyle</key>
|
|
22
|
+
<string>manual</string>
|
|
23
|
+
<key>signingCertificate</key>
|
|
24
|
+
<string>Apple Distribution</string>
|
|
25
|
+
<key>provisioningProfiles</key>
|
|
26
|
+
<dict>
|
|
27
|
+
<key>__BUNDLE_ID__</key>
|
|
28
|
+
<string>__PROVISIONING_PROFILE_NAME__</string>
|
|
29
|
+
</dict>
|
|
30
|
+
</dict>
|
|
31
|
+
</plist>
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
# ===================================================================
|
|
2
|
+
# 이 파일은 사용자 소유입니다. 모드별 동작을 여기서 수정하세요.
|
|
3
|
+
# ===================================================================
|
|
4
|
+
#
|
|
5
|
+
# iOS 배포용 Fastfile 템플릿입니다. Flutter 워크플로우가 IPA를 만든 뒤
|
|
6
|
+
# `cd ios && bundle exec fastlane <lane>`으로 실행합니다.
|
|
7
|
+
# project-auto-wizard는 이 파일이 이미 있으면 덮어쓰지 않으므로 자유롭게 수정해도 됩니다.
|
|
8
|
+
#
|
|
9
|
+
# lane
|
|
10
|
+
# deploy PROJECT-FLUTTER-IOS-TESTFLIGHT 워크플로우 (DEPLOY_MODE에 따라 분기)
|
|
11
|
+
# upload_testflight PROJECT-FLUTTER-IOS-TEST-TESTFLIGHT 워크플로우 (IPA를 TestFlight에 올리기만 함)
|
|
12
|
+
#
|
|
13
|
+
# deploy lane의 배포 모드 (워크플로우가 DEPLOY_MODE 환경변수로 전달)
|
|
14
|
+
# ┌───────────────┬──────────────────────────────────────────────────────────────┐
|
|
15
|
+
# │ DEPLOY_MODE │ 동작 │
|
|
16
|
+
# ├───────────────┼──────────────────────────────────────────────────────────────┤
|
|
17
|
+
# │ store_only │ TestFlight 업로드까지 (upload_to_testflight) │
|
|
18
|
+
# │ store_prepare │ TestFlight 업로드 후, App Store 버전과 메타데이터를 준비 │
|
|
19
|
+
# │ │ (upload_to_app_store, submit_for_review: false). 빌드는 이 │
|
|
20
|
+
# │ │ 단계에서 버전에 연결되지 않고, store_submit이 build_number │
|
|
21
|
+
# │ │ 로 다시 호출될 때 한번에 연결·제출됩니다. │
|
|
22
|
+
# │ store_submit │ store_prepare와 같은 호출이고 심사 제출까지 진행 │
|
|
23
|
+
# │ │ (submit_for_review: true, 이때 빌드가 버전에 연결됩니다) │
|
|
24
|
+
# └───────────────┴──────────────────────────────────────────────────────────────┘
|
|
25
|
+
# 알 수 없는 값이나 빈 값은 store_only로 처리합니다.
|
|
26
|
+
#
|
|
27
|
+
# 워크플로우가 넘기는 환경변수
|
|
28
|
+
# APP_STORE_CONNECT_API_KEY_ID App Store Connect API Key ID
|
|
29
|
+
# APP_STORE_CONNECT_ISSUER_ID App Store Connect Issuer ID
|
|
30
|
+
# API_KEY_PATH AuthKey_XXXX.p8 파일 경로
|
|
31
|
+
# IPA_PATH 업로드할 .ipa 절대경로
|
|
32
|
+
# RELEASE_NOTES TestFlight "What to Test" 문구 (changelog)
|
|
33
|
+
# APP_IDENTIFIER 번들 ID (upload_testflight lane은 없으면 fastlane이 추론하지만,
|
|
34
|
+
# deploy lane의 store_prepare/store_submit은 필수입니다)
|
|
35
|
+
# DEPLOY_MODE 위 표의 배포 모드 (deploy lane만)
|
|
36
|
+
# APP_VERSION, BUILD_NUMBER 앱 버전, 빌드 번호 (deploy lane만)
|
|
37
|
+
# SKIP_WAITING_FOR_BUILD_PROCESSING "true"면 빌드 처리 완료를 기다리지 않음 (store_only만 적용)
|
|
38
|
+
#
|
|
39
|
+
# 참고
|
|
40
|
+
# - store_prepare/store_submit은 App Store 버전에 빌드를 연결해야 하므로 빌드 처리가 끝날 때까지
|
|
41
|
+
# 기다립니다 (SKIP_WAITING_FOR_BUILD_PROCESSING을 무시).
|
|
42
|
+
# - 스크린샷은 올리지 않습니다. 메타데이터는 ios/fastlane/metadata/ 아래에 파일이 있을 때만 올립니다.
|
|
43
|
+
# 심사 제출 시 "What's New" 등 필수 항목은 이 폴더나 App Store Connect에서 미리 채워 두세요.
|
|
44
|
+
# - store_submit에서 수출 규정 오류가 나면 Info.plist에 ITSAppUsesNonExemptEncryption을 지정하거나
|
|
45
|
+
# upload_to_app_store에 submission_information을 추가하세요.
|
|
46
|
+
# - store_submit은 심사 승인 후 자동 출시하지 않습니다 (automatic_release: false).
|
|
47
|
+
|
|
48
|
+
default_platform(:ios)
|
|
49
|
+
|
|
50
|
+
def require_env(name)
|
|
51
|
+
value = ENV[name].to_s.strip
|
|
52
|
+
UI.user_error!("#{name} 환경변수가 비어 있습니다. 워크플로우에서 값을 전달하는지 확인하세요.") if value.empty?
|
|
53
|
+
value
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def load_api_key
|
|
57
|
+
app_store_connect_api_key(
|
|
58
|
+
key_id: require_env("APP_STORE_CONNECT_API_KEY_ID"),
|
|
59
|
+
issuer_id: require_env("APP_STORE_CONNECT_ISSUER_ID"),
|
|
60
|
+
key_filepath: require_env("API_KEY_PATH")
|
|
61
|
+
)
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
# APP_IDENTIFIER가 있을 때만 옵션에 포함해, 없으면 fastlane 기본 추론에 맡깁니다.
|
|
65
|
+
def app_identifier_option
|
|
66
|
+
app_identifier = ENV["APP_IDENTIFIER"].to_s.strip
|
|
67
|
+
app_identifier.empty? ? {} : { app_identifier: app_identifier }
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def upload_ipa_to_testflight(api_key, skip_waiting_for_build_processing)
|
|
71
|
+
options = {
|
|
72
|
+
api_key: api_key,
|
|
73
|
+
ipa: require_env("IPA_PATH"),
|
|
74
|
+
skip_waiting_for_build_processing: skip_waiting_for_build_processing
|
|
75
|
+
}
|
|
76
|
+
release_notes = ENV["RELEASE_NOTES"].to_s.strip
|
|
77
|
+
options[:changelog] = release_notes unless release_notes.empty?
|
|
78
|
+
upload_to_testflight(**options, **app_identifier_option)
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
platform :ios do
|
|
82
|
+
desc "IPA를 TestFlight에 업로드합니다 (테스트 빌드용)"
|
|
83
|
+
lane :upload_testflight do
|
|
84
|
+
upload_ipa_to_testflight(load_api_key, false)
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
desc "IPA를 업로드하고 DEPLOY_MODE에 따라 TestFlight / 심사 준비 / 심사 제출까지 진행합니다"
|
|
88
|
+
lane :deploy do
|
|
89
|
+
api_key = load_api_key
|
|
90
|
+
deploy_mode = ENV["DEPLOY_MODE"].to_s.strip
|
|
91
|
+
|
|
92
|
+
case deploy_mode
|
|
93
|
+
when "store_prepare", "store_submit"
|
|
94
|
+
UI.message("DEPLOY_MODE=#{deploy_mode}: 빌드 처리 완료까지 기다린 뒤 App Store 버전을 준비합니다.")
|
|
95
|
+
upload_ipa_to_testflight(api_key, false)
|
|
96
|
+
|
|
97
|
+
upload_to_app_store(
|
|
98
|
+
api_key: api_key,
|
|
99
|
+
app_identifier: require_env("APP_IDENTIFIER"),
|
|
100
|
+
app_version: require_env("APP_VERSION"),
|
|
101
|
+
build_number: require_env("BUILD_NUMBER"),
|
|
102
|
+
skip_binary_upload: true,
|
|
103
|
+
skip_screenshots: true,
|
|
104
|
+
skip_metadata: Dir.glob("metadata/*").empty?,
|
|
105
|
+
submit_for_review: deploy_mode == "store_submit",
|
|
106
|
+
automatic_release: false,
|
|
107
|
+
force: true,
|
|
108
|
+
precheck_include_in_app_purchases: false
|
|
109
|
+
)
|
|
110
|
+
when "store_only"
|
|
111
|
+
upload_ipa_to_testflight(api_key, ENV["SKIP_WAITING_FOR_BUILD_PROCESSING"].to_s.strip.downcase == "true")
|
|
112
|
+
else
|
|
113
|
+
UI.important("알 수 없는 DEPLOY_MODE '#{deploy_mode}' 입니다. store_only로 처리합니다.") unless deploy_mode.empty?
|
|
114
|
+
upload_ipa_to_testflight(api_key, ENV["SKIP_WAITING_FOR_BUILD_PROCESSING"].to_s.strip.downcase == "true")
|
|
115
|
+
end
|
|
116
|
+
end
|
|
117
|
+
end
|
|
Binary file
|
|
Binary file
|
|
Binary file
|