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.
- 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 +9 -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 +482 -107
- package/payload/version.yml.template +5 -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 +26 -60
- package/src/commands/dry-run.js +59 -5
- package/src/commands/full.js +135 -55
- package/src/commands/install-settings.js +51 -0
- package/src/commands/interactive-flutter.js +1 -1
- package/src/commands/interactive.js +135 -69
- package/src/commands/purge.js +50 -21
- package/src/commands/status.js +18 -12
- package/src/commands/uninstall.js +22 -22
- 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/app-files.js +28 -0
- 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 +133 -111
- package/src/core/deploy-style.js +83 -11
- package/src/core/detect-fs.js +127 -24
- package/src/core/detect.js +177 -47
- package/src/core/errors.js +3 -0
- package/src/core/flutter-doctor.js +49 -0
- package/src/core/flutter-hooks.js +29 -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-exec.js +67 -0
- package/src/core/removal-plan.js +75 -19
- package/src/core/types.js +136 -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 +128 -60
- 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
|
@@ -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:
|
|
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
|
-
# {{
|
|
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
|
# 워크플로우 실행 결과를 보기 쉽게 요약
|