project-auto-wizard 0.9.1 → 0.11.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 +83 -4
- package/package.json +1 -1
- 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 +2 -2
- package/payload/scripts/version_manager.py +2 -2
- package/payload/version.yml.template +1 -0
- package/payload/workflows/common/secret-backup/PROJECT-COMMON-SECRET-FILE-UPLOAD.yaml +12 -12
- 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 +33 -46
- package/payload/workflows/flutter/{PROJECT-FLUTTER-SUH-LAB-APP-BUILD-TRIGGER.yaml → PROJECT-FLUTTER-APP-BUILD-TRIGGER.yaml} +38 -38
- package/payload/workflows/flutter/PROJECT-FLUTTER-CI.yaml +123 -32
- package/payload/workflows/flutter/PROJECT-FLUTTER-IOS-TEST-TESTFLIGHT.yaml +57 -42
- package/payload/workflows/flutter/PROJECT-FLUTTER-IOS-TESTFLIGHT.yaml +61 -40
- package/payload/workflows/go/PROJECT-GO-CI.yaml +47 -0
- package/payload/workflows/go/PROJECT-GO-PR-PREVIEW.yaml +40 -40
- package/payload/workflows/next/PROJECT-NEXT-CI.yaml +47 -0
- package/payload/workflows/python/PROJECT-PYTHON-CI.yaml +47 -0
- package/payload/workflows/python/PROJECT-PYTHON-PR-PREVIEW.yaml +40 -40
- 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/payload/workflows/spring/server-deploy/PROJECT-SPRING-PR-PREVIEW.yaml +39 -39
- package/src/cli/args.js +37 -0
- package/src/cli/help.js +4 -0
- package/src/commands/doctor.js +55 -2
- 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 +51 -5
- package/src/commands/status.js +20 -2
- package/src/context.js +6 -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 +2 -2
- package/src/core/version-yml.js +40 -5
- package/src/core/wizard-env.js +26 -4
- package/src/index.js +32 -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 +24 -5
|
@@ -26,11 +26,39 @@ permissions:
|
|
|
26
26
|
|
|
27
27
|
env:
|
|
28
28
|
JAVA_VERSION: "__JAVA_VERSION__" # @wizard ask:@jdk
|
|
29
|
+
# 모노레포에서 이 프로젝트가 있는 하위 경로 ('.'이면 항상 검증)
|
|
30
|
+
PROJECT_PATH: "." # @wizard auto:project-path
|
|
29
31
|
|
|
30
32
|
jobs:
|
|
33
|
+
# ===================================================================
|
|
34
|
+
# 변경 감지 (모노레포에서 이 프로젝트 경로가 바뀌었는지 판별)
|
|
35
|
+
# ===================================================================
|
|
36
|
+
changes:
|
|
37
|
+
name: 변경 감지
|
|
38
|
+
runs-on: ubuntu-latest
|
|
39
|
+
permissions:
|
|
40
|
+
contents: read
|
|
41
|
+
pull-requests: read
|
|
42
|
+
outputs:
|
|
43
|
+
project: ${{ steps.filter.outputs.project }}
|
|
44
|
+
steps:
|
|
45
|
+
- name: Checkout repository
|
|
46
|
+
uses: actions/checkout@v7
|
|
47
|
+
|
|
48
|
+
- name: 프로젝트 경로 변경 여부 판별
|
|
49
|
+
id: filter
|
|
50
|
+
if: ${{ github.event_name != 'workflow_dispatch' }}
|
|
51
|
+
uses: dorny/paths-filter@v4
|
|
52
|
+
with:
|
|
53
|
+
filters: |
|
|
54
|
+
project:
|
|
55
|
+
- '${{ env.PROJECT_PATH == '.' && '**' || format('{0}/**', env.PROJECT_PATH) }}'
|
|
56
|
+
|
|
31
57
|
build-check:
|
|
32
58
|
name: Build Verification
|
|
33
59
|
runs-on: ubuntu-latest
|
|
60
|
+
needs: changes
|
|
61
|
+
if: ${{ github.event_name == 'workflow_dispatch' || needs.changes.outputs.project == 'true' }}
|
|
34
62
|
|
|
35
63
|
steps:
|
|
36
64
|
- name: Checkout Repository
|
|
@@ -318,3 +346,22 @@ jobs:
|
|
|
318
346
|
run: |
|
|
319
347
|
echo "❌ 빌드가 실패했습니다. 위의 에러 메시지를 확인해주세요."
|
|
320
348
|
exit 1
|
|
349
|
+
|
|
350
|
+
# ===================================================================
|
|
351
|
+
# CI Gate (required check로 등록할 단일 job)
|
|
352
|
+
# ===================================================================
|
|
353
|
+
ci-gate:
|
|
354
|
+
name: CI Gate
|
|
355
|
+
if: ${{ always() }}
|
|
356
|
+
needs: [changes, build-check]
|
|
357
|
+
runs-on: ubuntu-latest
|
|
358
|
+
steps:
|
|
359
|
+
- name: 결과 집계
|
|
360
|
+
env:
|
|
361
|
+
RESULTS: ${{ toJSON(needs.*.result) }}
|
|
362
|
+
run: |
|
|
363
|
+
echo "needs 결과: $RESULTS"
|
|
364
|
+
if echo "$RESULTS" | grep -Eq '"(failure|cancelled)"'; then
|
|
365
|
+
echo "실패하거나 취소된 job이 있습니다."
|
|
366
|
+
exit 1
|
|
367
|
+
fi
|
|
@@ -15,9 +15,9 @@
|
|
|
15
15
|
# ===================================================================
|
|
16
16
|
#
|
|
17
17
|
# 🚀 사용법:
|
|
18
|
-
#
|
|
19
|
-
#
|
|
20
|
-
#
|
|
18
|
+
# /wizard server build - PR/Issue 빌드 및 배포
|
|
19
|
+
# /wizard server destroy - Preview 환경 삭제
|
|
20
|
+
# /wizard server status - 현재 상태 확인
|
|
21
21
|
#
|
|
22
22
|
# 📊 리소스 네이밍 규칙:
|
|
23
23
|
# - 컨테이너: {PROJECT_NAME}-pr-{PR번호}
|
|
@@ -65,7 +65,7 @@ env:
|
|
|
65
65
|
|
|
66
66
|
# Issue Helper 댓글 마커 (프로젝트별 수정 필요)
|
|
67
67
|
# Issue 댓글에서 브랜치명을 추출할 때 사용
|
|
68
|
-
ISSUE_HELPER_MARKER: '
|
|
68
|
+
ISSUE_HELPER_MARKER: '<!-- project-auto-wizard issue helper -->'
|
|
69
69
|
|
|
70
70
|
# Health Check 설정 (프로젝트별 수정 필요)
|
|
71
71
|
# - HEALTH_CHECK_PATH: HTTP Health Check 경로 (빈값이면 HTTP 체크 건너뜀)
|
|
@@ -115,7 +115,7 @@ jobs:
|
|
|
115
115
|
is_custom_branch: ${{ steps.parse.outputs.is_custom_branch }}
|
|
116
116
|
steps:
|
|
117
117
|
- name: 댓글에 👀 리액션 추가
|
|
118
|
-
if: contains(github.event.comment.body, '
|
|
118
|
+
if: contains(github.event.comment.body, '/wizard') && contains(github.event.comment.body, 'server')
|
|
119
119
|
uses: actions/github-script@v9
|
|
120
120
|
with:
|
|
121
121
|
github-token: ${{ secrets.GITHUB_TOKEN }}
|
|
@@ -143,8 +143,8 @@ jobs:
|
|
|
143
143
|
fi
|
|
144
144
|
|
|
145
145
|
# 명령어 파싱 (브랜치 파라미터 지원)
|
|
146
|
-
# 형식:
|
|
147
|
-
if [[ "$COMMENT" =~
|
|
146
|
+
# 형식: /wizard server <command> [브랜치명]
|
|
147
|
+
if [[ "$COMMENT" =~ /wizard[[:space:]]+server[[:space:]]+build([[:space:]]+([^[:space:]]+))? ]]; then
|
|
148
148
|
echo "command=build" >> $GITHUB_OUTPUT
|
|
149
149
|
echo "is_valid=true" >> $GITHUB_OUTPUT
|
|
150
150
|
if [[ -n "${BASH_REMATCH[2]}" ]]; then
|
|
@@ -155,7 +155,7 @@ jobs:
|
|
|
155
155
|
echo "is_custom_branch=false" >> $GITHUB_OUTPUT
|
|
156
156
|
echo "✅ 명령어 감지: build"
|
|
157
157
|
fi
|
|
158
|
-
elif [[ "$COMMENT" =~
|
|
158
|
+
elif [[ "$COMMENT" =~ /wizard[[:space:]]+server[[:space:]]+destroy([[:space:]]+([^[:space:]]+))? ]]; then
|
|
159
159
|
echo "command=destroy" >> $GITHUB_OUTPUT
|
|
160
160
|
echo "is_valid=true" >> $GITHUB_OUTPUT
|
|
161
161
|
if [[ -n "${BASH_REMATCH[2]}" ]]; then
|
|
@@ -166,7 +166,7 @@ jobs:
|
|
|
166
166
|
echo "is_custom_branch=false" >> $GITHUB_OUTPUT
|
|
167
167
|
echo "✅ 명령어 감지: destroy"
|
|
168
168
|
fi
|
|
169
|
-
elif [[ "$COMMENT" =~
|
|
169
|
+
elif [[ "$COMMENT" =~ /wizard[[:space:]]+server[[:space:]]+status([[:space:]]+([^[:space:]]+))? ]]; then
|
|
170
170
|
echo "command=status" >> $GITHUB_OUTPUT
|
|
171
171
|
echo "is_valid=true" >> $GITHUB_OUTPUT
|
|
172
172
|
if [[ -n "${BASH_REMATCH[2]}" ]]; then
|
|
@@ -179,7 +179,7 @@ jobs:
|
|
|
179
179
|
fi
|
|
180
180
|
else
|
|
181
181
|
echo "is_valid=false" >> $GITHUB_OUTPUT
|
|
182
|
-
echo "ℹ️
|
|
182
|
+
echo "ℹ️ /wizard server 명령어가 아님"
|
|
183
183
|
fi
|
|
184
184
|
|
|
185
185
|
# -----------------------------------------------------------------
|
|
@@ -292,13 +292,13 @@ jobs:
|
|
|
292
292
|
# env:
|
|
293
293
|
# VERTEX_SA_KEY: ${{ secrets.VERTEX_SA_KEY }}
|
|
294
294
|
# run: |
|
|
295
|
-
# echo "$VERTEX_SA_KEY" | sed 's/\\n/\n/g' >
|
|
295
|
+
# echo "$VERTEX_SA_KEY" | sed 's/\\n/\n/g' > ./<모듈명>/src/main/resources/vertex-ai-key.json
|
|
296
296
|
|
|
297
297
|
# - name: "[선택] Firebase Admin SDK 생성"
|
|
298
298
|
# env:
|
|
299
299
|
# FIREBASE_KEY_JSON: ${{ secrets.FIREBASE_KEY_JSON }}
|
|
300
300
|
# run: |
|
|
301
|
-
# echo "$FIREBASE_KEY_JSON" | sed 's/\\n/\n/g' >
|
|
301
|
+
# echo "$FIREBASE_KEY_JSON" | sed 's/\\n/\n/g' > ./<모듈명>/src/main/resources/firebase-admin-sdk.json
|
|
302
302
|
|
|
303
303
|
# =================================================================
|
|
304
304
|
# ⚠️ [영역 2 끝] 프로젝트별 Secret 파일 생성 끝
|
|
@@ -606,17 +606,17 @@ jobs:
|
|
|
606
606
|
'### 📋 명령어',
|
|
607
607
|
'| 명령어 | 설명 |',
|
|
608
608
|
'|--------|------|',
|
|
609
|
-
'|
|
|
610
|
-
'|
|
|
611
|
-
'|
|
|
609
|
+
'| `/wizard server build` | 최신 커밋으로 재배포 |',
|
|
610
|
+
'| `/wizard server destroy` | Preview 환경 삭제 |',
|
|
611
|
+
'| `/wizard server status` | 현재 상태 확인 |',
|
|
612
612
|
'',
|
|
613
613
|
'<details>',
|
|
614
614
|
'<summary>🔧 고급 명령어 (다른 Issue/PR에서 제어)</summary>',
|
|
615
615
|
'',
|
|
616
616
|
'```',
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
617
|
+
`/wizard server build ${branchName}`,
|
|
618
|
+
`/wizard server destroy ${branchName}`,
|
|
619
|
+
`/wizard server status ${branchName}`,
|
|
620
620
|
'```',
|
|
621
621
|
'</details>',
|
|
622
622
|
'',
|
|
@@ -693,7 +693,7 @@ jobs:
|
|
|
693
693
|
'',
|
|
694
694
|
'### 💡 다음 단계',
|
|
695
695
|
'1. 위 링크에서 빌드/배포 로그를 확인하세요',
|
|
696
|
-
'2. 문제를 수정한 후 다시 시도하세요:
|
|
696
|
+
'2. 문제를 수정한 후 다시 시도하세요: `/wizard server build`',
|
|
697
697
|
'',
|
|
698
698
|
'---',
|
|
699
699
|
'*🤖 이 댓글은 PR Preview 시스템에 의해 자동 생성되었습니다.*'
|
|
@@ -1278,17 +1278,17 @@ jobs:
|
|
|
1278
1278
|
'### 📋 명령어',
|
|
1279
1279
|
'| 명령어 | 설명 |',
|
|
1280
1280
|
'|--------|------|',
|
|
1281
|
-
'|
|
|
1282
|
-
'|
|
|
1283
|
-
'|
|
|
1281
|
+
'| `/wizard server build` | 최신 커밋으로 재배포 |',
|
|
1282
|
+
'| `/wizard server destroy` | Preview 환경 삭제 |',
|
|
1283
|
+
'| `/wizard server status` | 현재 상태 확인 |',
|
|
1284
1284
|
'',
|
|
1285
1285
|
'<details>',
|
|
1286
1286
|
'<summary>🔧 고급 명령어 (다른 Issue/PR에서 제어)</summary>',
|
|
1287
1287
|
'',
|
|
1288
1288
|
'```',
|
|
1289
|
-
|
|
1290
|
-
|
|
1291
|
-
|
|
1289
|
+
`/wizard server build ${branchName}`,
|
|
1290
|
+
`/wizard server destroy ${branchName}`,
|
|
1291
|
+
`/wizard server status ${branchName}`,
|
|
1292
1292
|
'```',
|
|
1293
1293
|
'</details>',
|
|
1294
1294
|
'',
|
|
@@ -1362,7 +1362,7 @@ jobs:
|
|
|
1362
1362
|
'',
|
|
1363
1363
|
'### 💡 다음 단계',
|
|
1364
1364
|
'1. 위 링크에서 빌드/배포 로그를 확인하세요',
|
|
1365
|
-
'2. 문제를 수정한 후 다시 시도하세요:
|
|
1365
|
+
'2. 문제를 수정한 후 다시 시도하세요: `/wizard server build`',
|
|
1366
1366
|
'',
|
|
1367
1367
|
'---',
|
|
1368
1368
|
'*🤖 이 댓글은 Issue Preview 시스템에 의해 자동 생성되었습니다.*'
|
|
@@ -1393,7 +1393,7 @@ jobs:
|
|
|
1393
1393
|
if: |
|
|
1394
1394
|
(github.event_name == 'pull_request' && github.event.action == 'closed') ||
|
|
1395
1395
|
(github.event_name == 'issues' && github.event.action == 'closed') ||
|
|
1396
|
-
(github.event_name == 'issue_comment' && contains(github.event.comment.body, '
|
|
1396
|
+
(github.event_name == 'issue_comment' && contains(github.event.comment.body, '/wizard') && contains(github.event.comment.body, 'server destroy'))
|
|
1397
1397
|
runs-on: ubuntu-latest
|
|
1398
1398
|
steps:
|
|
1399
1399
|
- name: PR/Issue 번호 가져오기
|
|
@@ -1474,7 +1474,7 @@ jobs:
|
|
|
1474
1474
|
`| **컨테이너** | \`${projectName}-pr-${number}\` |`,
|
|
1475
1475
|
'| **상태** | 삭제됨 |',
|
|
1476
1476
|
'',
|
|
1477
|
-
'다시 배포하려면:
|
|
1477
|
+
'다시 배포하려면: `/wizard server build`',
|
|
1478
1478
|
'',
|
|
1479
1479
|
'---',
|
|
1480
1480
|
'*🤖 이 댓글은 PR/Issue Preview 시스템에 의해 자동 생성되었습니다.*'
|
|
@@ -1573,8 +1573,8 @@ jobs:
|
|
|
1573
1573
|
'### 📋 명령어',
|
|
1574
1574
|
'| 명령어 | 설명 |',
|
|
1575
1575
|
'|--------|------|',
|
|
1576
|
-
'|
|
|
1577
|
-
'|
|
|
1576
|
+
'| `/wizard server build` | 최신 커밋으로 재배포 |',
|
|
1577
|
+
'| `/wizard server destroy` | Preview 환경 삭제 |',
|
|
1578
1578
|
'',
|
|
1579
1579
|
'---',
|
|
1580
1580
|
`*🤖 이 댓글은 ${contextType} Preview 시스템에 의해 자동 생성되었습니다.*`
|
|
@@ -1588,7 +1588,7 @@ jobs:
|
|
|
1588
1588
|
`| **컨테이너** | \`${projectName}-pr-${number}\` |`,
|
|
1589
1589
|
'| **상태** | 🔴 Not Found |',
|
|
1590
1590
|
'',
|
|
1591
|
-
'배포하려면:
|
|
1591
|
+
'배포하려면: `/wizard server build`',
|
|
1592
1592
|
'',
|
|
1593
1593
|
'---',
|
|
1594
1594
|
`*🤖 이 댓글은 ${contextType} Preview 시스템에 의해 자동 생성되었습니다.*`
|
|
@@ -1605,7 +1605,7 @@ jobs:
|
|
|
1605
1605
|
# ============================================================
|
|
1606
1606
|
# Custom Branch Preview Jobs
|
|
1607
1607
|
# 특정 브랜치를 지정하여 빌드/삭제/상태 확인하는 기능
|
|
1608
|
-
# 명령어:
|
|
1608
|
+
# 명령어: /wizard server build <브랜치명>
|
|
1609
1609
|
# ============================================================
|
|
1610
1610
|
|
|
1611
1611
|
build-preview-custom-branch:
|
|
@@ -1984,9 +1984,9 @@ jobs:
|
|
|
1984
1984
|
'### 📋 명령어',
|
|
1985
1985
|
'| 명령어 | 설명 |',
|
|
1986
1986
|
'|--------|------|',
|
|
1987
|
-
`|
|
|
1988
|
-
`|
|
|
1989
|
-
`|
|
|
1987
|
+
`| \`/wizard server build ${branchName}\` | 최신 커밋으로 재배포 |`,
|
|
1988
|
+
`| \`/wizard server destroy ${branchName}\` | Preview 환경 삭제 |`,
|
|
1989
|
+
`| \`/wizard server status ${branchName}\` | 현재 상태 확인 |`,
|
|
1990
1990
|
'',
|
|
1991
1991
|
'---',
|
|
1992
1992
|
'*🤖 이 댓글은 Preview 시스템에 의해 자동 생성되었습니다.*'
|
|
@@ -2154,7 +2154,7 @@ jobs:
|
|
|
2154
2154
|
`| **브랜치** | \`${branchName}\` |`,
|
|
2155
2155
|
`| **컨테이너** | \`${containerName}\` |`,
|
|
2156
2156
|
'',
|
|
2157
|
-
`다시 배포하려면:
|
|
2157
|
+
`다시 배포하려면: \`/wizard server build ${branchName}\``,
|
|
2158
2158
|
'',
|
|
2159
2159
|
'---',
|
|
2160
2160
|
'*🤖 이 댓글은 Preview 시스템에 의해 자동 생성되었습니다.*'
|
|
@@ -2247,8 +2247,8 @@ jobs:
|
|
|
2247
2247
|
'### 📋 명령어',
|
|
2248
2248
|
'| 명령어 | 설명 |',
|
|
2249
2249
|
'|--------|------|',
|
|
2250
|
-
`|
|
|
2251
|
-
`|
|
|
2250
|
+
`| \`/wizard server build ${branchName}\` | 최신 커밋으로 재배포 |`,
|
|
2251
|
+
`| \`/wizard server destroy ${branchName}\` | Preview 환경 삭제 |`,
|
|
2252
2252
|
'',
|
|
2253
2253
|
'---',
|
|
2254
2254
|
'*🤖 이 댓글은 Preview 시스템에 의해 자동 생성되었습니다.*'
|
|
@@ -2263,7 +2263,7 @@ jobs:
|
|
|
2263
2263
|
`| **컨테이너** | \`${containerName}\` |`,
|
|
2264
2264
|
'| **상태** | 🔴 Not Found |',
|
|
2265
2265
|
'',
|
|
2266
|
-
`배포하려면:
|
|
2266
|
+
`배포하려면: \`/wizard server build ${branchName}\``,
|
|
2267
2267
|
'',
|
|
2268
2268
|
'---',
|
|
2269
2269
|
'*🤖 이 댓글은 Preview 시스템에 의해 자동 생성되었습니다.*'
|
package/src/cli/args.js
CHANGED
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
// CLI 인자 파싱 (.sh top-level while-case 등가) — template_integrator.sh 842~920.
|
|
2
2
|
import { VALID_TYPES, VALID_MODES } from "../context.js";
|
|
3
3
|
import { DEPLOY_STYLES, isDeployStyle, NO_DEPLOY_STYLE } from "../core/deploy-style.js";
|
|
4
|
+
import {
|
|
5
|
+
ENV_MODES, DEPLOY_MODES, STORE_PLATFORMS, NO_STORE, isEnvMode, isDeployMode, parseStoreList,
|
|
6
|
+
} from "../core/flutter-options.js";
|
|
4
7
|
|
|
5
8
|
// argv(process.argv.slice(2)) → 파싱 결과. 오류 시 throw(호출부에서 exit 1).
|
|
6
9
|
export function parseArgs(argv) {
|
|
@@ -16,6 +19,10 @@ export function parseArgs(argv) {
|
|
|
16
19
|
mainBranch: "", // 릴리스 브랜치 (--main-branch). 빈값=감지된 default branch
|
|
17
20
|
developBranch: "", // 개발 브랜치 (--develop-branch). 빈값=develop
|
|
18
21
|
deployStyle: "", // 서버 배포 방식 (--deploy-style). 빈값=version.yml 저장값 → simple
|
|
22
|
+
flutterEnvMode: "", // Flutter 환경변수 방식 (--flutter-env-mode). 빈값=저장값 → 신규 dart-define/기존 dotenv
|
|
23
|
+
flutterStore: null, // Flutter 스토어 배포 대상 string[] (--flutter-store). null=미지정, none → []
|
|
24
|
+
androidDeployMode: "", // Play Store 배포 모드 (--android-deploy-mode). 빈값=저장값 → store_only
|
|
25
|
+
iosDeployMode: "", // iOS 배포 모드 (--ios-deploy-mode). 빈값=저장값 → store_only
|
|
19
26
|
force: false,
|
|
20
27
|
help: false,
|
|
21
28
|
showVersion: false, // -v/--version → 패키지 버전 출력 (npm 관례)
|
|
@@ -84,6 +91,36 @@ export function parseArgs(argv) {
|
|
|
84
91
|
}
|
|
85
92
|
result.deployStyle = v; break;
|
|
86
93
|
}
|
|
94
|
+
case "--flutter-env-mode": {
|
|
95
|
+
const v = args.shift();
|
|
96
|
+
if (!isEnvMode(v)) {
|
|
97
|
+
throw new CliError(`--flutter-env-mode 값이 올바르지 않습니다: ${v ?? "(없음)"} (${ENV_MODES.join(" | ")})`);
|
|
98
|
+
}
|
|
99
|
+
result.flutterEnvMode = v; break;
|
|
100
|
+
}
|
|
101
|
+
case "--flutter-store": {
|
|
102
|
+
const v = args.shift();
|
|
103
|
+
// 빈 문자열은 parseStoreList가 []로 보지만, 스토어를 안 고르겠다는 뜻은 명시적인 none으로만 받는다.
|
|
104
|
+
const stores = v ? parseStoreList(v) : null;
|
|
105
|
+
if (stores === null) {
|
|
106
|
+
throw new CliError(`--flutter-store 값이 올바르지 않습니다: ${v || "(없음)"} (${[STORE_PLATFORMS.join(","), ...STORE_PLATFORMS, NO_STORE].join(" | ")})`);
|
|
107
|
+
}
|
|
108
|
+
result.flutterStore = stores; break;
|
|
109
|
+
}
|
|
110
|
+
case "--android-deploy-mode": {
|
|
111
|
+
const v = args.shift();
|
|
112
|
+
if (!isDeployMode(v)) {
|
|
113
|
+
throw new CliError(`--android-deploy-mode 값이 올바르지 않습니다: ${v ?? "(없음)"} (${DEPLOY_MODES.join(" | ")})`);
|
|
114
|
+
}
|
|
115
|
+
result.androidDeployMode = v; break;
|
|
116
|
+
}
|
|
117
|
+
case "--ios-deploy-mode": {
|
|
118
|
+
const v = args.shift();
|
|
119
|
+
if (!isDeployMode(v)) {
|
|
120
|
+
throw new CliError(`--ios-deploy-mode 값이 올바르지 않습니다: ${v ?? "(없음)"} (${DEPLOY_MODES.join(" | ")})`);
|
|
121
|
+
}
|
|
122
|
+
result.iosDeployMode = v; break;
|
|
123
|
+
}
|
|
87
124
|
case "--nexus":
|
|
88
125
|
if (seenFlags.has("--no-nexus")) throw new CliError("--nexus와 --no-nexus는 동시에 지정할 수 없습니다");
|
|
89
126
|
seenFlags.add("--nexus"); result.includeNexus = true; break;
|
package/src/cli/help.js
CHANGED
|
@@ -17,6 +17,10 @@ export const HELP_TEXT = `project-auto-wizard — One command DevOps: GitHub-nat
|
|
|
17
17
|
--main-branch B 릴리스 브랜치 (기본: 감지된 default branch)
|
|
18
18
|
--develop-branch B 개발 브랜치 (기본: develop). 릴리스 브랜치와 같으면 trunk-based 모드
|
|
19
19
|
--deploy-style STYLE 서버 배포 방식: simple | nginx | traefik | none (기본: simple)
|
|
20
|
+
--flutter-env-mode MODE Flutter 환경변수 방식: dart-define | dotenv (기본: 신규 설치 dart-define, 기존 설치는 저장값·dotenv 유지)
|
|
21
|
+
--flutter-store CSV Flutter 스토어 배포 대상: android,ios | android | ios | none (기본: 둘 다 설치)
|
|
22
|
+
--android-deploy-mode MODE Play Store 배포 모드: store_only | store_prepare | store_submit (기본: store_only)
|
|
23
|
+
--ios-deploy-mode MODE iOS 배포 모드: store_only | store_prepare | store_submit (기본: store_only)
|
|
20
24
|
--nexus / --no-nexus 라이브러리 publish 워크플로우 포함/제외 (Nexus + GitHub Packages)
|
|
21
25
|
--secret-backup / --no-secret-backup Secret 백업 워크플로우 포함/제외
|
|
22
26
|
--semver-auto / --no-semver-auto 커밋 타입 기반 자동 major/minor/patch 승격 (기본: 사용함)
|
package/src/commands/doctor.js
CHANGED
|
@@ -11,9 +11,13 @@
|
|
|
11
11
|
// ④ GitHub 설정 화면에 실제로 표시되는 문자열("Read and write permissions" 등)은 번역하지
|
|
12
12
|
// 않는다. 번역하면 설명은 읽히지만 정작 화면에서 그 항목을 찾지 못한다.
|
|
13
13
|
import { spawnSync } from "node:child_process";
|
|
14
|
-
import { existsSync } from "node:fs";
|
|
15
|
-
import { join } from "node:path";
|
|
14
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
15
|
+
import { join, posix } from "node:path";
|
|
16
16
|
import { A, paint, colorEnabled, visualWidth } from "../ui/ansi.js";
|
|
17
|
+
import { PATHS } from "../core/paths.js";
|
|
18
|
+
import { parseExisting } from "../core/version-yml.js";
|
|
19
|
+
import { STORE_PLATFORMS, STORE_APP_FILES, parseStoreList, storeAppFilesFor } from "../core/flutter-options.js";
|
|
20
|
+
import { inferInstalledStores } from "../core/installed-stores.js";
|
|
17
21
|
|
|
18
22
|
const defaultExec = (cmd, args) => spawnSync(cmd, args, { encoding: "utf8" });
|
|
19
23
|
|
|
@@ -23,6 +27,7 @@ const defaultExec = (cmd, args) => spawnSync(cmd, args, { encoding: "utf8" });
|
|
|
23
27
|
const REPO_URL = "https://github.com/Twin-Fang/project-auto-wizard";
|
|
24
28
|
export const DOC = {
|
|
25
29
|
postInstall: `${REPO_URL}#post-install`,
|
|
30
|
+
flutterStore: `${REPO_URL}#flutter-store`,
|
|
26
31
|
};
|
|
27
32
|
|
|
28
33
|
export function runDoctor(cwd = process.cwd(), { exec = defaultExec } = {}) {
|
|
@@ -39,6 +44,9 @@ export function runDoctor(cwd = process.cwd(), { exec = defaultExec } = {}) {
|
|
|
39
44
|
note: ["이 폴더엔 아직 설치되지 않았습니다 — 지금 설치하면 됩니다."],
|
|
40
45
|
});
|
|
41
46
|
|
|
47
|
+
// Flutter 스토어 배포 파일 점검 (이슈 #131) — 로컬 파일만 보므로 gh 조회 결과와 무관하게 수행한다.
|
|
48
|
+
if (installed) for (const item of flutterStoreChecks(cwd)) add(item);
|
|
49
|
+
|
|
42
50
|
const ghVersion = exec("gh", ["--version"]);
|
|
43
51
|
if (ghVersion.error || ghVersion.status !== 0) {
|
|
44
52
|
add({
|
|
@@ -163,6 +171,51 @@ export function runDoctor(cwd = process.cwd(), { exec = defaultExec } = {}) {
|
|
|
163
171
|
return results;
|
|
164
172
|
}
|
|
165
173
|
|
|
174
|
+
const PLATFORM_ROW_NAME = { android: "Flutter Android 배포 파일", ios: "Flutter iOS 배포 파일" };
|
|
175
|
+
const PLACEHOLDER_RE = /__[A-Z][A-Z0-9_]*__/g; // 감지 규칙은 ExportOptions.plist 템플릿·IOS-TESTFLIGHT 검증과 동일
|
|
176
|
+
const EXPORT_OPTIONS_REL = STORE_APP_FILES.ios.find((rel) => rel.endsWith("ExportOptions.plist"));
|
|
177
|
+
|
|
178
|
+
// Flutter 스토어 배포 진단 — 선택한 플랫폼의 필수 파일과 ExportOptions.plist 플레이스홀더 (이슈 #131).
|
|
179
|
+
// 스토어 시크릿 등록 여부는 이번 범위 밖이다. 반환: doctor 결과 행 배열.
|
|
180
|
+
function flutterStoreChecks(cwd) {
|
|
181
|
+
const existing = parseExisting(readFileSync(join(cwd, PATHS.versionFile), "utf8"));
|
|
182
|
+
if (!existing.types.includes("flutter")) return [];
|
|
183
|
+
const saved = existing.options.flutterStore == null ? null : parseStoreList(existing.options.flutterStore);
|
|
184
|
+
// 저장값 없는 기존 설치는 설치된 스토어 워크플로우로 추론한다 (interactive와 같은 규칙).
|
|
185
|
+
const stores = saved ?? inferInstalledStores(join(cwd, PATHS.workflowsDir));
|
|
186
|
+
const flutterRoot = existing.paths.get("flutter") || ".";
|
|
187
|
+
const rows = [];
|
|
188
|
+
|
|
189
|
+
for (const platform of STORE_PLATFORMS.filter((p) => stores.includes(p))) {
|
|
190
|
+
const files = storeAppFilesFor([platform]).map((rel) => posix.join(flutterRoot, rel));
|
|
191
|
+
const missing = files.filter((file) => !existsSync(join(cwd, file)));
|
|
192
|
+
const head = { name: PLATFORM_ROW_NAME[platform], purpose: "fastlane 스토어 업로드에 필요한 파일" };
|
|
193
|
+
rows.push(missing.length
|
|
194
|
+
? {
|
|
195
|
+
...head, status: "WARN", value: `없는 파일: ${missing.join(", ")}`,
|
|
196
|
+
impact: ["이 파일이 없으면 스토어 배포 워크플로우가 fastlane 단계에서 실패합니다."],
|
|
197
|
+
actions: ["마법사를 다시 실행하면 없는 파일만 새로 만들어 줍니다 (이미 있는 파일은 덮어쓰지 않음)"],
|
|
198
|
+
doc: DOC.flutterStore,
|
|
199
|
+
}
|
|
200
|
+
: { ...head, status: "OK", value: `${files.length}개 있음` });
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
const plistPath = posix.join(flutterRoot, EXPORT_OPTIONS_REL);
|
|
204
|
+
if (stores.includes("ios") && existsSync(join(cwd, plistPath))) {
|
|
205
|
+
const head = { name: "ExportOptions.plist", purpose: "iOS 서명·내보내기 설정" };
|
|
206
|
+
const left = [...new Set(readFileSync(join(cwd, plistPath), "utf8").match(PLACEHOLDER_RE) ?? [])];
|
|
207
|
+
rows.push(left.length
|
|
208
|
+
? {
|
|
209
|
+
...head, status: "WARN", value: `채워지지 않은 값: ${left.join(", ")}`,
|
|
210
|
+
impact: ["채우지 않으면 IOS-TESTFLIGHT 워크플로우가 ExportOptions.plist 검증 단계에서 중단됩니다."],
|
|
211
|
+
actions: [`${plistPath} 의 플레이스홀더를 실제 값(Team ID · 번들 ID · 프로비저닝 프로파일 이름)으로 바꾸세요`],
|
|
212
|
+
doc: DOC.flutterStore,
|
|
213
|
+
}
|
|
214
|
+
: { ...head, status: "OK", value: "플레이스홀더 없음" });
|
|
215
|
+
}
|
|
216
|
+
return rows;
|
|
217
|
+
}
|
|
218
|
+
|
|
166
219
|
const asLines = (v) => (Array.isArray(v) ? v : v ? [String(v)] : []);
|
|
167
220
|
const headOf = (r) => `${r.label || r.name}${r.purpose ? ` — ${r.purpose}` : ""}`;
|
|
168
221
|
|
package/src/commands/dry-run.js
CHANGED
|
@@ -4,6 +4,7 @@ import { existsSync, readFileSync } from "node:fs";
|
|
|
4
4
|
import { join } from "node:path";
|
|
5
5
|
import { PATHS } from "../core/paths.js";
|
|
6
6
|
import { planWorkflows } from "../core/copy/workflows.js";
|
|
7
|
+
import { planFlutterAppFiles } from "../core/copy/flutter-app.js";
|
|
7
8
|
import { planUninstall } from "./uninstall.js";
|
|
8
9
|
import { renderVersionYml, parseExisting } from "../core/version-yml.js";
|
|
9
10
|
import { readVersionYmlTemplate } from "../core/assets.js";
|
|
@@ -33,6 +34,8 @@ export function planDryRun(mode, context, payloadRoot, targetRoot = ".") {
|
|
|
33
34
|
return {
|
|
34
35
|
mode,
|
|
35
36
|
workflows: planWorkflows(context, payloadRoot, targetRoot),
|
|
37
|
+
// Flutter 스토어 배포 파일(Fastfile·ExportOptions.plist) — 이미 있는 파일은 덮어쓰지 않고 유지한다.
|
|
38
|
+
flutterApp: planFlutterAppFiles(context, payloadRoot, targetRoot),
|
|
36
39
|
versionYml: versionYmlPreview(context, payloadRoot, targetRoot),
|
|
37
40
|
};
|
|
38
41
|
}
|
|
@@ -57,6 +60,13 @@ export function printDryRun(plan) {
|
|
|
57
60
|
for (const f of w.changed) lines.push(` ~ ${f.filename} [${f.type}]`);
|
|
58
61
|
lines.push(`동일한 파일 (${w.unchanged.length}개, 변경 없음)`);
|
|
59
62
|
}
|
|
63
|
+
if (plan.flutterApp && (plan.flutterApp.created.length || plan.flutterApp.kept.length)) {
|
|
64
|
+
const { created, kept } = plan.flutterApp;
|
|
65
|
+
lines.push(`Flutter 스토어 배포 파일 — 신규 (${created.length}개):`);
|
|
66
|
+
for (const f of created) lines.push(` + ${f}`);
|
|
67
|
+
lines.push(`Flutter 스토어 배포 파일 — 기존 파일 유지 (${kept.length}개, 덮어쓰지 않음):`);
|
|
68
|
+
for (const f of kept) lines.push(` = ${f} (기존 파일 유지)`);
|
|
69
|
+
}
|
|
60
70
|
if (plan.versionYml) {
|
|
61
71
|
lines.push(plan.versionYml.existed
|
|
62
72
|
? (plan.versionYml.changed ? "version.yml: 갱신될 예정" : "version.yml: 변경 없음")
|
package/src/commands/full.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// full 모드 오케스트레이터 (.sh execute_integration full case 등가).
|
|
2
|
-
// 복사 순서: workflows(+env 치환) → version.yml → readme → scripts → gitignore(조건부)
|
|
2
|
+
// 복사 순서: workflows(+env 치환) → flutter 앱 파일 → version.yml → readme → scripts → gitignore(조건부)
|
|
3
3
|
// gitignore는 충돌 백업 부산물(.bak/.template.yaml)이 이번 실행에서 실제로 생겼을 때만 갱신한다 — issue #7.
|
|
4
4
|
// (원본의 util/issue/discussion/setup-guide/config 설치는 project-auto-wizard 스코프에서 제외 — DESIGN-SPEC §2)
|
|
5
5
|
import { join } from "node:path";
|
|
@@ -12,10 +12,12 @@ import { existingMarkerInDir } from "../core/paths-resolve.js";
|
|
|
12
12
|
import { addVersionSectionToReadme } from "../core/copy/readme.js";
|
|
13
13
|
import { copyWorkflows, computeBaselineEntries, makeSrcText } from "../core/copy/workflows.js";
|
|
14
14
|
import { copyScripts } from "../core/copy/simple.js";
|
|
15
|
+
import { copyFlutterAppFiles } from "../core/copy/flutter-app.js";
|
|
15
16
|
import { ensureGitignore } from "../core/copy/gitignore.js";
|
|
16
17
|
import { readBaseline, writeBaseline } from "../core/baseline.js";
|
|
17
18
|
import { scanUnsubstituted, collectRequiredSecrets, narrowSecretsBySshAuth } from "../core/verify.js";
|
|
18
19
|
import { cleanupOtherDeployWorkflows, DEFAULT_DEPLOY_STYLE } from "../core/deploy-style.js";
|
|
20
|
+
import { cleanupDeselectedStoreWorkflows } from "../core/flutter-options.js";
|
|
19
21
|
import { log, maskValue } from "../core/logger.js";
|
|
20
22
|
|
|
21
23
|
// context: { version, types, paths:Map, branch, versionCode, includeNexus, includeSecretBackup,
|
|
@@ -47,6 +49,12 @@ export function runFull(context, payloadRoot, targetRoot = ".", hooks = {}) {
|
|
|
47
49
|
const wfCounters = copyWorkflows(context, payloadRoot, targetRoot, hooks);
|
|
48
50
|
const deployValues = wfCounters.deployValues || new Map(); // Map<type, Map<key,value>>
|
|
49
51
|
|
|
52
|
+
// 1-1. Flutter 앱 파일(fastlane·ExportOptions) — 사용자가 값을 채워 쓰는 파일이라 없을 때만 만든다.
|
|
53
|
+
// 워크플로우 복사가 끝난 뒤에 실행한다(워크플로우가 이 파일들을 전제로 돈다).
|
|
54
|
+
const flutterApp = copyFlutterAppFiles(context, payloadRoot, targetRoot);
|
|
55
|
+
for (const f of flutterApp.created) log.info("flutter-app", "create", f);
|
|
56
|
+
for (const f of flutterApp.kept) log.info("flutter-app", "keep", `${f} (기존 파일 유지)`);
|
|
57
|
+
|
|
50
58
|
// 기존 version.yml의 알려지지 않은 최상위 필드를 재생성 시 보존한다 (issue #20 M8).
|
|
51
59
|
const vyPath = join(targetRoot, PATHS.versionFile);
|
|
52
60
|
const extraTopLevel = existsSync(vyPath) ? parseExisting(readFileSync(vyPath, "utf8")).extraTopLevel : [];
|
|
@@ -80,7 +88,24 @@ export function runFull(context, payloadRoot, targetRoot = ".", hooks = {}) {
|
|
|
80
88
|
|
|
81
89
|
for (const f of cleanup.removed || []) log.info("cleanup", "remove", `${f} (이전 배포 방식 정리)`);
|
|
82
90
|
for (const f of cleanup.backedUp || []) log.info("cleanup", "backup", `${f} → ${f}.bak`);
|
|
83
|
-
|
|
91
|
+
|
|
92
|
+
// 6-1. 선택 해제된 스토어 워크플로우 정리 — 스토어 대상을 줄여 재설치하면 이전 워크플로우가 남아
|
|
93
|
+
// main push마다 계속 도는 것을 막는다. 규칙은 6과 같다(미수정 삭제, 수정본 .bak).
|
|
94
|
+
// 선택이 미결정(null)이거나 Flutter가 없으면 현행 동작 그대로 아무것도 지우지 않는다.
|
|
95
|
+
// Fastfile·ExportOptions는 사용자 소유라 여기서 다루지 않는다.
|
|
96
|
+
const workflowsDir = join(targetRoot, PATHS.workflowsDir);
|
|
97
|
+
const storeCleanup = Array.isArray(context.flutterStore) && types.includes("flutter")
|
|
98
|
+
? cleanupDeselectedStoreWorkflows(
|
|
99
|
+
workflowsDir,
|
|
100
|
+
existsSync(workflowsDir) ? readdirSync(workflowsDir) : [],
|
|
101
|
+
context.flutterStore,
|
|
102
|
+
previousBaseline)
|
|
103
|
+
: { removed: [], backedUp: [] };
|
|
104
|
+
for (const f of [...storeCleanup.removed, ...storeCleanup.backedUp]) delete previousBaseline?.files?.[f];
|
|
105
|
+
for (const f of storeCleanup.removed) log.info("cleanup", "remove", `${f} (선택 해제된 스토어 워크플로우 정리)`);
|
|
106
|
+
for (const f of storeCleanup.backedUp) log.info("cleanup", "backup", `${f} → ${f}.bak`);
|
|
107
|
+
|
|
108
|
+
const gitignoreUpdated = gitignoreUpdated0 || cleanup.backedUp.length > 0 || storeCleanup.backedUp.length > 0;
|
|
84
109
|
if (gitignoreUpdated) ensureGitignore(targetRoot);
|
|
85
110
|
|
|
86
111
|
// 7. baseline 기록 (issue #69) — 다음 업데이트에서 "누가 바꿨는지"를 가를 기준점.
|
|
@@ -122,7 +147,7 @@ export function runFull(context, payloadRoot, targetRoot = ".", hooks = {}) {
|
|
|
122
147
|
["결과", unresolved.length ? `주의 (미치환 ${unresolved.length}건)` : "OK"],
|
|
123
148
|
]);
|
|
124
149
|
|
|
125
|
-
return { workflows: wfCounters, gitignoreUpdated, unresolved, secrets, cleanup };
|
|
150
|
+
return { workflows: wfCounters, gitignoreUpdated, unresolved, secrets, cleanup, storeCleanup, flutterApp };
|
|
126
151
|
}
|
|
127
152
|
|
|
128
153
|
// deployValues는 Map<type, Map<key,value>> — 타입 구분 없이 첫 값만 필요할 때 쓴다.
|