project-auto-wizard 0.12.2 → 0.13.1

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.
Files changed (91) hide show
  1. package/README.ja.md +165 -0
  2. package/README.ko.md +165 -0
  3. package/README.md +107 -286
  4. package/README.zh-CN.md +165 -0
  5. package/bin/project-auto-wizard.js +1 -2
  6. package/package.json +9 -3
  7. package/payload/config/breaking-changes.json +24 -0
  8. package/payload/config/wizard-prompts.yml +45 -0
  9. package/payload/scripts/changelog_manager.py +141 -46
  10. package/payload/scripts/issue_helper.py +34 -5
  11. package/payload/scripts/version_manager.py +482 -107
  12. package/payload/version.yml.template +5 -2
  13. package/payload/workflows/common/PROJECT-COMMON-AI-PR-SUMMARY.yaml +39 -9
  14. package/payload/workflows/common/PROJECT-COMMON-AUTO-CHANGELOG-CONTROL.yaml +165 -38
  15. package/payload/workflows/common/PROJECT-COMMON-README-VERSION-UPDATE.yaml +14 -5
  16. package/payload/workflows/common/PROJECT-COMMON-RELEASE-PUBLISH.yaml +220 -6
  17. package/payload/workflows/common/PROJECT-COMMON-VERSION-CONTROL.yaml +89 -3
  18. package/payload/workflows/flutter/PROJECT-FLUTTER-ANDROID-FIREBASE-CICD.yaml +78 -16
  19. package/payload/workflows/flutter/PROJECT-FLUTTER-ANDROID-PLAYSTORE-CICD.yaml +72 -16
  20. package/payload/workflows/flutter/PROJECT-FLUTTER-ANDROID-SELFHOSTED-CICD.yaml +62 -20
  21. package/payload/workflows/flutter/PROJECT-FLUTTER-ANDROID-TEST-APK.yaml +51 -13
  22. package/payload/workflows/flutter/PROJECT-FLUTTER-CI.yaml +75 -11
  23. package/payload/workflows/flutter/PROJECT-FLUTTER-IOS-TEST-TESTFLIGHT.yaml +52 -3
  24. package/payload/workflows/flutter/PROJECT-FLUTTER-IOS-TESTFLIGHT.yaml +60 -11
  25. package/payload/workflows/go/PROJECT-GO-CI.yaml +10 -1
  26. package/payload/workflows/go/PROJECT-GO-PR-PREVIEW.yaml +148 -28
  27. package/payload/workflows/go/PROJECT-GO-SIMPLE-CICD.yaml +45 -4
  28. package/payload/workflows/next/PROJECT-NEXT-CI.yaml +25 -3
  29. package/payload/workflows/next/PROJECT-NEXT-CICD.yaml +83 -9
  30. package/payload/workflows/python/PROJECT-PYTHON-CI.yaml +70 -6
  31. package/payload/workflows/python/PROJECT-PYTHON-PR-PREVIEW.yaml +148 -28
  32. package/payload/workflows/python/PROJECT-PYTHON-SIMPLE-CICD.yaml +45 -4
  33. package/payload/workflows/react/PROJECT-REACT-CI.yaml +25 -3
  34. package/payload/workflows/react/PROJECT-REACT-CICD.yaml +84 -10
  35. package/payload/workflows/spring/PROJECT-SPRING-CI.yml +13 -3
  36. package/payload/workflows/spring/server-deploy/PROJECT-SPRING-NONSTOP-NGINX-CICD.yaml +47 -5
  37. package/payload/workflows/spring/server-deploy/PROJECT-SPRING-NONSTOP-TRAEFIK-CICD.yaml +47 -5
  38. package/payload/workflows/spring/server-deploy/PROJECT-SPRING-PR-PREVIEW.yaml +153 -30
  39. package/payload/workflows/spring/server-deploy/PROJECT-SPRING-SIMPLE-CICD.yaml +47 -5
  40. package/src/cli/args.js +33 -24
  41. package/src/cli/help.js +16 -4
  42. package/src/commands/doctor.js +26 -60
  43. package/src/commands/dry-run.js +59 -5
  44. package/src/commands/full.js +135 -55
  45. package/src/commands/install-settings.js +51 -0
  46. package/src/commands/interactive-flutter.js +1 -1
  47. package/src/commands/interactive.js +135 -69
  48. package/src/commands/purge.js +50 -21
  49. package/src/commands/status.js +18 -12
  50. package/src/commands/uninstall.js +22 -22
  51. package/src/context.js +5 -5
  52. package/src/core/baseline.js +20 -5
  53. package/src/core/branches.js +19 -1
  54. package/src/core/branding.js +1 -1
  55. package/src/core/breaking-check.js +14 -19
  56. package/src/core/breaking.js +7 -3
  57. package/src/core/copy/app-files.js +28 -0
  58. package/src/core/copy/gitignore.js +20 -12
  59. package/src/core/copy/readme.js +24 -5
  60. package/src/core/copy/simple.js +52 -13
  61. package/src/core/copy/workflows.js +133 -111
  62. package/src/core/deploy-style.js +83 -11
  63. package/src/core/detect-fs.js +127 -24
  64. package/src/core/detect.js +177 -47
  65. package/src/core/errors.js +3 -0
  66. package/src/core/flutter-doctor.js +49 -0
  67. package/src/core/flutter-hooks.js +29 -0
  68. package/src/core/flutter-options.js +4 -3
  69. package/src/core/fsutil.js +23 -6
  70. package/src/core/logger.js +55 -19
  71. package/src/core/paths-resolve.js +50 -47
  72. package/src/core/paths.js +26 -1
  73. package/src/core/release-options.js +23 -0
  74. package/src/core/removal-exec.js +67 -0
  75. package/src/core/removal-plan.js +75 -19
  76. package/src/core/types.js +136 -0
  77. package/src/core/verify.js +85 -19
  78. package/src/core/version-yml.js +42 -8
  79. package/src/core/wizard-env.js +21 -14
  80. package/src/core/wizard-labels.js +34 -25
  81. package/src/index.js +128 -60
  82. package/src/ui/ansi.js +3 -1
  83. package/src/ui/banner.js +12 -8
  84. package/src/ui/env-plan.js +68 -44
  85. package/src/ui/prompts.js +22 -12
  86. package/src/ui/readline-engine.js +109 -22
  87. package/src/ui/status-cards.js +10 -5
  88. package/src/ui/summary.js +71 -33
  89. package/payload/scripts/__pycache__/changelog_manager.cpython-314.pyc +0 -0
  90. package/payload/scripts/__pycache__/issue_helper.cpython-314.pyc +0 -0
  91. package/payload/scripts/__pycache__/version_manager.cpython-314.pyc +0 -0
