project-auto-wizard 0.12.2 → 0.13.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.ja.md +165 -0
- package/README.ko.md +165 -0
- package/README.md +107 -286
- package/README.zh-CN.md +165 -0
- package/bin/project-auto-wizard.js +1 -2
- package/package.json +7 -3
- package/payload/config/breaking-changes.json +24 -0
- package/payload/config/wizard-prompts.yml +45 -0
- package/payload/scripts/changelog_manager.py +141 -46
- package/payload/scripts/issue_helper.py +34 -5
- package/payload/scripts/version_manager.py +397 -97
- package/payload/version.yml.template +4 -2
- package/payload/workflows/common/PROJECT-COMMON-AI-PR-SUMMARY.yaml +39 -9
- package/payload/workflows/common/PROJECT-COMMON-AUTO-CHANGELOG-CONTROL.yaml +165 -38
- package/payload/workflows/common/PROJECT-COMMON-README-VERSION-UPDATE.yaml +14 -5
- package/payload/workflows/common/PROJECT-COMMON-RELEASE-PUBLISH.yaml +220 -6
- package/payload/workflows/common/PROJECT-COMMON-VERSION-CONTROL.yaml +89 -3
- package/payload/workflows/flutter/PROJECT-FLUTTER-ANDROID-FIREBASE-CICD.yaml +78 -16
- package/payload/workflows/flutter/PROJECT-FLUTTER-ANDROID-PLAYSTORE-CICD.yaml +72 -16
- package/payload/workflows/flutter/PROJECT-FLUTTER-ANDROID-SELFHOSTED-CICD.yaml +62 -20
- package/payload/workflows/flutter/PROJECT-FLUTTER-ANDROID-TEST-APK.yaml +51 -13
- package/payload/workflows/flutter/PROJECT-FLUTTER-CI.yaml +75 -11
- package/payload/workflows/flutter/PROJECT-FLUTTER-IOS-TEST-TESTFLIGHT.yaml +52 -3
- package/payload/workflows/flutter/PROJECT-FLUTTER-IOS-TESTFLIGHT.yaml +60 -11
- package/payload/workflows/go/PROJECT-GO-CI.yaml +10 -1
- package/payload/workflows/go/PROJECT-GO-PR-PREVIEW.yaml +148 -28
- package/payload/workflows/go/PROJECT-GO-SIMPLE-CICD.yaml +45 -4
- package/payload/workflows/next/PROJECT-NEXT-CI.yaml +25 -3
- package/payload/workflows/next/PROJECT-NEXT-CICD.yaml +83 -9
- package/payload/workflows/python/PROJECT-PYTHON-CI.yaml +70 -6
- package/payload/workflows/python/PROJECT-PYTHON-PR-PREVIEW.yaml +148 -28
- package/payload/workflows/python/PROJECT-PYTHON-SIMPLE-CICD.yaml +45 -4
- package/payload/workflows/react/PROJECT-REACT-CI.yaml +25 -3
- package/payload/workflows/react/PROJECT-REACT-CICD.yaml +84 -10
- package/payload/workflows/spring/PROJECT-SPRING-CI.yml +13 -3
- package/payload/workflows/spring/server-deploy/PROJECT-SPRING-NONSTOP-NGINX-CICD.yaml +47 -5
- package/payload/workflows/spring/server-deploy/PROJECT-SPRING-NONSTOP-TRAEFIK-CICD.yaml +47 -5
- package/payload/workflows/spring/server-deploy/PROJECT-SPRING-PR-PREVIEW.yaml +153 -30
- package/payload/workflows/spring/server-deploy/PROJECT-SPRING-SIMPLE-CICD.yaml +47 -5
- package/src/cli/args.js +33 -24
- package/src/cli/help.js +16 -4
- package/src/commands/doctor.js +17 -10
- package/src/commands/dry-run.js +54 -1
- package/src/commands/full.js +127 -50
- package/src/commands/interactive-flutter.js +1 -1
- package/src/commands/interactive.js +124 -56
- package/src/commands/purge.js +59 -17
- package/src/commands/status.js +16 -1
- package/src/commands/uninstall.js +58 -13
- package/src/context.js +5 -5
- package/src/core/baseline.js +20 -5
- package/src/core/branches.js +19 -1
- package/src/core/branding.js +1 -1
- package/src/core/breaking-check.js +14 -19
- package/src/core/breaking.js +7 -3
- package/src/core/copy/gitignore.js +20 -12
- package/src/core/copy/readme.js +24 -5
- package/src/core/copy/simple.js +52 -13
- package/src/core/copy/workflows.js +90 -47
- package/src/core/deploy-style.js +83 -11
- package/src/core/detect-fs.js +127 -24
- package/src/core/detect.js +131 -47
- package/src/core/errors.js +3 -0
- package/src/core/flutter-options.js +4 -3
- package/src/core/fsutil.js +23 -6
- package/src/core/logger.js +55 -19
- package/src/core/paths-resolve.js +50 -47
- package/src/core/paths.js +26 -1
- package/src/core/release-options.js +23 -0
- package/src/core/removal-plan.js +75 -19
- package/src/core/types.js +113 -0
- package/src/core/verify.js +85 -19
- package/src/core/version-yml.js +42 -8
- package/src/core/wizard-env.js +21 -14
- package/src/core/wizard-labels.js +34 -25
- package/src/index.js +122 -49
- package/src/ui/ansi.js +3 -1
- package/src/ui/banner.js +12 -8
- package/src/ui/env-plan.js +68 -44
- package/src/ui/prompts.js +22 -12
- package/src/ui/readline-engine.js +109 -22
- package/src/ui/status-cards.js +10 -5
- package/src/ui/summary.js +71 -33
- 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
|
@@ -27,6 +27,10 @@ name: PROJECT-SPRING-PR-PREVIEW
|
|
|
27
27
|
env:
|
|
28
28
|
# 프로젝트 고유 식별자 (컨테이너명, 이미지명, 도메인에 사용) (프로젝트별 수정 필요)
|
|
29
29
|
PROJECT_NAME: "__PROJECT_NAME__" # @wizard ask:@repo
|
|
30
|
+
# 모노레포(--paths)에서 이 프로젝트가 있는 하위 경로. 빌드·Docker context의 기준 ('.'이면 레포 루트)
|
|
31
|
+
PROJECT_PATH: "." # @wizard auto:project-path
|
|
32
|
+
# 모노레포(PROJECT_PATH가 '.'이 아님)에서는 이미지·컨테이너·도메인 이름에 '-spring'를 붙여
|
|
33
|
+
# 같은 레포의 다른 타입 서비스와 이름이 겹치지 않게 한다 (예: my-app-spring)
|
|
30
34
|
|
|
31
35
|
# Spring Boot 빌드 설정 (프로젝트별 수정 필요)
|
|
32
36
|
JAVA_VERSION: "__JAVA_VERSION__" # @wizard ask:@jdk
|
|
@@ -35,7 +39,7 @@ env:
|
|
|
35
39
|
APPLICATION_YML_PATH: "__APPLICATION_YML_PATH__" # @wizard auto:spring-app-yml-path
|
|
36
40
|
|
|
37
41
|
# Docker 설정 (프로젝트별 수정 필요)
|
|
38
|
-
DOCKERFILE_PATH: './Dockerfile'
|
|
42
|
+
DOCKERFILE_PATH: './Dockerfile' # PROJECT_PATH 기준 상대 경로
|
|
39
43
|
INTERNAL_PORT: '8080'
|
|
40
44
|
|
|
41
45
|
# 볼륨 마운트 설정 (프로젝트별 수정 필요, 빈값이면 마운트 안함)
|
|
@@ -231,6 +235,42 @@ jobs:
|
|
|
231
235
|
with:
|
|
232
236
|
ref: ${{ steps.pr.outputs.ref }}
|
|
233
237
|
|
|
238
|
+
# 필수 Secret·Dockerfile이 없으면 docker 로그인 같은 서드파티 오류 대신 빠진 항목을 알려 주고 멈춘다
|
|
239
|
+
- name: 배포 사전 점검 (Secret·Dockerfile)
|
|
240
|
+
working-directory: ${{ env.PROJECT_PATH }}
|
|
241
|
+
env:
|
|
242
|
+
DOCKERHUB_USERNAME: ${{ secrets.DOCKERHUB_USERNAME }}
|
|
243
|
+
DOCKERHUB_TOKEN: ${{ secrets.DOCKERHUB_TOKEN }}
|
|
244
|
+
SERVER_HOST: ${{ secrets.SERVER_HOST }}
|
|
245
|
+
SERVER_USER: ${{ secrets.SERVER_USER }}
|
|
246
|
+
SERVER_PASSWORD: ${{ secrets.SERVER_PASSWORD }}
|
|
247
|
+
SSH_KEY: ${{ secrets.SSH_KEY }}
|
|
248
|
+
DOCKERFILE: ${{ env.DOCKERFILE_PATH }}
|
|
249
|
+
run: |
|
|
250
|
+
MISSING=""
|
|
251
|
+
for NAME in DOCKERHUB_USERNAME DOCKERHUB_TOKEN SERVER_HOST SERVER_USER; do
|
|
252
|
+
[ -n "${!NAME}" ] || MISSING="$MISSING $NAME"
|
|
253
|
+
done
|
|
254
|
+
# SSH_AUTH_METHOD=key면 SSH_KEY, 아니면 SERVER_PASSWORD가 필요하다
|
|
255
|
+
if [ "${SSH_AUTH_METHOD:-password}" = "key" ]; then
|
|
256
|
+
[ -n "$SSH_KEY" ] || MISSING="$MISSING SSH_KEY"
|
|
257
|
+
else
|
|
258
|
+
[ -n "$SERVER_PASSWORD" ] || MISSING="$MISSING SERVER_PASSWORD"
|
|
259
|
+
fi
|
|
260
|
+
FAILED=false
|
|
261
|
+
if [ -n "$MISSING" ]; then
|
|
262
|
+
echo "::error title=필수 Secret 누락::등록되지 않은 GitHub Secret:$MISSING — Settings → Secrets and variables → Actions에서 등록하세요"
|
|
263
|
+
FAILED=true
|
|
264
|
+
fi
|
|
265
|
+
if [ ! -f "$DOCKERFILE" ]; then
|
|
266
|
+
echo "::error title=Dockerfile 없음::${PROJECT_PATH}/${DOCKERFILE#./} 파일이 없습니다. 이 워크플로우는 Docker 이미지로 배포하므로 Dockerfile이 필요합니다 (서버 배포를 하지 않는다면 이 워크플로우 파일을 삭제하세요)"
|
|
267
|
+
FAILED=true
|
|
268
|
+
fi
|
|
269
|
+
if [ "$FAILED" = "true" ]; then
|
|
270
|
+
exit 1
|
|
271
|
+
fi
|
|
272
|
+
echo "✅ 사전 점검 통과: 필수 Secret과 Dockerfile이 모두 있습니다"
|
|
273
|
+
|
|
234
274
|
- name: JDK 설정
|
|
235
275
|
uses: actions/setup-java@v5
|
|
236
276
|
with:
|
|
@@ -239,6 +279,7 @@ jobs:
|
|
|
239
279
|
cache: 'gradle'
|
|
240
280
|
|
|
241
281
|
- name: Gradle 권한 설정
|
|
282
|
+
working-directory: ${{ env.PROJECT_PATH }}
|
|
242
283
|
run: chmod +x gradlew
|
|
243
284
|
|
|
244
285
|
# [영역 2] 프로젝트별 Secret 파일 생성
|
|
@@ -280,6 +321,7 @@ jobs:
|
|
|
280
321
|
# [영역 2 끝] 프로젝트별 Secret 파일 생성 끝
|
|
281
322
|
|
|
282
323
|
- name: Gradle 빌드
|
|
324
|
+
working-directory: ${{ env.PROJECT_PATH }}
|
|
283
325
|
run: ${{ env.GRADLE_BUILD_CMD }}
|
|
284
326
|
|
|
285
327
|
- name: 진행 상황 - 빌드 완료
|
|
@@ -333,10 +375,10 @@ jobs:
|
|
|
333
375
|
- name: Docker 이미지 빌드 & Push
|
|
334
376
|
uses: docker/build-push-action@v5
|
|
335
377
|
with:
|
|
336
|
-
context: .
|
|
337
|
-
file: ${{ env.DOCKERFILE_PATH }}
|
|
378
|
+
context: ${{ env.PROJECT_PATH }}
|
|
379
|
+
file: ${{ env.PROJECT_PATH }}/${{ env.DOCKERFILE_PATH }}
|
|
338
380
|
push: true
|
|
339
|
-
tags: ${{ secrets.DOCKERHUB_USERNAME }}/${{ env.PROJECT_NAME }}:pr-${{ github.event.issue.number }}
|
|
381
|
+
tags: ${{ secrets.DOCKERHUB_USERNAME }}/${{ env.PROJECT_PATH != '.' && format('{0}-spring', env.PROJECT_NAME) || env.PROJECT_NAME }}:pr-${{ github.event.issue.number }}
|
|
340
382
|
cache-from: type=gha
|
|
341
383
|
cache-to: type=gha,mode=max
|
|
342
384
|
|
|
@@ -406,7 +448,7 @@ jobs:
|
|
|
406
448
|
echo "🔐 SSH 인증 방식: ${SSH_AUTH_METHOD}"
|
|
407
449
|
|
|
408
450
|
PR_NUMBER=${{ github.event.issue.number }}
|
|
409
|
-
PROJECT_NAME="${{ env.PROJECT_NAME }}"
|
|
451
|
+
PROJECT_NAME="${{ env.PROJECT_PATH != '.' && format('{0}-spring', env.PROJECT_NAME) || env.PROJECT_NAME }}"
|
|
410
452
|
CONTAINER_NAME="${PROJECT_NAME}-pr-${PR_NUMBER}"
|
|
411
453
|
IMAGE="${{ secrets.DOCKERHUB_USERNAME }}/${PROJECT_NAME}:pr-${PR_NUMBER}"
|
|
412
454
|
DOMAIN="${PROJECT_NAME}-pr-${PR_NUMBER}.${{ env.PREVIEW_DOMAIN_SUFFIX }}"
|
|
@@ -535,7 +577,7 @@ jobs:
|
|
|
535
577
|
const seconds = Math.floor((deployElapsed % 60000) / 1000);
|
|
536
578
|
const deployDuration = minutes > 0 ? `${minutes}분 ${seconds}초` : `${seconds}초`;
|
|
537
579
|
|
|
538
|
-
const projectName = '${{ env.PROJECT_NAME }}';
|
|
580
|
+
const projectName = '${{ env.PROJECT_PATH != '.' && format('{0}-spring', env.PROJECT_NAME) || env.PROJECT_NAME }}';
|
|
539
581
|
const domainSuffix = '${{ env.PREVIEW_DOMAIN_SUFFIX }}';
|
|
540
582
|
const previewPort = '${{ env.PREVIEW_PORT }}';
|
|
541
583
|
const apiDocsPath = '${{ env.API_DOCS_PATH }}';
|
|
@@ -604,7 +646,7 @@ jobs:
|
|
|
604
646
|
with:
|
|
605
647
|
script: |
|
|
606
648
|
const commentId = ${{ steps.progress.outputs.comment_id || 0 }};
|
|
607
|
-
const projectName = '${{ env.PROJECT_NAME }}';
|
|
649
|
+
const projectName = '${{ env.PROJECT_PATH != '.' && format('{0}-spring', env.PROJECT_NAME) || env.PROJECT_NAME }}';
|
|
608
650
|
const prNumber = context.issue.number;
|
|
609
651
|
const runUrl = `https://github.com/${context.repo.owner}/${context.repo.repo}/actions/runs/${context.runId}`;
|
|
610
652
|
|
|
@@ -653,6 +695,7 @@ jobs:
|
|
|
653
695
|
`**[📋 빌드/배포 로그 확인](${runUrl})**`,
|
|
654
696
|
'',
|
|
655
697
|
'### 🔍 가능한 원인',
|
|
698
|
+
'- 필수 Secret(DOCKERHUB_*, SERVER_*) 미등록 또는 Dockerfile 없음 → 로그의 "배포 사전 점검" 단계 확인',
|
|
656
699
|
'- Gradle 빌드 실패',
|
|
657
700
|
'- Docker 이미지 빌드 실패',
|
|
658
701
|
'- 컨테이너 시작 실패 (Spring Boot 기동 오류)',
|
|
@@ -894,6 +937,43 @@ jobs:
|
|
|
894
937
|
with:
|
|
895
938
|
ref: ${{ needs.get-branch-from-issue.outputs.branch_name }}
|
|
896
939
|
|
|
940
|
+
# 필수 Secret·Dockerfile이 없으면 docker 로그인 같은 서드파티 오류 대신 빠진 항목을 알려 주고 멈춘다
|
|
941
|
+
- name: 배포 사전 점검 (Secret·Dockerfile)
|
|
942
|
+
if: steps.check_branch.outputs.exists == 'true'
|
|
943
|
+
working-directory: ${{ env.PROJECT_PATH }}
|
|
944
|
+
env:
|
|
945
|
+
DOCKERHUB_USERNAME: ${{ secrets.DOCKERHUB_USERNAME }}
|
|
946
|
+
DOCKERHUB_TOKEN: ${{ secrets.DOCKERHUB_TOKEN }}
|
|
947
|
+
SERVER_HOST: ${{ secrets.SERVER_HOST }}
|
|
948
|
+
SERVER_USER: ${{ secrets.SERVER_USER }}
|
|
949
|
+
SERVER_PASSWORD: ${{ secrets.SERVER_PASSWORD }}
|
|
950
|
+
SSH_KEY: ${{ secrets.SSH_KEY }}
|
|
951
|
+
DOCKERFILE: ${{ env.DOCKERFILE_PATH }}
|
|
952
|
+
run: |
|
|
953
|
+
MISSING=""
|
|
954
|
+
for NAME in DOCKERHUB_USERNAME DOCKERHUB_TOKEN SERVER_HOST SERVER_USER; do
|
|
955
|
+
[ -n "${!NAME}" ] || MISSING="$MISSING $NAME"
|
|
956
|
+
done
|
|
957
|
+
# SSH_AUTH_METHOD=key면 SSH_KEY, 아니면 SERVER_PASSWORD가 필요하다
|
|
958
|
+
if [ "${SSH_AUTH_METHOD:-password}" = "key" ]; then
|
|
959
|
+
[ -n "$SSH_KEY" ] || MISSING="$MISSING SSH_KEY"
|
|
960
|
+
else
|
|
961
|
+
[ -n "$SERVER_PASSWORD" ] || MISSING="$MISSING SERVER_PASSWORD"
|
|
962
|
+
fi
|
|
963
|
+
FAILED=false
|
|
964
|
+
if [ -n "$MISSING" ]; then
|
|
965
|
+
echo "::error title=필수 Secret 누락::등록되지 않은 GitHub Secret:$MISSING — Settings → Secrets and variables → Actions에서 등록하세요"
|
|
966
|
+
FAILED=true
|
|
967
|
+
fi
|
|
968
|
+
if [ ! -f "$DOCKERFILE" ]; then
|
|
969
|
+
echo "::error title=Dockerfile 없음::${PROJECT_PATH}/${DOCKERFILE#./} 파일이 없습니다. 이 워크플로우는 Docker 이미지로 배포하므로 Dockerfile이 필요합니다 (서버 배포를 하지 않는다면 이 워크플로우 파일을 삭제하세요)"
|
|
970
|
+
FAILED=true
|
|
971
|
+
fi
|
|
972
|
+
if [ "$FAILED" = "true" ]; then
|
|
973
|
+
exit 1
|
|
974
|
+
fi
|
|
975
|
+
echo "✅ 사전 점검 통과: 필수 Secret과 Dockerfile이 모두 있습니다"
|
|
976
|
+
|
|
897
977
|
- name: JDK 설정
|
|
898
978
|
if: steps.check_branch.outputs.exists == 'true'
|
|
899
979
|
uses: actions/setup-java@v5
|
|
@@ -903,6 +983,7 @@ jobs:
|
|
|
903
983
|
cache: 'gradle'
|
|
904
984
|
|
|
905
985
|
- name: Gradle 권한 설정
|
|
986
|
+
working-directory: ${{ env.PROJECT_PATH }}
|
|
906
987
|
if: steps.check_branch.outputs.exists == 'true'
|
|
907
988
|
run: chmod +x gradlew
|
|
908
989
|
|
|
@@ -917,6 +998,7 @@ jobs:
|
|
|
917
998
|
# [영역 2 끝] 프로젝트별 Secret 파일 생성 끝
|
|
918
999
|
|
|
919
1000
|
- name: Gradle 빌드
|
|
1001
|
+
working-directory: ${{ env.PROJECT_PATH }}
|
|
920
1002
|
if: steps.check_branch.outputs.exists == 'true'
|
|
921
1003
|
run: ${{ env.GRADLE_BUILD_CMD }}
|
|
922
1004
|
|
|
@@ -978,10 +1060,10 @@ jobs:
|
|
|
978
1060
|
if: steps.check_branch.outputs.exists == 'true'
|
|
979
1061
|
uses: docker/build-push-action@v5
|
|
980
1062
|
with:
|
|
981
|
-
context: .
|
|
982
|
-
file: ${{ env.DOCKERFILE_PATH }}
|
|
1063
|
+
context: ${{ env.PROJECT_PATH }}
|
|
1064
|
+
file: ${{ env.PROJECT_PATH }}/${{ env.DOCKERFILE_PATH }}
|
|
983
1065
|
push: true
|
|
984
|
-
tags: ${{ secrets.DOCKERHUB_USERNAME }}/${{ env.PROJECT_NAME }}:pr-${{ needs.get-branch-from-issue.outputs.issue_number }}
|
|
1066
|
+
tags: ${{ secrets.DOCKERHUB_USERNAME }}/${{ env.PROJECT_PATH != '.' && format('{0}-spring', env.PROJECT_NAME) || env.PROJECT_NAME }}:pr-${{ needs.get-branch-from-issue.outputs.issue_number }}
|
|
985
1067
|
cache-from: type=gha
|
|
986
1068
|
cache-to: type=gha,mode=max
|
|
987
1069
|
|
|
@@ -1056,7 +1138,7 @@ jobs:
|
|
|
1056
1138
|
echo "🔐 SSH 인증 방식: ${SSH_AUTH_METHOD}"
|
|
1057
1139
|
|
|
1058
1140
|
ISSUE_NUMBER=${{ needs.get-branch-from-issue.outputs.issue_number }}
|
|
1059
|
-
PROJECT_NAME="${{ env.PROJECT_NAME }}"
|
|
1141
|
+
PROJECT_NAME="${{ env.PROJECT_PATH != '.' && format('{0}-spring', env.PROJECT_NAME) || env.PROJECT_NAME }}"
|
|
1060
1142
|
CONTAINER_NAME="${PROJECT_NAME}-pr-${ISSUE_NUMBER}"
|
|
1061
1143
|
IMAGE="${{ secrets.DOCKERHUB_USERNAME }}/${PROJECT_NAME}:pr-${ISSUE_NUMBER}"
|
|
1062
1144
|
DOMAIN="${PROJECT_NAME}-pr-${ISSUE_NUMBER}.${{ env.PREVIEW_DOMAIN_SUFFIX }}"
|
|
@@ -1187,7 +1269,7 @@ jobs:
|
|
|
1187
1269
|
const seconds = Math.floor((deployElapsed % 60000) / 1000);
|
|
1188
1270
|
const deployDuration = minutes > 0 ? `${minutes}분 ${seconds}초` : `${seconds}초`;
|
|
1189
1271
|
|
|
1190
|
-
const projectName = '${{ env.PROJECT_NAME }}';
|
|
1272
|
+
const projectName = '${{ env.PROJECT_PATH != '.' && format('{0}-spring', env.PROJECT_NAME) || env.PROJECT_NAME }}';
|
|
1191
1273
|
const domainSuffix = '${{ env.PREVIEW_DOMAIN_SUFFIX }}';
|
|
1192
1274
|
const previewPort = '${{ env.PREVIEW_PORT }}';
|
|
1193
1275
|
const apiDocsPath = '${{ env.API_DOCS_PATH }}';
|
|
@@ -1257,7 +1339,7 @@ jobs:
|
|
|
1257
1339
|
with:
|
|
1258
1340
|
script: |
|
|
1259
1341
|
const commentId = ${{ steps.progress.outputs.comment_id || 0 }};
|
|
1260
|
-
const projectName = '${{ env.PROJECT_NAME }}';
|
|
1342
|
+
const projectName = '${{ env.PROJECT_PATH != '.' && format('{0}-spring', env.PROJECT_NAME) || env.PROJECT_NAME }}';
|
|
1261
1343
|
const issueNumber = ${{ needs.get-branch-from-issue.outputs.issue_number }};
|
|
1262
1344
|
const branchName = '${{ needs.get-branch-from-issue.outputs.branch_name }}';
|
|
1263
1345
|
const runUrl = `https://github.com/${context.repo.owner}/${context.repo.repo}/actions/runs/${context.runId}`;
|
|
@@ -1303,6 +1385,7 @@ jobs:
|
|
|
1303
1385
|
`**[📋 빌드/배포 로그 확인](${runUrl})**`,
|
|
1304
1386
|
'',
|
|
1305
1387
|
'### 🔍 가능한 원인',
|
|
1388
|
+
'- 필수 Secret(DOCKERHUB_*, SERVER_*) 미등록 또는 Dockerfile 없음 → 로그의 "배포 사전 점검" 단계 확인',
|
|
1306
1389
|
'- Gradle 빌드 실패',
|
|
1307
1390
|
'- Docker 이미지 빌드 실패',
|
|
1308
1391
|
'- 컨테이너 시작 실패 (Spring Boot 기동 오류)',
|
|
@@ -1379,7 +1462,7 @@ jobs:
|
|
|
1379
1462
|
echo "🔐 SSH 인증 방식: ${SSH_AUTH_METHOD}"
|
|
1380
1463
|
|
|
1381
1464
|
PR_NUMBER=${{ steps.pr_number.outputs.number }}
|
|
1382
|
-
PROJECT_NAME="${{ env.PROJECT_NAME }}"
|
|
1465
|
+
PROJECT_NAME="${{ env.PROJECT_PATH != '.' && format('{0}-spring', env.PROJECT_NAME) || env.PROJECT_NAME }}"
|
|
1383
1466
|
CONTAINER_NAME="${PROJECT_NAME}-pr-${PR_NUMBER}"
|
|
1384
1467
|
IMAGE="${{ secrets.DOCKERHUB_USERNAME }}/${PROJECT_NAME}:pr-${PR_NUMBER}"
|
|
1385
1468
|
|
|
@@ -1407,7 +1490,7 @@ jobs:
|
|
|
1407
1490
|
with:
|
|
1408
1491
|
script: |
|
|
1409
1492
|
const number = ${{ steps.pr_number.outputs.number }};
|
|
1410
|
-
const projectName = '${{ env.PROJECT_NAME }}';
|
|
1493
|
+
const projectName = '${{ env.PROJECT_PATH != '.' && format('{0}-spring', env.PROJECT_NAME) || env.PROJECT_NAME }}';
|
|
1411
1494
|
|
|
1412
1495
|
const body = [
|
|
1413
1496
|
'## 🗑️ Preview 환경 삭제 완료!',
|
|
@@ -1450,7 +1533,7 @@ jobs:
|
|
|
1450
1533
|
export PATH=$PATH:/usr/local/bin
|
|
1451
1534
|
|
|
1452
1535
|
NUMBER=${{ github.event.issue.number }}
|
|
1453
|
-
PROJECT_NAME="${{ env.PROJECT_NAME }}"
|
|
1536
|
+
PROJECT_NAME="${{ env.PROJECT_PATH != '.' && format('{0}-spring', env.PROJECT_NAME) || env.PROJECT_NAME }}"
|
|
1454
1537
|
CONTAINER_NAME="${PROJECT_NAME}-pr-${NUMBER}"
|
|
1455
1538
|
|
|
1456
1539
|
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
|
@@ -1480,7 +1563,7 @@ jobs:
|
|
|
1480
1563
|
export PATH=$PATH:/usr/local/bin
|
|
1481
1564
|
|
|
1482
1565
|
NUMBER=${{ github.event.issue.number }}
|
|
1483
|
-
PROJECT_NAME="${{ env.PROJECT_NAME }}"
|
|
1566
|
+
PROJECT_NAME="${{ env.PROJECT_PATH != '.' && format('{0}-spring', env.PROJECT_NAME) || env.PROJECT_NAME }}"
|
|
1484
1567
|
CONTAINER_NAME="${PROJECT_NAME}-pr-${NUMBER}"
|
|
1485
1568
|
docker ps --filter "name=${CONTAINER_NAME}" --format "{{.Names}}" | grep -q "${CONTAINER_NAME}"
|
|
1486
1569
|
|
|
@@ -1489,7 +1572,7 @@ jobs:
|
|
|
1489
1572
|
with:
|
|
1490
1573
|
script: |
|
|
1491
1574
|
const number = context.issue.number;
|
|
1492
|
-
const projectName = '${{ env.PROJECT_NAME }}';
|
|
1575
|
+
const projectName = '${{ env.PROJECT_PATH != '.' && format('{0}-spring', env.PROJECT_NAME) || env.PROJECT_NAME }}';
|
|
1493
1576
|
const domainSuffix = '${{ env.PREVIEW_DOMAIN_SUFFIX }}';
|
|
1494
1577
|
const previewPort = '${{ env.PREVIEW_PORT }}';
|
|
1495
1578
|
const domain = `${projectName}-pr-${number}.${domainSuffix}`;
|
|
@@ -1640,6 +1723,43 @@ jobs:
|
|
|
1640
1723
|
with:
|
|
1641
1724
|
ref: ${{ steps.extract.outputs.branch_name }}
|
|
1642
1725
|
|
|
1726
|
+
# 필수 Secret·Dockerfile이 없으면 docker 로그인 같은 서드파티 오류 대신 빠진 항목을 알려 주고 멈춘다
|
|
1727
|
+
- name: 배포 사전 점검 (Secret·Dockerfile)
|
|
1728
|
+
if: steps.check_branch.outputs.exists == 'true'
|
|
1729
|
+
working-directory: ${{ env.PROJECT_PATH }}
|
|
1730
|
+
env:
|
|
1731
|
+
DOCKERHUB_USERNAME: ${{ secrets.DOCKERHUB_USERNAME }}
|
|
1732
|
+
DOCKERHUB_TOKEN: ${{ secrets.DOCKERHUB_TOKEN }}
|
|
1733
|
+
SERVER_HOST: ${{ secrets.SERVER_HOST }}
|
|
1734
|
+
SERVER_USER: ${{ secrets.SERVER_USER }}
|
|
1735
|
+
SERVER_PASSWORD: ${{ secrets.SERVER_PASSWORD }}
|
|
1736
|
+
SSH_KEY: ${{ secrets.SSH_KEY }}
|
|
1737
|
+
DOCKERFILE: ${{ env.DOCKERFILE_PATH }}
|
|
1738
|
+
run: |
|
|
1739
|
+
MISSING=""
|
|
1740
|
+
for NAME in DOCKERHUB_USERNAME DOCKERHUB_TOKEN SERVER_HOST SERVER_USER; do
|
|
1741
|
+
[ -n "${!NAME}" ] || MISSING="$MISSING $NAME"
|
|
1742
|
+
done
|
|
1743
|
+
# SSH_AUTH_METHOD=key면 SSH_KEY, 아니면 SERVER_PASSWORD가 필요하다
|
|
1744
|
+
if [ "${SSH_AUTH_METHOD:-password}" = "key" ]; then
|
|
1745
|
+
[ -n "$SSH_KEY" ] || MISSING="$MISSING SSH_KEY"
|
|
1746
|
+
else
|
|
1747
|
+
[ -n "$SERVER_PASSWORD" ] || MISSING="$MISSING SERVER_PASSWORD"
|
|
1748
|
+
fi
|
|
1749
|
+
FAILED=false
|
|
1750
|
+
if [ -n "$MISSING" ]; then
|
|
1751
|
+
echo "::error title=필수 Secret 누락::등록되지 않은 GitHub Secret:$MISSING — Settings → Secrets and variables → Actions에서 등록하세요"
|
|
1752
|
+
FAILED=true
|
|
1753
|
+
fi
|
|
1754
|
+
if [ ! -f "$DOCKERFILE" ]; then
|
|
1755
|
+
echo "::error title=Dockerfile 없음::${PROJECT_PATH}/${DOCKERFILE#./} 파일이 없습니다. 이 워크플로우는 Docker 이미지로 배포하므로 Dockerfile이 필요합니다 (서버 배포를 하지 않는다면 이 워크플로우 파일을 삭제하세요)"
|
|
1756
|
+
FAILED=true
|
|
1757
|
+
fi
|
|
1758
|
+
if [ "$FAILED" = "true" ]; then
|
|
1759
|
+
exit 1
|
|
1760
|
+
fi
|
|
1761
|
+
echo "✅ 사전 점검 통과: 필수 Secret과 Dockerfile이 모두 있습니다"
|
|
1762
|
+
|
|
1643
1763
|
- name: 최신 커밋 정보 가져오기
|
|
1644
1764
|
if: steps.check_branch.outputs.exists == 'true'
|
|
1645
1765
|
id: commit
|
|
@@ -1651,7 +1771,7 @@ jobs:
|
|
|
1651
1771
|
uses: actions/github-script@v9
|
|
1652
1772
|
with:
|
|
1653
1773
|
script: |
|
|
1654
|
-
const projectName = '${{ env.PROJECT_NAME }}';
|
|
1774
|
+
const projectName = '${{ env.PROJECT_PATH != '.' && format('{0}-spring', env.PROJECT_NAME) || env.PROJECT_NAME }}';
|
|
1655
1775
|
const containerSuffix = '${{ steps.extract.outputs.container_suffix }}';
|
|
1656
1776
|
const branchName = '${{ steps.extract.outputs.branch_name }}';
|
|
1657
1777
|
const sha = '${{ steps.commit.outputs.sha }}';
|
|
@@ -1700,6 +1820,7 @@ jobs:
|
|
|
1700
1820
|
${{ runner.os }}-gradle-
|
|
1701
1821
|
|
|
1702
1822
|
- name: Gradle 빌드
|
|
1823
|
+
working-directory: ${{ env.PROJECT_PATH }}
|
|
1703
1824
|
if: steps.check_branch.outputs.exists == 'true'
|
|
1704
1825
|
id: build
|
|
1705
1826
|
run: |
|
|
@@ -1717,7 +1838,7 @@ jobs:
|
|
|
1717
1838
|
with:
|
|
1718
1839
|
script: |
|
|
1719
1840
|
const commentId = ${{ steps.progress.outputs.comment_id }};
|
|
1720
|
-
const projectName = '${{ env.PROJECT_NAME }}';
|
|
1841
|
+
const projectName = '${{ env.PROJECT_PATH != '.' && format('{0}-spring', env.PROJECT_NAME) || env.PROJECT_NAME }}';
|
|
1721
1842
|
const branchName = '${{ steps.extract.outputs.branch_name }}';
|
|
1722
1843
|
const sha = '${{ steps.commit.outputs.sha }}';
|
|
1723
1844
|
const buildDuration = '${{ steps.build.outputs.duration }}';
|
|
@@ -1755,13 +1876,14 @@ jobs:
|
|
|
1755
1876
|
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
|
1756
1877
|
|
|
1757
1878
|
- name: Docker 이미지 빌드 & 푸시
|
|
1879
|
+
working-directory: ${{ env.PROJECT_PATH }}
|
|
1758
1880
|
if: steps.check_branch.outputs.exists == 'true'
|
|
1759
1881
|
id: docker
|
|
1760
1882
|
run: |
|
|
1761
1883
|
DOCKER_START=$(date +%s)
|
|
1762
1884
|
CONTAINER_SUFFIX="${{ steps.extract.outputs.container_suffix }}"
|
|
1763
|
-
IMAGE_TAG="${{ secrets.DOCKERHUB_USERNAME }}/${{ env.PROJECT_NAME }}:${CONTAINER_SUFFIX}"
|
|
1764
|
-
docker build -t $IMAGE_TAG .
|
|
1885
|
+
IMAGE_TAG="${{ secrets.DOCKERHUB_USERNAME }}/${{ env.PROJECT_PATH != '.' && format('{0}-spring', env.PROJECT_NAME) || env.PROJECT_NAME }}:${CONTAINER_SUFFIX}"
|
|
1886
|
+
docker build -t $IMAGE_TAG -f "${{ env.DOCKERFILE_PATH }}" .
|
|
1765
1887
|
docker push $IMAGE_TAG
|
|
1766
1888
|
DOCKER_END=$(date +%s)
|
|
1767
1889
|
DOCKER_DURATION=$((DOCKER_END - DOCKER_START))
|
|
@@ -1775,7 +1897,7 @@ jobs:
|
|
|
1775
1897
|
with:
|
|
1776
1898
|
script: |
|
|
1777
1899
|
const commentId = ${{ steps.progress.outputs.comment_id }};
|
|
1778
|
-
const projectName = '${{ env.PROJECT_NAME }}';
|
|
1900
|
+
const projectName = '${{ env.PROJECT_PATH != '.' && format('{0}-spring', env.PROJECT_NAME) || env.PROJECT_NAME }}';
|
|
1779
1901
|
const branchName = '${{ steps.extract.outputs.branch_name }}';
|
|
1780
1902
|
const sha = '${{ steps.commit.outputs.sha }}';
|
|
1781
1903
|
const buildDuration = '${{ steps.build_progress.outputs.build_duration }}';
|
|
@@ -1819,7 +1941,7 @@ jobs:
|
|
|
1819
1941
|
script: |
|
|
1820
1942
|
export PATH=$PATH:/usr/local/bin
|
|
1821
1943
|
|
|
1822
|
-
PROJECT_NAME="${{ env.PROJECT_NAME }}"
|
|
1944
|
+
PROJECT_NAME="${{ env.PROJECT_PATH != '.' && format('{0}-spring', env.PROJECT_NAME) || env.PROJECT_NAME }}"
|
|
1823
1945
|
CONTAINER_SUFFIX="${{ steps.extract.outputs.container_suffix }}"
|
|
1824
1946
|
CONTAINER_NAME="${PROJECT_NAME}-${CONTAINER_SUFFIX}"
|
|
1825
1947
|
IMAGE_TAG="${{ steps.docker.outputs.image_tag }}"
|
|
@@ -1877,7 +1999,7 @@ jobs:
|
|
|
1877
1999
|
with:
|
|
1878
2000
|
script: |
|
|
1879
2001
|
const commentId = ${{ steps.progress.outputs.comment_id }};
|
|
1880
|
-
const projectName = '${{ env.PROJECT_NAME }}';
|
|
2002
|
+
const projectName = '${{ env.PROJECT_PATH != '.' && format('{0}-spring', env.PROJECT_NAME) || env.PROJECT_NAME }}';
|
|
1881
2003
|
const containerSuffix = '${{ steps.extract.outputs.container_suffix }}';
|
|
1882
2004
|
const branchName = '${{ steps.extract.outputs.branch_name }}';
|
|
1883
2005
|
const sha = '${{ steps.commit.outputs.sha }}';
|
|
@@ -1942,7 +2064,7 @@ jobs:
|
|
|
1942
2064
|
with:
|
|
1943
2065
|
script: |
|
|
1944
2066
|
const commentId = ${{ steps.progress.outputs.comment_id || 0 }};
|
|
1945
|
-
const projectName = '${{ env.PROJECT_NAME }}';
|
|
2067
|
+
const projectName = '${{ env.PROJECT_PATH != '.' && format('{0}-spring', env.PROJECT_NAME) || env.PROJECT_NAME }}';
|
|
1946
2068
|
const branchName = '${{ steps.extract.outputs.branch_name }}';
|
|
1947
2069
|
const runUrl = `https://github.com/${context.repo.owner}/${context.repo.repo}/actions/runs/${context.runId}`;
|
|
1948
2070
|
|
|
@@ -1987,6 +2109,7 @@ jobs:
|
|
|
1987
2109
|
`**[📋 빌드/배포 로그 확인](${runUrl})**`,
|
|
1988
2110
|
'',
|
|
1989
2111
|
'### 🔍 가능한 원인',
|
|
2112
|
+
'- 필수 Secret(DOCKERHUB_*, SERVER_*) 미등록 또는 Dockerfile 없음 → 로그의 "배포 사전 점검" 단계 확인',
|
|
1990
2113
|
'- Gradle 빌드 실패',
|
|
1991
2114
|
'- Docker 이미지 빌드 실패',
|
|
1992
2115
|
'- 서버 연결 문제',
|
|
@@ -2056,7 +2179,7 @@ jobs:
|
|
|
2056
2179
|
script: |
|
|
2057
2180
|
export PATH=$PATH:/usr/local/bin
|
|
2058
2181
|
|
|
2059
|
-
PROJECT_NAME="${{ env.PROJECT_NAME }}"
|
|
2182
|
+
PROJECT_NAME="${{ env.PROJECT_PATH != '.' && format('{0}-spring', env.PROJECT_NAME) || env.PROJECT_NAME }}"
|
|
2060
2183
|
CONTAINER_SUFFIX="${{ steps.extract.outputs.container_suffix }}"
|
|
2061
2184
|
CONTAINER_NAME="${PROJECT_NAME}-${CONTAINER_SUFFIX}"
|
|
2062
2185
|
|
|
@@ -2078,7 +2201,7 @@ jobs:
|
|
|
2078
2201
|
uses: actions/github-script@v9
|
|
2079
2202
|
with:
|
|
2080
2203
|
script: |
|
|
2081
|
-
const projectName = '${{ env.PROJECT_NAME }}';
|
|
2204
|
+
const projectName = '${{ env.PROJECT_PATH != '.' && format('{0}-spring', env.PROJECT_NAME) || env.PROJECT_NAME }}';
|
|
2082
2205
|
const containerSuffix = '${{ steps.extract.outputs.container_suffix }}';
|
|
2083
2206
|
const branchName = '${{ steps.extract.outputs.branch_name }}';
|
|
2084
2207
|
const containerName = `${projectName}-${containerSuffix}`;
|
|
@@ -2149,7 +2272,7 @@ jobs:
|
|
|
2149
2272
|
script: |
|
|
2150
2273
|
export PATH=$PATH:/usr/local/bin
|
|
2151
2274
|
|
|
2152
|
-
PROJECT_NAME="${{ env.PROJECT_NAME }}"
|
|
2275
|
+
PROJECT_NAME="${{ env.PROJECT_PATH != '.' && format('{0}-spring', env.PROJECT_NAME) || env.PROJECT_NAME }}"
|
|
2153
2276
|
CONTAINER_SUFFIX="${{ steps.extract.outputs.container_suffix }}"
|
|
2154
2277
|
CONTAINER_NAME="${PROJECT_NAME}-${CONTAINER_SUFFIX}"
|
|
2155
2278
|
|
|
@@ -2159,7 +2282,7 @@ jobs:
|
|
|
2159
2282
|
uses: actions/github-script@v9
|
|
2160
2283
|
with:
|
|
2161
2284
|
script: |
|
|
2162
|
-
const projectName = '${{ env.PROJECT_NAME }}';
|
|
2285
|
+
const projectName = '${{ env.PROJECT_PATH != '.' && format('{0}-spring', env.PROJECT_NAME) || env.PROJECT_NAME }}';
|
|
2163
2286
|
const containerSuffix = '${{ steps.extract.outputs.container_suffix }}';
|
|
2164
2287
|
const branchName = '${{ steps.extract.outputs.branch_name }}';
|
|
2165
2288
|
const domainSuffix = '${{ env.PREVIEW_DOMAIN_SUFFIX }}';
|
|
@@ -63,6 +63,10 @@ on:
|
|
|
63
63
|
env:
|
|
64
64
|
# 프로젝트 설정 - 실제 프로젝트명으로 변경하세요
|
|
65
65
|
PROJECT_NAME: "__PROJECT_NAME__" # @wizard ask:@repo
|
|
66
|
+
# 모노레포(--paths)에서 이 프로젝트가 있는 하위 경로. 빌드·Docker context의 기준 ('.'이면 레포 루트)
|
|
67
|
+
PROJECT_PATH: "." # @wizard auto:project-path
|
|
68
|
+
# 모노레포(PROJECT_PATH가 '.'이 아님)에서는 이미지·컨테이너·도메인 이름에 '-spring'를 붙여
|
|
69
|
+
# 같은 레포의 다른 타입 서비스와 이름이 겹치지 않게 한다 (예: my-app-spring)
|
|
66
70
|
|
|
67
71
|
# Spring Boot 설정
|
|
68
72
|
SPRING_PROFILE: "prod"
|
|
@@ -71,7 +75,7 @@ env:
|
|
|
71
75
|
|
|
72
76
|
# 경로 설정
|
|
73
77
|
APPLICATION_YML_DIR: "__APPLICATION_YML_DIR__" # @wizard auto:spring-app-yml-dir
|
|
74
|
-
DOCKERFILE_PATH: "./Dockerfile"
|
|
78
|
+
DOCKERFILE_PATH: "./Dockerfile" # PROJECT_PATH 기준 상대 경로
|
|
75
79
|
|
|
76
80
|
# 포트 설정
|
|
77
81
|
CONTAINER_INTERNAL_PORT: "8080" # Spring Boot 내부 포트 (고정값)
|
|
@@ -116,6 +120,42 @@ jobs:
|
|
|
116
120
|
- name: 코드 체크아웃
|
|
117
121
|
uses: actions/checkout@v7
|
|
118
122
|
|
|
123
|
+
# 필수 Secret·Dockerfile이 없으면 docker 로그인 같은 서드파티 오류 대신 빠진 항목을 알려 주고 멈춘다
|
|
124
|
+
- name: 배포 사전 점검 (Secret·Dockerfile)
|
|
125
|
+
working-directory: ${{ env.PROJECT_PATH }}
|
|
126
|
+
env:
|
|
127
|
+
DOCKERHUB_USERNAME: ${{ secrets.DOCKERHUB_USERNAME }}
|
|
128
|
+
DOCKERHUB_TOKEN: ${{ secrets.DOCKERHUB_TOKEN }}
|
|
129
|
+
SERVER_HOST: ${{ secrets.SERVER_HOST }}
|
|
130
|
+
SERVER_USER: ${{ secrets.SERVER_USER }}
|
|
131
|
+
SERVER_PASSWORD: ${{ secrets.SERVER_PASSWORD }}
|
|
132
|
+
SSH_KEY: ${{ secrets.SSH_KEY }}
|
|
133
|
+
DOCKERFILE: ${{ env.DOCKERFILE_PATH }}
|
|
134
|
+
run: |
|
|
135
|
+
MISSING=""
|
|
136
|
+
for NAME in DOCKERHUB_USERNAME DOCKERHUB_TOKEN SERVER_HOST SERVER_USER; do
|
|
137
|
+
[ -n "${!NAME}" ] || MISSING="$MISSING $NAME"
|
|
138
|
+
done
|
|
139
|
+
# SSH_AUTH_METHOD=key면 SSH_KEY, 아니면 SERVER_PASSWORD가 필요하다
|
|
140
|
+
if [ "${SSH_AUTH_METHOD:-password}" = "key" ]; then
|
|
141
|
+
[ -n "$SSH_KEY" ] || MISSING="$MISSING SSH_KEY"
|
|
142
|
+
else
|
|
143
|
+
[ -n "$SERVER_PASSWORD" ] || MISSING="$MISSING SERVER_PASSWORD"
|
|
144
|
+
fi
|
|
145
|
+
FAILED=false
|
|
146
|
+
if [ -n "$MISSING" ]; then
|
|
147
|
+
echo "::error title=필수 Secret 누락::등록되지 않은 GitHub Secret:$MISSING — Settings → Secrets and variables → Actions에서 등록하세요"
|
|
148
|
+
FAILED=true
|
|
149
|
+
fi
|
|
150
|
+
if [ ! -f "$DOCKERFILE" ]; then
|
|
151
|
+
echo "::error title=Dockerfile 없음::${PROJECT_PATH}/${DOCKERFILE#./} 파일이 없습니다. 이 워크플로우는 Docker 이미지로 배포하므로 Dockerfile이 필요합니다 (서버 배포를 하지 않는다면 이 워크플로우 파일을 삭제하세요)"
|
|
152
|
+
FAILED=true
|
|
153
|
+
fi
|
|
154
|
+
if [ "$FAILED" = "true" ]; then
|
|
155
|
+
exit 1
|
|
156
|
+
fi
|
|
157
|
+
echo "✅ 사전 점검 통과: 필수 Secret과 Dockerfile이 모두 있습니다"
|
|
158
|
+
|
|
119
159
|
- name: Java 설정
|
|
120
160
|
uses: actions/setup-java@v5
|
|
121
161
|
with:
|
|
@@ -124,6 +164,7 @@ jobs:
|
|
|
124
164
|
cache: 'gradle'
|
|
125
165
|
|
|
126
166
|
- name: Gradle Wrapper 실행권한 부여
|
|
167
|
+
working-directory: ${{ env.PROJECT_PATH }}
|
|
127
168
|
run: chmod +x gradlew
|
|
128
169
|
|
|
129
170
|
# GitHub Secrets에서 APPLICATION_PROD_YML 값을 읽어와서
|
|
@@ -140,6 +181,7 @@ jobs:
|
|
|
140
181
|
|
|
141
182
|
# 테스트는 제외하고 운영 프로파일로 빌드
|
|
142
183
|
- name: Build with Gradle
|
|
184
|
+
working-directory: ${{ env.PROJECT_PATH }}
|
|
143
185
|
run: ./gradlew clean build -x test ${{ env.GRADLE_OPTS }}
|
|
144
186
|
|
|
145
187
|
- name: Docker 빌드환경 설정
|
|
@@ -163,10 +205,10 @@ jobs:
|
|
|
163
205
|
- name: Docker 이미지 빌드 및 푸시
|
|
164
206
|
uses: docker/build-push-action@v5
|
|
165
207
|
with:
|
|
166
|
-
context: .
|
|
167
|
-
file: ${{ env.DOCKERFILE_PATH }}
|
|
208
|
+
context: ${{ env.PROJECT_PATH }}
|
|
209
|
+
file: ${{ env.PROJECT_PATH }}/${{ env.DOCKERFILE_PATH }}
|
|
168
210
|
push: true
|
|
169
|
-
tags: ${{ secrets.DOCKERHUB_USERNAME }}/${{ env.PROJECT_NAME }}:${{ github.ref_name }}
|
|
211
|
+
tags: ${{ secrets.DOCKERHUB_USERNAME }}/${{ env.PROJECT_PATH != '.' && format('{0}-spring', env.PROJECT_NAME) || env.PROJECT_NAME }}:${{ github.ref_name }}
|
|
170
212
|
cache-from: type=local,src=/tmp/.buildx-cache
|
|
171
213
|
cache-to: type=local,dest=/tmp/.buildx-cache-new,mode=max
|
|
172
214
|
|
|
@@ -214,7 +256,7 @@ jobs:
|
|
|
214
256
|
|
|
215
257
|
BRANCH=${{ github.ref_name }}
|
|
216
258
|
|
|
217
|
-
PROJECT_NAME="${{ env.PROJECT_NAME }}"
|
|
259
|
+
PROJECT_NAME="${{ env.PROJECT_PATH != '.' && format('{0}-spring', env.PROJECT_NAME) || env.PROJECT_NAME }}"
|
|
218
260
|
|
|
219
261
|
# 포트 및 컨테이너명 설정
|
|
220
262
|
PORT=${{ env.DEPLOY_PORT }}
|
package/src/cli/args.js
CHANGED
|
@@ -1,9 +1,15 @@
|
|
|
1
|
-
// CLI 인자
|
|
1
|
+
// CLI 인자 파싱.
|
|
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 { isValidBranchName } from "../core/branches.js";
|
|
4
5
|
import {
|
|
5
6
|
ENV_MODES, DEPLOY_MODES, STORE_PLATFORMS, NO_STORE, isEnvMode, isDeployMode, parseStoreList,
|
|
6
7
|
} from "../core/flutter-options.js";
|
|
8
|
+
import { CliError } from "../core/errors.js";
|
|
9
|
+
import { normalizePath, isRepoRelativePath } from "../core/paths.js";
|
|
10
|
+
|
|
11
|
+
// 기존 import 경로(cli/args.js) 호환 — 정의는 core에 있다.
|
|
12
|
+
export { CliError, normalizePath, isRepoRelativePath };
|
|
7
13
|
|
|
8
14
|
// argv(process.argv.slice(2)) → 파싱 결과. 오류 시 throw(호출부에서 exit 1).
|
|
9
15
|
export function parseArgs(argv) {
|
|
@@ -49,8 +55,15 @@ export function parseArgs(argv) {
|
|
|
49
55
|
case "-v": case "--version":
|
|
50
56
|
// npm 관례: -v/--version 은 패키지 버전 출력. (초기 버전 지정은 --project-version)
|
|
51
57
|
result.showVersion = true; break;
|
|
52
|
-
case "--project-version":
|
|
53
|
-
|
|
58
|
+
case "--project-version": {
|
|
59
|
+
// 릴리스 워크플로우(version_manager)는 x.y.z만 올릴 수 있다 — 흔한 v 접두사는 떼고 받는다.
|
|
60
|
+
const raw = (args.shift() ?? "").trim();
|
|
61
|
+
const v = raw.replace(/^v/i, "");
|
|
62
|
+
if (!/^\d+\.\d+\.\d+$/.test(v)) {
|
|
63
|
+
throw new CliError(`--project-version 값이 올바르지 않습니다: '${raw}' (x.y.z 형식, 예: 1.0.0)`);
|
|
64
|
+
}
|
|
65
|
+
result.version = v; break;
|
|
66
|
+
}
|
|
54
67
|
case "-t": case "--type": {
|
|
55
68
|
const csv = args.shift() ?? "";
|
|
56
69
|
const seen = new Set();
|
|
@@ -132,16 +145,19 @@ export function parseArgs(argv) {
|
|
|
132
145
|
case "--no-copilot":
|
|
133
146
|
if (seenFlags.has("--copilot")) throw new CliError("--copilot과 --no-copilot은 동시에 지정할 수 없습니다");
|
|
134
147
|
seenFlags.add("--no-copilot"); result.includeCopilotAi = false; break;
|
|
135
|
-
case "--paths":
|
|
136
|
-
|
|
137
|
-
const v = args.shift();
|
|
138
|
-
if (!v) throw new CliError("--
|
|
139
|
-
result.
|
|
148
|
+
case "--paths": {
|
|
149
|
+
// 값이 없는데 조용히 자동 감지로 넘어가면 사용자가 지정했다고 믿은 경로와 다르게 설치된다.
|
|
150
|
+
const v = (args.shift() ?? "").trim();
|
|
151
|
+
if (!v) throw new CliError("--paths 인자가 비어 있습니다 (예: --paths flutter=app,react=client)");
|
|
152
|
+
result.pathsCsv = v; break;
|
|
140
153
|
}
|
|
141
|
-
case "--develop-branch": {
|
|
142
|
-
const v = args.shift();
|
|
143
|
-
if (!v) throw new CliError(
|
|
144
|
-
|
|
154
|
+
case "--main-branch": case "--develop-branch": {
|
|
155
|
+
const v = (args.shift() ?? "").trim();
|
|
156
|
+
if (!v) throw new CliError(`${a}에 빈 값을 지정할 수 없습니다`);
|
|
157
|
+
// 워크플로우 트리거·셸 명령에 그대로 치환되는 값이라 쓸 수 없는 이름은 설치 전에 거부한다.
|
|
158
|
+
if (!isValidBranchName(v)) throw new CliError(`${a} 값이 브랜치 이름으로 올바르지 않습니다: '${v}'`);
|
|
159
|
+
if (a === "--main-branch") result.mainBranch = v; else result.developBranch = v;
|
|
160
|
+
break;
|
|
145
161
|
}
|
|
146
162
|
case "-h": case "--help": result.help = true; break;
|
|
147
163
|
default:
|
|
@@ -156,17 +172,6 @@ export function parseArgs(argv) {
|
|
|
156
172
|
return result;
|
|
157
173
|
}
|
|
158
174
|
|
|
159
|
-
export class CliError extends Error {}
|
|
160
|
-
|
|
161
|
-
// 경로 정규화 (.sh resolve_project_paths §3.4): 앞뒤 공백·\→/·끝 /·앞 ./ 제거, 빈값→"."
|
|
162
|
-
export function normalizePath(p) {
|
|
163
|
-
let s = String(p).trim();
|
|
164
|
-
s = s.replace(/\\/g, "/");
|
|
165
|
-
s = s.replace(/\/+$/, ""); // 끝 /
|
|
166
|
-
s = s.replace(/^\.\//, ""); // 앞 ./
|
|
167
|
-
return s === "" ? "." : s;
|
|
168
|
-
}
|
|
169
|
-
|
|
170
175
|
// "flutter=app,react=client" → Map<type, normalizedPath>. 타입 검증(무효 → throw).
|
|
171
176
|
export function parsePathsCsv(csv) {
|
|
172
177
|
const map = new Map();
|
|
@@ -179,7 +184,11 @@ export function parsePathsCsv(csv) {
|
|
|
179
184
|
if (!VALID_TYPES.includes(type)) {
|
|
180
185
|
throw new CliError(`--paths에 지원하지 않는 타입: '${type}'`);
|
|
181
186
|
}
|
|
182
|
-
|
|
187
|
+
const path = normalizePath(rawPath);
|
|
188
|
+
if (!isRepoRelativePath(path)) {
|
|
189
|
+
throw new CliError(`--paths는 레포 안의 상대경로만 지정할 수 있습니다: '${type}=${rawPath.trim()}'`);
|
|
190
|
+
}
|
|
191
|
+
map.set(type, path);
|
|
183
192
|
}
|
|
184
193
|
return map;
|
|
185
194
|
}
|