project-auto-wizard 0.1.25 → 0.1.32
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 +1 -1
- package/package.json +1 -1
- package/payload/scripts/__pycache__/changelog_manager.cpython-314.pyc +0 -0
- package/payload/scripts/__pycache__/version_manager.cpython-314.pyc +0 -0
- package/payload/scripts/changelog_manager.py +2 -8
- package/payload/scripts/version_manager.py +24 -10
- package/payload/version.yml.template +1 -2
- package/payload/workflows/common/PROJECT-COMMON-AUTO-CHANGELOG-CONTROL.yaml +2 -4
- package/payload/workflows/common/PROJECT-COMMON-RELEASE-PUBLISH.yaml +2 -4
- package/payload/workflows/common/PROJECT-COMMON-VERSION-CONTROL.yaml +4 -9
- package/payload/workflows/flutter/PROJECT-FLUTTER-ANDROID-FIREBASE-CICD.yaml +13 -2
- package/payload/workflows/flutter/PROJECT-FLUTTER-ANDROID-PLAYSTORE-CICD.yaml +24 -10
- package/payload/workflows/flutter/PROJECT-FLUTTER-ANDROID-SELFHOSTED-CICD.yaml +3 -0
- package/payload/workflows/flutter/PROJECT-FLUTTER-ANDROID-TEST-APK.yaml +29 -26
- package/payload/workflows/flutter/PROJECT-FLUTTER-CI.yaml +29 -3
- package/payload/workflows/flutter/PROJECT-FLUTTER-IOS-TEST-TESTFLIGHT.yaml +21 -17
- package/payload/workflows/flutter/PROJECT-FLUTTER-IOS-TESTFLIGHT.yaml +22 -11
- package/src/commands/interactive.js +4 -3
- package/src/core/detect-fs.js +8 -1
- package/src/core/detect.js +39 -0
- package/src/core/version-yml.js +6 -4
- package/src/index.js +3 -3
- package/src/ui/summary.js +5 -1
package/README.md
CHANGED
|
@@ -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.1.
|
|
20
|
+
## 최신 버전 : v0.1.30 (2026-08-08)
|
|
21
21
|
|
|
22
22
|
[전체 버전 기록 보기](CHANGELOG.md)
|
|
23
23
|
|
package/package.json
CHANGED
|
Binary file
|
|
Binary file
|
|
@@ -433,12 +433,9 @@ def cmd_classify_bump(commits_file: str) -> int:
|
|
|
433
433
|
def cmd_update_from_summary() -> int:
|
|
434
434
|
"""pr_body.md에서 Markdown을 파싱하여 CHANGELOG.json 갱신."""
|
|
435
435
|
version = os.environ.get('VERSION')
|
|
436
|
-
|
|
437
|
-
# 멀티타입 — PROJECT_TYPES(csv) env가 있으면 배열로, 없으면 단수 키 fallback
|
|
436
|
+
# PROJECT_TYPES(csv)가 유일한 입력 — 단수 PROJECT_TYPE 폴백은 제거됐다 (issue #62)
|
|
438
437
|
project_types_csv = os.environ.get('PROJECT_TYPES', '')
|
|
439
438
|
project_types = [t.strip() for t in project_types_csv.split(',') if t.strip()]
|
|
440
|
-
if not project_types and project_type:
|
|
441
|
-
project_types = [project_type]
|
|
442
439
|
today = os.environ.get('TODAY')
|
|
443
440
|
pr_number_raw = os.environ.get('PR_NUMBER')
|
|
444
441
|
timestamp = os.environ.get('TIMESTAMP')
|
|
@@ -482,8 +479,7 @@ def cmd_update_from_summary() -> int:
|
|
|
482
479
|
# 릴리즈 데이터 생성
|
|
483
480
|
new_release = {
|
|
484
481
|
"version": version,
|
|
485
|
-
"
|
|
486
|
-
"project_types": project_types, # 신규 멀티타입 배열
|
|
482
|
+
"project_types": project_types,
|
|
487
483
|
"date": today,
|
|
488
484
|
"pr_number": pr_number,
|
|
489
485
|
"raw_summary": raw_summary,
|
|
@@ -510,7 +506,6 @@ def cmd_update_from_summary() -> int:
|
|
|
510
506
|
"metadata": {
|
|
511
507
|
"lastUpdated": timestamp,
|
|
512
508
|
"currentVersion": version,
|
|
513
|
-
"projectType": project_type,
|
|
514
509
|
"projectTypes": project_types,
|
|
515
510
|
"totalReleases": 0,
|
|
516
511
|
},
|
|
@@ -525,7 +520,6 @@ def cmd_update_from_summary() -> int:
|
|
|
525
520
|
|
|
526
521
|
changelog_data["metadata"]["lastUpdated"] = timestamp
|
|
527
522
|
changelog_data["metadata"]["currentVersion"] = version
|
|
528
|
-
changelog_data["metadata"]["projectType"] = project_type
|
|
529
523
|
changelog_data["metadata"]["projectTypes"] = project_types
|
|
530
524
|
changelog_data["metadata"]["totalReleases"] = len(changelog_data.get("releases", [])) + 1
|
|
531
525
|
changelog_data.setdefault("releases", []).insert(0, new_release)
|
|
@@ -146,21 +146,22 @@ def get_current_version():
|
|
|
146
146
|
return read_scalar_key("version", "0.0.0")
|
|
147
147
|
|
|
148
148
|
|
|
149
|
-
def get_project_type():
|
|
150
|
-
return read_scalar_key("project_type", "basic")
|
|
151
|
-
|
|
152
|
-
|
|
153
149
|
def get_project_types_csv():
|
|
154
150
|
"""Return project_types as a list. Supports both:
|
|
155
151
|
project_types: ["a", "b"]
|
|
156
152
|
project_types:
|
|
157
153
|
- "a"
|
|
158
154
|
- "b"
|
|
159
|
-
Returns [] if key absent
|
|
155
|
+
Returns [] if the key is absent — project_types is the single source of
|
|
156
|
+
truth (issue #62), so callers must treat [] as a hard error rather than
|
|
157
|
+
falling back to a singular key."""
|
|
160
158
|
text = read_text()
|
|
161
159
|
|
|
162
|
-
# Inline array form: project_types: ["a", "b"]
|
|
163
|
-
|
|
160
|
+
# Inline array form: project_types: ["a", "b"] # trailing comment allowed
|
|
161
|
+
# The template always appends "# first entry is primary", so anchoring at
|
|
162
|
+
# end-of-line made this branch never match — every install silently fell
|
|
163
|
+
# through to the singular key instead (issue #62).
|
|
164
|
+
m = re.search(r'^project_types:[ \t]*\[(.*?)\][ \t]*(?:#.*)?$', text, re.MULTILINE)
|
|
164
165
|
if m:
|
|
165
166
|
inner = m.group(1)
|
|
166
167
|
items = re.findall(r'"([^"]*)"|\'([^\']*)\'', inner)
|
|
@@ -174,7 +175,8 @@ def get_project_types_csv():
|
|
|
174
175
|
m = re.search(r'^project_types:[ \t]*\n((?:[ \t]+-[ \t]*.*\n?)+)', text, re.MULTILINE)
|
|
175
176
|
if m:
|
|
176
177
|
block = m.group(1)
|
|
177
|
-
|
|
178
|
+
# trailing comments are allowed on list items too
|
|
179
|
+
types = re.findall(r'-[ \t]*["\']?([^"\'#\n]+?)["\']?[ \t]*(?:#.*)?$', block, re.MULTILINE)
|
|
178
180
|
return [t.strip() for t in types if t.strip()]
|
|
179
181
|
|
|
180
182
|
return []
|
|
@@ -221,6 +223,13 @@ def get_version_code():
|
|
|
221
223
|
|
|
222
224
|
|
|
223
225
|
def set_version_code(new_code):
|
|
226
|
+
current = read_scalar_key("version_code", None)
|
|
227
|
+
if current not in (None, "", "null"):
|
|
228
|
+
try:
|
|
229
|
+
if int(new_code) < int(current):
|
|
230
|
+
log(f"WARNING: version_code {new_code} is lower than current {current} — writing anyway (regression?)")
|
|
231
|
+
except ValueError:
|
|
232
|
+
pass
|
|
224
233
|
text = read_text()
|
|
225
234
|
pattern = re.compile(r'^version_code:[ \t]*.*$', re.MULTILINE)
|
|
226
235
|
replacement = f'version_code: {new_code} # app build number'
|
|
@@ -423,7 +432,9 @@ def sync_for_type(project_type, new_version, version_code_getter):
|
|
|
423
432
|
def sync_all_project_files(new_version):
|
|
424
433
|
types = get_project_types_csv()
|
|
425
434
|
if not types:
|
|
426
|
-
|
|
435
|
+
# No silent fallback: an unreadable project_types used to degrade to
|
|
436
|
+
# "basic" and skip every sync without a word (issue #62).
|
|
437
|
+
raise SystemExit("ERROR: version.yml has no readable project_types — cannot sync project files")
|
|
427
438
|
for t in types:
|
|
428
439
|
sync_for_type(t, new_version, get_version_code)
|
|
429
440
|
|
|
@@ -503,7 +514,10 @@ def get_project_file_version(project_type):
|
|
|
503
514
|
|
|
504
515
|
def sync_versions():
|
|
505
516
|
yml_version = get_current_version()
|
|
506
|
-
|
|
517
|
+
types = get_project_types_csv()
|
|
518
|
+
if not types:
|
|
519
|
+
raise SystemExit("ERROR: version.yml has no readable project_types — cannot sync versions")
|
|
520
|
+
primary_type = types[0]
|
|
507
521
|
project_version = get_project_file_version(primary_type)
|
|
508
522
|
|
|
509
523
|
log("Version sync check")
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
# Supported project types: spring, flutter, next, react, react-native,
|
|
11
11
|
# react-native-expo, node, python, basic
|
|
12
12
|
# Multi-type projects: list every type in the project_types array
|
|
13
|
-
# ->
|
|
13
|
+
# -> the first entry is the primary type (project_types is the single source of truth)
|
|
14
14
|
# Monorepo paths: project_paths maps each type to its subdir relative to the repo root
|
|
15
15
|
# (e.g., flutter: "app", react: "client"). Omitted -> repo root.
|
|
16
16
|
#
|
|
@@ -27,7 +27,6 @@
|
|
|
27
27
|
version: "{{VERSION}}"
|
|
28
28
|
version_code: {{VERSION_CODE}} # app build number
|
|
29
29
|
project_types: {{PROJECT_TYPES}} # first entry is primary
|
|
30
|
-
project_type: "{{PROJECT_TYPE}}" # auto-mirror of project_types[0] — do not edit directly
|
|
31
30
|
{{PROJECT_PATHS}}
|
|
32
31
|
metadata:
|
|
33
32
|
last_updated: "{{NOW}}"
|
|
@@ -156,13 +156,11 @@ jobs:
|
|
|
156
156
|
run: |
|
|
157
157
|
# note: `grep | sed || echo default` never fires the default (the
|
|
158
158
|
# pipeline exits with sed's 0) — default via ${var:-} instead
|
|
159
|
-
PROJECT_TYPE=$(grep "^project_type:" version.yml 2>/dev/null | sed 's/project_type: *"\([^"]*\)".*/\1/')
|
|
160
|
-
PROJECT_TYPE=${PROJECT_TYPE:-unknown}
|
|
161
159
|
PROJECT_TYPES=$(grep "^project_types:" version.yml 2>/dev/null | sed -E 's/.*\[([^]]*)\].*/\1/' | tr -d '" ')
|
|
162
|
-
PROJECT_TYPES=${PROJECT_TYPES
|
|
160
|
+
PROJECT_TYPES=${PROJECT_TYPES:-unknown}
|
|
163
161
|
|
|
164
162
|
export VERSION="${{ steps.bump.outputs.new_version }}"
|
|
165
|
-
export
|
|
163
|
+
export PROJECT_TYPES
|
|
166
164
|
export TODAY=$(date '+%Y-%m-%d')
|
|
167
165
|
export PR_NUMBER="${{ github.event.pull_request.number }}"
|
|
168
166
|
export TIMESTAMP=$(date '+%Y-%m-%dT%H:%M:%SZ')
|
|
@@ -192,13 +192,11 @@ jobs:
|
|
|
192
192
|
|
|
193
193
|
# note: `grep | sed || echo default` never fires the default (the
|
|
194
194
|
# pipeline exits with sed's 0) — default via ${var:-} instead
|
|
195
|
-
PROJECT_TYPE=$(grep "^project_type:" version.yml 2>/dev/null | sed 's/project_type: *"\([^"]*\)".*/\1/')
|
|
196
|
-
PROJECT_TYPE=${PROJECT_TYPE:-unknown}
|
|
197
195
|
PROJECT_TYPES=$(grep "^project_types:" version.yml 2>/dev/null | sed -E 's/.*\[([^]]*)\].*/\1/' | tr -d '" ')
|
|
198
|
-
PROJECT_TYPES=${PROJECT_TYPES
|
|
196
|
+
PROJECT_TYPES=${PROJECT_TYPES:-unknown}
|
|
199
197
|
|
|
200
198
|
export VERSION="$NEW_VERSION"
|
|
201
|
-
export
|
|
199
|
+
export PROJECT_TYPES
|
|
202
200
|
export TODAY=$(date '+%Y-%m-%d')
|
|
203
201
|
export TIMESTAMP=$(date '+%Y-%m-%dT%H:%M:%SZ')
|
|
204
202
|
|
|
@@ -114,21 +114,16 @@ jobs:
|
|
|
114
114
|
echo "new_version=$NEW_VERSION" >> $GITHUB_OUTPUT
|
|
115
115
|
echo "new version: $NEW_VERSION"
|
|
116
116
|
|
|
117
|
-
- name: Read project
|
|
117
|
+
- name: Read project types
|
|
118
118
|
id: project_info
|
|
119
119
|
if: steps.release_guard.outputs.skip != 'true'
|
|
120
120
|
run: |
|
|
121
121
|
if [ -f "version.yml" ]; then
|
|
122
|
-
|
|
123
|
-
PROJECT_TYPES=$
|
|
124
|
-
if [ -z "$PROJECT_TYPES" ]; then
|
|
125
|
-
PROJECT_TYPES="$PROJECT_TYPE"
|
|
126
|
-
fi
|
|
127
|
-
echo "project_type=$PROJECT_TYPE" >> $GITHUB_OUTPUT
|
|
122
|
+
PROJECT_TYPES=$(grep "^project_types:" version.yml 2>/dev/null | sed -E 's/.*\[([^]]*)\].*/\1/' | tr -d '" ')
|
|
123
|
+
PROJECT_TYPES=${PROJECT_TYPES:-unknown}
|
|
128
124
|
echo "project_types=$PROJECT_TYPES" >> $GITHUB_OUTPUT
|
|
129
|
-
echo "project
|
|
125
|
+
echo "project types: $PROJECT_TYPES"
|
|
130
126
|
else
|
|
131
|
-
echo "project_type=unknown" >> $GITHUB_OUTPUT
|
|
132
127
|
echo "project_types=unknown" >> $GITHUB_OUTPUT
|
|
133
128
|
echo "version.yml not found"
|
|
134
129
|
fi
|
|
@@ -112,7 +112,11 @@ jobs:
|
|
|
112
112
|
restore-keys: ${{ runner.os }}-flutter-pub-
|
|
113
113
|
|
|
114
114
|
- name: Install dependencies
|
|
115
|
-
run:
|
|
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:
|
|
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
|
-
|
|
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.
|
|
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:
|
|
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.
|
|
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:
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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가
|
|
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)"
|
|
@@ -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
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
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 --
|
|
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:
|
|
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
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
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
|
-
|
|
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.
|
|
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:
|
|
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.
|
|
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:
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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/${
|
|
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가
|
|
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
|
});
|
package/src/core/detect-fs.js
CHANGED
|
@@ -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"]);
|
package/src/core/detect.js
CHANGED
|
@@ -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/core/version-yml.js
CHANGED
|
@@ -5,6 +5,9 @@ import { escapeYamlDoubleQuoted } from "./wizard-env.js";
|
|
|
5
5
|
// 레이아웃 단일 진실 = payload/version.yml.template (호출부가 templateText로 주입).
|
|
6
6
|
|
|
7
7
|
// version.yml.template이 아는 최상위 키 — 이 밖의 최상위 키는 사용자가 직접 추가한 것으로 간주한다.
|
|
8
|
+
// "project_type"(단수)은 더 이상 렌더하지 않는 레거시 키지만 이 집합에는 남겨둔다 (issue #62):
|
|
9
|
+
// 빼면 기존 파일의 단수 줄이 "사용자가 추가한 필드"로 오인돼 재생성 때 되살아난다. 아는 키로
|
|
10
|
+
// 둬야 재통합 시 흡수되어 사라진다.
|
|
8
11
|
const KNOWN_TOP_LEVEL_KEYS = new Set([
|
|
9
12
|
"version", "version_code", "project_types", "project_type", "project_paths", "metadata", "deploy",
|
|
10
13
|
]);
|
|
@@ -147,7 +150,7 @@ export function parseTemplateBranches(content) {
|
|
|
147
150
|
}
|
|
148
151
|
|
|
149
152
|
// version.yml 전체 생성 — payload/version.yml.template 렌더링.
|
|
150
|
-
// opts: { templateText, version, types:[],
|
|
153
|
+
// opts: { templateText, version, types:[], paths:Map, pathMarkers?:Map,
|
|
151
154
|
// branch, branches?, versionCode, now, today, templateOptions?, deployValues?,
|
|
152
155
|
// extraTopLevel?:string[] } ← 기존 version.yml의 알려지지 않은 최상위 필드 보존 (issue #20 M8)
|
|
153
156
|
// templateText = payload/version.yml.template 원문 (readVersionYmlTemplate — 필수)
|
|
@@ -156,13 +159,12 @@ export function parseTemplateBranches(content) {
|
|
|
156
159
|
// pathMarkers = Map<type, markerFilename> (project_paths 주석용)
|
|
157
160
|
// templateOptions = { templateVersion, includeNexus, includeSecretBackup, optionsDate }
|
|
158
161
|
export function buildVersionYml({
|
|
159
|
-
templateText, version, types = [],
|
|
162
|
+
templateText, version, types = [], paths = new Map(), pathMarkers = new Map(),
|
|
160
163
|
branch = "main", branches = null, versionCode = 1, now, today,
|
|
161
164
|
templateOptions = null, deployValues = new Map(), extraTopLevel = [],
|
|
162
165
|
}) {
|
|
163
166
|
if (!templateText) throw new Error("version.yml.template 원문이 필요합니다 (payload/version.yml.template 누락?)");
|
|
164
167
|
const typesJson = types.length ? `[${types.map((t) => `"${t}"`).join(", ")}]` : `["basic"]`;
|
|
165
|
-
const primary = primaryType || types[0] || "basic";
|
|
166
168
|
const b = branches || { main: branch || "main", develop: "develop", mode: "pr-flow" };
|
|
167
169
|
const {
|
|
168
170
|
templateVersion = "unknown", includeNexus = false, includeSecretBackup = false,
|
|
@@ -197,7 +199,7 @@ export function buildVersionYml({
|
|
|
197
199
|
|
|
198
200
|
const scalars = {
|
|
199
201
|
VERSION: version, VERSION_CODE: String(versionCode),
|
|
200
|
-
PROJECT_TYPES: typesJson,
|
|
202
|
+
PROJECT_TYPES: typesJson,
|
|
201
203
|
NOW: now, TODAY: today || optionsDate, DEFAULT_BRANCH: branch,
|
|
202
204
|
TEMPLATE_VERSION: templateVersion,
|
|
203
205
|
MAIN_BRANCH: b.main, DEVELOP_BRANCH: b.develop, BRANCH_MODE: b.mode,
|
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; // 기존 빌드번호
|
|
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("추가된 워크플로우:");
|