@@ -31,9 +31,13 @@ name: PROJECT-PYTHON-PR-PREVIEW
31
31
  env:
32
32
  # 프로젝트 고유 식별자 (컨테이너명, 이미지명, 도메인에 사용)
33
33
  PROJECT_NAME: "__PROJECT_NAME__" # @wizard ask:@repo
34
+ # 모노레포(--paths)에서 이 프로젝트가 있는 하위 경로. 빌드·Docker context의 기준 ('.'이면 레포 루트)
35
+ PROJECT_PATH: "." # @wizard auto:project-path
36
+ # 모노레포(PROJECT_PATH가 '.'이 아님)에서는 이미지·컨테이너·도메인 이름에 '-python'를 붙여
37
+ # 같은 레포의 다른 타입 서비스와 이름이 겹치지 않게 한다 (예: my-app-python)
34
38
 
35
39
  # Docker 설정
36
- DOCKERFILE_PATH: './Dockerfile'
40
+ DOCKERFILE_PATH: './Dockerfile' # PROJECT_PATH 기준 상대 경로
37
41
  INTERNAL_PORT: '8000'
38
42
 
39
43
  # 볼륨 마운트 설정 (프로젝트별 수정 필요, 빈값이면 마운트 안함)
@@ -227,8 +231,45 @@ jobs:
227
231
  with:
228
232
  ref: ${{ steps.pr.outputs.ref }}
229
233
 
234
+ # 필수 Secret·Dockerfile이 없으면 docker 로그인 같은 서드파티 오류 대신 빠진 항목을 알려 주고 멈춘다
235
+ - name: 배포 사전 점검 (Secret·Dockerfile)
236
+ working-directory: ${{ env.PROJECT_PATH }}
237
+ env:
238
+ DOCKERHUB_USERNAME: ${{ secrets.DOCKERHUB_USERNAME }}
239
+ DOCKERHUB_TOKEN: ${{ secrets.DOCKERHUB_TOKEN }}
240
+ SERVER_HOST: ${{ secrets.SERVER_HOST }}
241
+ SERVER_USER: ${{ secrets.SERVER_USER }}
242
+ SERVER_PASSWORD: ${{ secrets.SERVER_PASSWORD }}
243
+ SSH_KEY: ${{ secrets.SSH_KEY }}
244
+ DOCKERFILE: ${{ env.DOCKERFILE_PATH }}
245
+ run: |
246
+ MISSING=""
247
+ for NAME in DOCKERHUB_USERNAME DOCKERHUB_TOKEN SERVER_HOST SERVER_USER; do
248
+ [ -n "${!NAME}" ] || MISSING="$MISSING $NAME"
249
+ done
250
+ # SSH_AUTH_METHOD=key면 SSH_KEY, 아니면 SERVER_PASSWORD가 필요하다
251
+ if [ "${SSH_AUTH_METHOD:-password}" = "key" ]; then
252
+ [ -n "$SSH_KEY" ] || MISSING="$MISSING SSH_KEY"
253
+ else
254
+ [ -n "$SERVER_PASSWORD" ] || MISSING="$MISSING SERVER_PASSWORD"
255
+ fi
256
+ FAILED=false
257
+ if [ -n "$MISSING" ]; then
258
+ echo "::error title=필수 Secret 누락::등록되지 않은 GitHub Secret:$MISSING — Settings → Secrets and variables → Actions에서 등록하세요"
259
+ FAILED=true
260
+ fi
261
+ if [ ! -f "$DOCKERFILE" ]; then
262
+ echo "::error title=Dockerfile 없음::${PROJECT_PATH}/${DOCKERFILE#./} 파일이 없습니다. 이 워크플로우는 Docker 이미지로 배포하므로 Dockerfile이 필요합니다 (서버 배포를 하지 않는다면 이 워크플로우 파일을 삭제하세요)"
263
+ FAILED=true
264
+ fi
265
+ if [ "$FAILED" = "true" ]; then
266
+ exit 1
267
+ fi
268
+ echo "✅ 사전 점검 통과: 필수 Secret과 Dockerfile이 모두 있습니다"
269
+
230
270
  # 환경변수 파일 생성 (프로젝트별 수정 영역)
231
271
  - name: "[필수] .env 파일 생성"
272
+ working-directory: ${{ env.PROJECT_PATH }}
232
273
  run: |
233
274
  cat << 'EOF' > .env
234
275
  ${{ secrets.ENV_FILE }}
@@ -246,10 +287,10 @@ jobs:
246
287
  - name: Docker 이미지 빌드 & Push
247
288
  uses: docker/build-push-action@v5
248
289
  with:
249
- context: .
250
- file: ${{ env.DOCKERFILE_PATH }}
290
+ context: ${{ env.PROJECT_PATH }}
291
+ file: ${{ env.PROJECT_PATH }}/${{ env.DOCKERFILE_PATH }}
251
292
  push: true
252
- tags: ${{ secrets.DOCKERHUB_USERNAME }}/${{ env.PROJECT_NAME }}:pr-${{ github.event.issue.number }}
293
+ tags: ${{ secrets.DOCKERHUB_USERNAME }}/${{ env.PROJECT_PATH != '.' && format('{0}-python', env.PROJECT_NAME) || env.PROJECT_NAME }}:pr-${{ github.event.issue.number }}
253
294
  cache-from: type=gha
254
295
  cache-to: type=gha,mode=max
255
296
 
@@ -318,7 +359,7 @@ jobs:
318
359
  echo "🔐 SSH 인증 방식: ${SSH_AUTH_METHOD}"
319
360
 
320
361
  PR_NUMBER=${{ github.event.issue.number }}
321
- PROJECT_NAME="${{ env.PROJECT_NAME }}"
362
+ PROJECT_NAME="${{ env.PROJECT_PATH != '.' && format('{0}-python', env.PROJECT_NAME) || env.PROJECT_NAME }}"
322
363
  CONTAINER_NAME="${PROJECT_NAME}-pr-${PR_NUMBER}"
323
364
  IMAGE="${{ secrets.DOCKERHUB_USERNAME }}/${PROJECT_NAME}:pr-${PR_NUMBER}"
324
365
  DOMAIN="${PROJECT_NAME}-pr-${PR_NUMBER}.${{ env.PREVIEW_DOMAIN_SUFFIX }}"
@@ -445,7 +486,7 @@ jobs:
445
486
  const seconds = Math.floor((deployElapsed % 60000) / 1000);
446
487
  const deployDuration = minutes > 0 ? `${minutes}분 ${seconds}초` : `${seconds}초`;
447
488
 
448
- const projectName = '${{ env.PROJECT_NAME }}';
489
+ const projectName = '${{ env.PROJECT_PATH != '.' && format('{0}-python', env.PROJECT_NAME) || env.PROJECT_NAME }}';
449
490
  const domainSuffix = '${{ env.PREVIEW_DOMAIN_SUFFIX }}';
450
491
  const previewPort = '${{ env.PREVIEW_PORT }}';
451
492
  const apiDocsPath = '${{ env.API_DOCS_PATH }}';
@@ -513,7 +554,7 @@ jobs:
513
554
  with:
514
555
  script: |
515
556
  const commentId = ${{ steps.progress.outputs.comment_id || 0 }};
516
- const projectName = '${{ env.PROJECT_NAME }}';
557
+ const projectName = '${{ env.PROJECT_PATH != '.' && format('{0}-python', env.PROJECT_NAME) || env.PROJECT_NAME }}';
517
558
  const prNumber = context.issue.number;
518
559
  const runUrl = `https://github.com/${context.repo.owner}/${context.repo.repo}/actions/runs/${context.runId}`;
519
560
 
@@ -550,6 +591,7 @@ jobs:
550
591
  `**[📋 빌드/배포 로그 확인](${runUrl})**`,
551
592
  '',
552
593
  '### 🔍 가능한 원인',
594
+ '- 필수 Secret(DOCKERHUB_*, SERVER_*) 미등록 또는 Dockerfile 없음 → 로그의 "배포 사전 점검" 단계 확인',
553
595
  '- Docker 이미지 빌드 실패 (Python 의존성 문제)',
554
596
  '- 컨테이너 시작 실패 (FastAPI/Uvicorn 기동 오류)',
555
597
  '- Health Check 타임아웃 (120초 내 기동 완료 안됨)',
@@ -791,8 +833,46 @@ jobs:
791
833
  with:
792
834
  ref: ${{ needs.get-branch-from-issue.outputs.branch_name }}
793
835
 
836
+ # 필수 Secret·Dockerfile이 없으면 docker 로그인 같은 서드파티 오류 대신 빠진 항목을 알려 주고 멈춘다
837
+ - name: 배포 사전 점검 (Secret·Dockerfile)
838
+ if: steps.check_branch.outputs.exists == 'true'
839
+ working-directory: ${{ env.PROJECT_PATH }}
840
+ env:
841
+ DOCKERHUB_USERNAME: ${{ secrets.DOCKERHUB_USERNAME }}
842
+ DOCKERHUB_TOKEN: ${{ secrets.DOCKERHUB_TOKEN }}
843
+ SERVER_HOST: ${{ secrets.SERVER_HOST }}
844
+ SERVER_USER: ${{ secrets.SERVER_USER }}
845
+ SERVER_PASSWORD: ${{ secrets.SERVER_PASSWORD }}
846
+ SSH_KEY: ${{ secrets.SSH_KEY }}
847
+ DOCKERFILE: ${{ env.DOCKERFILE_PATH }}
848
+ run: |
849
+ MISSING=""
850
+ for NAME in DOCKERHUB_USERNAME DOCKERHUB_TOKEN SERVER_HOST SERVER_USER; do
851
+ [ -n "${!NAME}" ] || MISSING="$MISSING $NAME"
852
+ done
853
+ # SSH_AUTH_METHOD=key면 SSH_KEY, 아니면 SERVER_PASSWORD가 필요하다
854
+ if [ "${SSH_AUTH_METHOD:-password}" = "key" ]; then
855
+ [ -n "$SSH_KEY" ] || MISSING="$MISSING SSH_KEY"
856
+ else
857
+ [ -n "$SERVER_PASSWORD" ] || MISSING="$MISSING SERVER_PASSWORD"
858
+ fi
859
+ FAILED=false
860
+ if [ -n "$MISSING" ]; then
861
+ echo "::error title=필수 Secret 누락::등록되지 않은 GitHub Secret:$MISSING — Settings → Secrets and variables → Actions에서 등록하세요"
862
+ FAILED=true
863
+ fi
864
+ if [ ! -f "$DOCKERFILE" ]; then
865
+ echo "::error title=Dockerfile 없음::${PROJECT_PATH}/${DOCKERFILE#./} 파일이 없습니다. 이 워크플로우는 Docker 이미지로 배포하므로 Dockerfile이 필요합니다 (서버 배포를 하지 않는다면 이 워크플로우 파일을 삭제하세요)"
866
+ FAILED=true
867
+ fi
868
+ if [ "$FAILED" = "true" ]; then
869
+ exit 1
870
+ fi
871
+ echo "✅ 사전 점검 통과: 필수 Secret과 Dockerfile이 모두 있습니다"
872
+
794
873
  # 환경변수 파일 생성 (프로젝트별 수정 영역)
795
874
  - name: "[필수] .env 파일 생성"
875
+ working-directory: ${{ env.PROJECT_PATH }}
796
876
  if: steps.check_branch.outputs.exists == 'true'
797
877
  run: |
798
878
  cat << 'EOF' > .env
@@ -814,10 +894,10 @@ jobs:
814
894
  if: steps.check_branch.outputs.exists == 'true'
815
895
  uses: docker/build-push-action@v5
816
896
  with:
817
- context: .
818
- file: ${{ env.DOCKERFILE_PATH }}
897
+ context: ${{ env.PROJECT_PATH }}
898
+ file: ${{ env.PROJECT_PATH }}/${{ env.DOCKERFILE_PATH }}
819
899
  push: true
820
- tags: ${{ secrets.DOCKERHUB_USERNAME }}/${{ env.PROJECT_NAME }}:pr-${{ needs.get-branch-from-issue.outputs.issue_number }}
900
+ tags: ${{ secrets.DOCKERHUB_USERNAME }}/${{ env.PROJECT_PATH != '.' && format('{0}-python', env.PROJECT_NAME) || env.PROJECT_NAME }}:pr-${{ needs.get-branch-from-issue.outputs.issue_number }}
821
901
  cache-from: type=gha
822
902
  cache-to: type=gha,mode=max
823
903
 
@@ -891,7 +971,7 @@ jobs:
891
971
  echo "🔐 SSH 인증 방식: ${SSH_AUTH_METHOD}"
892
972
 
893
973
  ISSUE_NUMBER=${{ needs.get-branch-from-issue.outputs.issue_number }}
894
- PROJECT_NAME="${{ env.PROJECT_NAME }}"
974
+ PROJECT_NAME="${{ env.PROJECT_PATH != '.' && format('{0}-python', env.PROJECT_NAME) || env.PROJECT_NAME }}"
895
975
  CONTAINER_NAME="${PROJECT_NAME}-pr-${ISSUE_NUMBER}"
896
976
  IMAGE="${{ secrets.DOCKERHUB_USERNAME }}/${PROJECT_NAME}:pr-${ISSUE_NUMBER}"
897
977
  DOMAIN="${PROJECT_NAME}-pr-${ISSUE_NUMBER}.${{ env.PREVIEW_DOMAIN_SUFFIX }}"
@@ -1020,7 +1100,7 @@ jobs:
1020
1100
  const seconds = Math.floor((deployElapsed % 60000) / 1000);
1021
1101
  const deployDuration = minutes > 0 ? `${minutes}분 ${seconds}초` : `${seconds}초`;
1022
1102
 
1023
- const projectName = '${{ env.PROJECT_NAME }}';
1103
+ const projectName = '${{ env.PROJECT_PATH != '.' && format('{0}-python', env.PROJECT_NAME) || env.PROJECT_NAME }}';
1024
1104
  const domainSuffix = '${{ env.PREVIEW_DOMAIN_SUFFIX }}';
1025
1105
  const previewPort = '${{ env.PREVIEW_PORT }}';
1026
1106
  const apiDocsPath = '${{ env.API_DOCS_PATH }}';
@@ -1089,7 +1169,7 @@ jobs:
1089
1169
  with:
1090
1170
  script: |
1091
1171
  const commentId = ${{ steps.progress.outputs.comment_id || 0 }};
1092
- const projectName = '${{ env.PROJECT_NAME }}';
1172
+ const projectName = '${{ env.PROJECT_PATH != '.' && format('{0}-python', env.PROJECT_NAME) || env.PROJECT_NAME }}';
1093
1173
  const issueNumber = ${{ needs.get-branch-from-issue.outputs.issue_number }};
1094
1174
  const branchName = '${{ needs.get-branch-from-issue.outputs.branch_name }}';
1095
1175
  const runUrl = `https://github.com/${context.repo.owner}/${context.repo.repo}/actions/runs/${context.runId}`;
@@ -1124,6 +1204,7 @@ jobs:
1124
1204
  `**[📋 빌드/배포 로그 확인](${runUrl})**`,
1125
1205
  '',
1126
1206
  '### 🔍 가능한 원인',
1207
+ '- 필수 Secret(DOCKERHUB_*, SERVER_*) 미등록 또는 Dockerfile 없음 → 로그의 "배포 사전 점검" 단계 확인',
1127
1208
  '- Docker 이미지 빌드 실패 (Python 의존성 문제)',
1128
1209
  '- 컨테이너 시작 실패 (FastAPI/Uvicorn 기동 오류)',
1129
1210
  '- Health Check 타임아웃 (120초 내 기동 완료 안됨)',
@@ -1202,7 +1283,7 @@ jobs:
1202
1283
  echo "🔐 SSH 인증 방식: ${SSH_AUTH_METHOD}"
1203
1284
 
1204
1285
  PR_NUMBER=${{ steps.pr_number.outputs.number }}
1205
- PROJECT_NAME="${{ env.PROJECT_NAME }}"
1286
+ PROJECT_NAME="${{ env.PROJECT_PATH != '.' && format('{0}-python', env.PROJECT_NAME) || env.PROJECT_NAME }}"
1206
1287
  CONTAINER_NAME="${PROJECT_NAME}-pr-${PR_NUMBER}"
1207
1288
  IMAGE="${{ secrets.DOCKERHUB_USERNAME }}/${PROJECT_NAME}:pr-${PR_NUMBER}"
1208
1289
 
@@ -1230,7 +1311,7 @@ jobs:
1230
1311
  with:
1231
1312
  script: |
1232
1313
  const number = ${{ steps.pr_number.outputs.number }};
1233
- const projectName = '${{ env.PROJECT_NAME }}';
1314
+ const projectName = '${{ env.PROJECT_PATH != '.' && format('{0}-python', env.PROJECT_NAME) || env.PROJECT_NAME }}';
1234
1315
 
1235
1316
  const body = [
1236
1317
  '## 🗑️ Preview 환경 삭제 완료!',
@@ -1273,7 +1354,7 @@ jobs:
1273
1354
  export PATH=$PATH:/usr/local/bin
1274
1355
 
1275
1356
  NUMBER=${{ github.event.issue.number }}
1276
- PROJECT_NAME="${{ env.PROJECT_NAME }}"
1357
+ PROJECT_NAME="${{ env.PROJECT_PATH != '.' && format('{0}-python', env.PROJECT_NAME) || env.PROJECT_NAME }}"
1277
1358
  CONTAINER_NAME="${PROJECT_NAME}-pr-${NUMBER}"
1278
1359
 
1279
1360
  echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
@@ -1303,7 +1384,7 @@ jobs:
1303
1384
  export PATH=$PATH:/usr/local/bin
1304
1385
 
1305
1386
  NUMBER=${{ github.event.issue.number }}
1306
- PROJECT_NAME="${{ env.PROJECT_NAME }}"
1387
+ PROJECT_NAME="${{ env.PROJECT_PATH != '.' && format('{0}-python', env.PROJECT_NAME) || env.PROJECT_NAME }}"
1307
1388
  CONTAINER_NAME="${PROJECT_NAME}-pr-${NUMBER}"
1308
1389
  docker ps --filter "name=${CONTAINER_NAME}" --format "{{.Names}}" | grep -q "${CONTAINER_NAME}"
1309
1390
 
@@ -1312,7 +1393,7 @@ jobs:
1312
1393
  with:
1313
1394
  script: |
1314
1395
  const number = context.issue.number;
1315
- const projectName = '${{ env.PROJECT_NAME }}';
1396
+ const projectName = '${{ env.PROJECT_PATH != '.' && format('{0}-python', env.PROJECT_NAME) || env.PROJECT_NAME }}';
1316
1397
  const domainSuffix = '${{ env.PREVIEW_DOMAIN_SUFFIX }}';
1317
1398
  const previewPort = '${{ env.PREVIEW_PORT }}';
1318
1399
  const domain = `${projectName}-pr-${number}.${domainSuffix}`;
@@ -1502,7 +1583,45 @@ jobs:
1502
1583
  with:
1503
1584
  ref: ${{ needs.check-command.outputs.custom_branch }}
1504
1585
 
1586
+ # 필수 Secret·Dockerfile이 없으면 docker 로그인 같은 서드파티 오류 대신 빠진 항목을 알려 주고 멈춘다
1587
+ - name: 배포 사전 점검 (Secret·Dockerfile)
1588
+ if: steps.branch_info.outputs.branch_exists == 'true'
1589
+ working-directory: ${{ env.PROJECT_PATH }}
1590
+ env:
1591
+ DOCKERHUB_USERNAME: ${{ secrets.DOCKERHUB_USERNAME }}
1592
+ DOCKERHUB_TOKEN: ${{ secrets.DOCKERHUB_TOKEN }}
1593
+ SERVER_HOST: ${{ secrets.SERVER_HOST }}
1594
+ SERVER_USER: ${{ secrets.SERVER_USER }}
1595
+ SERVER_PASSWORD: ${{ secrets.SERVER_PASSWORD }}
1596
+ SSH_KEY: ${{ secrets.SSH_KEY }}
1597
+ DOCKERFILE: ${{ env.DOCKERFILE_PATH }}
1598
+ run: |
1599
+ MISSING=""
1600
+ for NAME in DOCKERHUB_USERNAME DOCKERHUB_TOKEN SERVER_HOST SERVER_USER; do
1601
+ [ -n "${!NAME}" ] || MISSING="$MISSING $NAME"
1602
+ done
1603
+ # SSH_AUTH_METHOD=key면 SSH_KEY, 아니면 SERVER_PASSWORD가 필요하다
1604
+ if [ "${SSH_AUTH_METHOD:-password}" = "key" ]; then
1605
+ [ -n "$SSH_KEY" ] || MISSING="$MISSING SSH_KEY"
1606
+ else
1607
+ [ -n "$SERVER_PASSWORD" ] || MISSING="$MISSING SERVER_PASSWORD"
1608
+ fi
1609
+ FAILED=false
1610
+ if [ -n "$MISSING" ]; then
1611
+ echo "::error title=필수 Secret 누락::등록되지 않은 GitHub Secret:$MISSING — Settings → Secrets and variables → Actions에서 등록하세요"
1612
+ FAILED=true
1613
+ fi
1614
+ if [ ! -f "$DOCKERFILE" ]; then
1615
+ echo "::error title=Dockerfile 없음::${PROJECT_PATH}/${DOCKERFILE#./} 파일이 없습니다. 이 워크플로우는 Docker 이미지로 배포하므로 Dockerfile이 필요합니다 (서버 배포를 하지 않는다면 이 워크플로우 파일을 삭제하세요)"
1616
+ FAILED=true
1617
+ fi
1618
+ if [ "$FAILED" = "true" ]; then
1619
+ exit 1
1620
+ fi
1621
+ echo "✅ 사전 점검 통과: 필수 Secret과 Dockerfile이 모두 있습니다"
1622
+
1505
1623
  - name: "[필수] .env 파일 생성"
1624
+ working-directory: ${{ env.PROJECT_PATH }}
1506
1625
  if: steps.branch_info.outputs.branch_exists == 'true'
1507
1626
  run: |
1508
1627
  cat << 'EOF' > .env
@@ -1530,16 +1649,16 @@ jobs:
1530
1649
  SUFFIX="pr-${{ steps.branch_info.outputs.container_number }}"
1531
1650
  fi
1532
1651
  echo "suffix=$SUFFIX" >> $GITHUB_OUTPUT
1533
- echo "name=${{ env.PROJECT_NAME }}-${SUFFIX}" >> $GITHUB_OUTPUT
1652
+ echo "name=${{ env.PROJECT_PATH != '.' && format('{0}-python', env.PROJECT_NAME) || env.PROJECT_NAME }}-${SUFFIX}" >> $GITHUB_OUTPUT
1534
1653
 
1535
1654
  - name: Docker 이미지 빌드 & Push
1536
1655
  if: steps.branch_info.outputs.branch_exists == 'true'
1537
1656
  uses: docker/build-push-action@v5
1538
1657
  with:
1539
- context: .
1540
- file: ${{ env.DOCKERFILE_PATH }}
1658
+ context: ${{ env.PROJECT_PATH }}
1659
+ file: ${{ env.PROJECT_PATH }}/${{ env.DOCKERFILE_PATH }}
1541
1660
  push: true
1542
- tags: ${{ secrets.DOCKERHUB_USERNAME }}/${{ env.PROJECT_NAME }}:${{ steps.container.outputs.suffix }}
1661
+ tags: ${{ secrets.DOCKERHUB_USERNAME }}/${{ env.PROJECT_PATH != '.' && format('{0}-python', env.PROJECT_NAME) || env.PROJECT_NAME }}:${{ steps.container.outputs.suffix }}
1543
1662
  cache-from: type=gha
1544
1663
  cache-to: type=gha,mode=max
1545
1664
 
@@ -1612,7 +1731,7 @@ jobs:
1612
1731
  fi
1613
1732
  echo "🔐 SSH 인증 방식: ${SSH_AUTH_METHOD}"
1614
1733
 
1615
- PROJECT_NAME="${{ env.PROJECT_NAME }}"
1734
+ PROJECT_NAME="${{ env.PROJECT_PATH != '.' && format('{0}-python', env.PROJECT_NAME) || env.PROJECT_NAME }}"
1616
1735
  CONTAINER_NAME="${{ steps.container.outputs.name }}"
1617
1736
  CONTAINER_SUFFIX="${{ steps.container.outputs.suffix }}"
1618
1737
  IMAGE="${{ secrets.DOCKERHUB_USERNAME }}/${PROJECT_NAME}:${CONTAINER_SUFFIX}"
@@ -1735,7 +1854,7 @@ jobs:
1735
1854
  const seconds = Math.floor((deployElapsed % 60000) / 1000);
1736
1855
  const deployDuration = minutes > 0 ? `${minutes}분 ${seconds}초` : `${seconds}초`;
1737
1856
 
1738
- const projectName = '${{ env.PROJECT_NAME }}';
1857
+ const projectName = '${{ env.PROJECT_PATH != '.' && format('{0}-python', env.PROJECT_NAME) || env.PROJECT_NAME }}';
1739
1858
  const domainSuffix = '${{ env.PREVIEW_DOMAIN_SUFFIX }}';
1740
1859
  const previewPort = '${{ env.PREVIEW_PORT }}';
1741
1860
  const apiDocsPath = '${{ env.API_DOCS_PATH }}';
@@ -1837,6 +1956,7 @@ jobs:
1837
1956
  `**[📋 빌드/배포 로그 확인](${runUrl})**`,
1838
1957
  '',
1839
1958
  '### 🔍 가능한 원인',
1959
+ '- 필수 Secret(DOCKERHUB_*, SERVER_*) 미등록 또는 Dockerfile 없음 → 로그의 "배포 사전 점검" 단계 확인',
1840
1960
  '- Docker 이미지 빌드 실패 (Python 의존성 문제)',
1841
1961
  '- 컨테이너 시작 실패 (FastAPI/Uvicorn 기동 오류)',
1842
1962
  '- Health Check 타임아웃 (120초 내 기동 완료 안됨)',
@@ -1882,7 +2002,7 @@ jobs:
1882
2002
  with:
1883
2003
  script: |
1884
2004
  const branchName = '${{ needs.check-command.outputs.custom_branch }}';
1885
- const projectName = '${{ env.PROJECT_NAME }}';
2005
+ const projectName = '${{ env.PROJECT_PATH != '.' && format('{0}-python', env.PROJECT_NAME) || env.PROJECT_NAME }}';
1886
2006
 
1887
2007
  // 브랜치명에서 Issue 번호 추출 (#번호 패턴)
1888
2008
  const issueMatch = branchName.match(/#(\d+)/);
@@ -1932,7 +2052,7 @@ jobs:
1932
2052
  echo "🔐 SSH 인증 방식: ${SSH_AUTH_METHOD}"
1933
2053
 
1934
2054
  CONTAINER_NAME="${{ steps.container_info.outputs.container_name }}"
1935
- PROJECT_NAME="${{ env.PROJECT_NAME }}"
2055
+ PROJECT_NAME="${{ env.PROJECT_PATH != '.' && format('{0}-python', env.PROJECT_NAME) || env.PROJECT_NAME }}"
1936
2056
 
1937
2057
  # 컨테이너명에서 suffix 추출
1938
2058
  SUFFIX="${CONTAINER_NAME#${PROJECT_NAME}-}"
@@ -1998,7 +2118,7 @@ jobs:
1998
2118
  with:
1999
2119
  script: |
2000
2120
  const branchName = '${{ needs.check-command.outputs.custom_branch }}';
2001
- const projectName = '${{ env.PROJECT_NAME }}';
2121
+ const projectName = '${{ env.PROJECT_PATH != '.' && format('{0}-python', env.PROJECT_NAME) || env.PROJECT_NAME }}';
2002
2122
 
2003
2123
  // 브랜치명에서 Issue 번호 추출 (#번호 패턴)
2004
2124
  const issueMatch = branchName.match(/#(\d+)/);
@@ -2056,7 +2176,7 @@ jobs:
2056
2176
  script: |
2057
2177
  const branchName = '${{ needs.check-command.outputs.custom_branch }}';
2058
2178
  const containerName = '${{ steps.container_info.outputs.container_name }}';
2059
- const projectName = '${{ env.PROJECT_NAME }}';
2179
+ const projectName = '${{ env.PROJECT_PATH != '.' && format('{0}-python', env.PROJECT_NAME) || env.PROJECT_NAME }}';
2060
2180
  const domainSuffix = '${{ env.PREVIEW_DOMAIN_SUFFIX }}';
2061
2181
  const previewPort = '${{ env.PREVIEW_PORT }}';
2062
2182
  const domain = `${containerName}.${domainSuffix}`;
@@ -27,6 +27,10 @@ on:
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가 '.'이 아님)에서는 이미지·컨테이너·도메인 이름에 '-python'를 붙여
33
+ # 같은 레포의 다른 타입 서비스와 이름이 겹치지 않게 한다 (예: my-app-python)
30
34
 
31
35
  # 포트 설정
32
36
  DEPLOY_PORT: "__DEPLOY_PORT__" # @wizard ask:8000
@@ -69,6 +73,42 @@ jobs:
69
73
  - name: 코드 체크아웃
70
74
  uses: actions/checkout@v7
71
75
 
76
+ # 필수 Secret·Dockerfile이 없으면 docker 로그인 같은 서드파티 오류 대신 빠진 항목을 알려 주고 멈춘다
77
+ - name: 배포 사전 점검 (Secret·Dockerfile)
78
+ working-directory: ${{ env.PROJECT_PATH }}
79
+ env:
80
+ DOCKERHUB_USERNAME: ${{ secrets.DOCKERHUB_USERNAME }}
81
+ DOCKERHUB_TOKEN: ${{ secrets.DOCKERHUB_TOKEN }}
82
+ SERVER_HOST: ${{ secrets.SERVER_HOST }}
83
+ SERVER_USER: ${{ secrets.SERVER_USER }}
84
+ SERVER_PASSWORD: ${{ secrets.SERVER_PASSWORD }}
85
+ SSH_KEY: ${{ secrets.SSH_KEY }}
86
+ DOCKERFILE: Dockerfile
87
+ run: |
88
+ MISSING=""
89
+ for NAME in DOCKERHUB_USERNAME DOCKERHUB_TOKEN SERVER_HOST SERVER_USER; do
90
+ [ -n "${!NAME}" ] || MISSING="$MISSING $NAME"
91
+ done
92
+ # SSH_AUTH_METHOD=key면 SSH_KEY, 아니면 SERVER_PASSWORD가 필요하다
93
+ if [ "${SSH_AUTH_METHOD:-password}" = "key" ]; then
94
+ [ -n "$SSH_KEY" ] || MISSING="$MISSING SSH_KEY"
95
+ else
96
+ [ -n "$SERVER_PASSWORD" ] || MISSING="$MISSING SERVER_PASSWORD"
97
+ fi
98
+ FAILED=false
99
+ if [ -n "$MISSING" ]; then
100
+ echo "::error title=필수 Secret 누락::등록되지 않은 GitHub Secret:$MISSING — Settings → Secrets and variables → Actions에서 등록하세요"
101
+ FAILED=true
102
+ fi
103
+ if [ ! -f "$DOCKERFILE" ]; then
104
+ echo "::error title=Dockerfile 없음::${PROJECT_PATH}/${DOCKERFILE#./} 파일이 없습니다. 이 워크플로우는 Docker 이미지로 배포하므로 Dockerfile이 필요합니다 (서버 배포를 하지 않는다면 이 워크플로우 파일을 삭제하세요)"
105
+ FAILED=true
106
+ fi
107
+ if [ "$FAILED" = "true" ]; then
108
+ exit 1
109
+ fi
110
+ echo "✅ 사전 점검 통과: 필수 Secret과 Dockerfile이 모두 있습니다"
111
+
72
112
  - name: 디스크 공간 확보
73
113
  run: |
74
114
  echo "📊 빌드 전 디스크 사용량:"
@@ -87,6 +127,7 @@ jobs:
87
127
  df -h
88
128
 
89
129
  - name: .env 파일 생성
130
+ working-directory: ${{ env.PROJECT_PATH }}
90
131
  run: |
91
132
  cat > .env << 'EOF'
92
133
  ${{ secrets.ENV_FILE }}
@@ -105,10 +146,10 @@ jobs:
105
146
  - name: Docker 이미지 빌드 및 푸시
106
147
  uses: docker/build-push-action@v5
107
148
  with:
108
- context: .
109
- file: ./Dockerfile
149
+ context: ${{ env.PROJECT_PATH }}
150
+ file: ${{ env.PROJECT_PATH }}/Dockerfile
110
151
  push: true
111
- tags: ${{ secrets.DOCKERHUB_USERNAME }}/${{ env.PROJECT_NAME }}:${{ github.ref_name }}
152
+ tags: ${{ secrets.DOCKERHUB_USERNAME }}/${{ env.PROJECT_PATH != '.' && format('{0}-python', env.PROJECT_NAME) || env.PROJECT_NAME }}:${{ github.ref_name }}
112
153
  # GitHub Actions 캐시 사용 (로컬 디스크 공간 절약)
113
154
  cache-from: type=gha
114
155
  cache-to: type=gha,mode=max
@@ -162,7 +203,7 @@ jobs:
162
203
 
163
204
  BRANCH=${{ github.ref_name }}
164
205
 
165
- PROJECT_NAME="${{ env.PROJECT_NAME }}"
206
+ PROJECT_NAME="${{ env.PROJECT_PATH != '.' && format('{0}-python', env.PROJECT_NAME) || env.PROJECT_NAME }}"
166
207
 
167
208
  # 포트 및 컨테이너명 설정
168
209
  PORT=${{ env.DEPLOY_PORT }}
@@ -1,6 +1,6 @@
1
1
  # project-auto-wizard:managed-workflow
2
2
  # React CI (빌드 검증)
3
- # {{MAIN_BRANCH}} 브랜치 푸시 시 npm ci와 npm run build로 빌드를 검증합니다. node_modules와 빌드 결과를 캐싱합니다.
3
+ # {{DEVELOP_BRANCH}} 브랜치 push·PR 시 npm ci, npm test(test 스크립트가 있을 때), npm run build로 검증합니다. node_modules와 빌드 결과를 캐싱합니다.
4
4
  #
5
5
  # GitHub Secrets
6
6
  # ENV_FILE (선택): .env 파일 내용. CI는 빌드 검증 목적이므로 배포 관련 Secrets는 불필요
@@ -15,6 +15,8 @@ name: PROJECT-REACT-CI
15
15
  on:
16
16
  push:
17
17
  branches: ["{{DEVELOP_BRANCH}}"]
18
+ pull_request:
19
+ branches: ["{{DEVELOP_BRANCH}}"]
18
20
  workflow_dispatch:
19
21
 
20
22
  env:
@@ -48,6 +50,9 @@ jobs:
48
50
  if: ${{ github.event_name != 'workflow_dispatch' }}
49
51
  uses: dorny/paths-filter@v4
50
52
  with:
53
+ # push는 이번 push 범위(직전 커밋 before 대비)만 본다. 비워 두면 기본 브랜치 대비 누적 diff가 된다.
54
+ # 새 브랜치 첫 push(before가 0)와 PR은 빈 값 → 기본 동작(기본 브랜치·PR base 대비)
55
+ base: ${{ github.event_name == 'push' && github.event.before != '0000000000000000000000000000000000000000' && github.event.before || '' }}
51
56
  filters: |
52
57
  project:
53
58
  - '${{ env.PROJECT_PATH == '.' && '**' || format('{0}/**', env.PROJECT_PATH) }}'
@@ -57,6 +62,10 @@ jobs:
57
62
  runs-on: ubuntu-latest
58
63
  needs: changes
59
64
  if: ${{ github.event_name == 'workflow_dispatch' || needs.changes.outputs.project == 'true' }}
65
+ # 모노레포(--paths)면 그 하위 폴더에서 빌드한다
66
+ defaults:
67
+ run:
68
+ working-directory: ${{ env.PROJECT_PATH }}
60
69
 
61
70
  steps:
62
71
  - name: 코드 체크아웃
@@ -72,6 +81,7 @@ jobs:
72
81
  with:
73
82
  node-version: ${{ env.NODE_VERSION }}
74
83
  cache: "npm"
84
+ cache-dependency-path: ${{ env.PROJECT_PATH }}/package-lock.json
75
85
 
76
86
  - name: npm 의존성 캐싱
77
87
  uses: actions/cache@v6
@@ -104,12 +114,24 @@ jobs:
104
114
  - name: Next.js 빌드 캐싱
105
115
  uses: actions/cache@v6
106
116
  with:
107
- path: .next/cache
117
+ path: ${{ env.PROJECT_PATH }}/.next/cache
108
118
  key: ${{ runner.os }}-nextjs-${{ hashFiles('**/package-lock.json') }}-${{ hashFiles('**.[jt]s', '**.[jt]sx') }}
109
119
  restore-keys: |
110
120
  ${{ runner.os }}-nextjs-${{ hashFiles('**/package-lock.json') }}-
111
121
  ${{ runner.os }}-nextjs-
112
122
 
123
+ # package.json에 test 스크립트가 있을 때만 실행한다 (npm init 기본값 "no test specified"는 없는 것으로 본다)
124
+ - name: 테스트 실행
125
+ run: |
126
+ TEST_SCRIPT=$(node -p "(require('./package.json').scripts || {}).test || ''")
127
+ if [ -z "$TEST_SCRIPT" ] || echo "$TEST_SCRIPT" | grep -q "no test specified"; then
128
+ echo "::notice::package.json에 test 스크립트가 없어 테스트를 건너뜁니다"
129
+ exit 0
130
+ fi
131
+ echo "🧪 npm test 실행: $TEST_SCRIPT"
132
+ # CI=true: watch 모드로 대기하는 테스트 러너(react-scripts 등)를 한 번만 실행
133
+ CI=true npm test
134
+
113
135
  # 빌드 실패 시에도 워크플로우를 중단하지 않고 에러 로그 수집
114
136
  - name: 프로젝트 빌드
115
137
  id: build
@@ -161,7 +183,7 @@ jobs:
161
183
  uses: actions/upload-artifact@v7
162
184
  with:
163
185
  name: build-log
164
- path: build_log.txt
186
+ path: ${{ env.PROJECT_PATH }}/build_log.txt
165
187
  retention-days: 7 # 7일간 보관
166
188
 
167
189
  # 워크플로우 실행 결과를 보기 쉽게 요약