project-auto-wizard 0.1.5
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/LICENSE +21 -0
- package/README.md +139 -0
- package/bin/project-auto-wizard.js +19 -0
- package/package.json +36 -0
- package/payload/coderabbit.yaml +19 -0
- package/payload/config/breaking-changes.json +1 -0
- package/payload/config/wizard-prompts.yml +62 -0
- package/payload/scripts/changelog_manager.py +726 -0
- package/payload/scripts/version_manager.py +617 -0
- package/payload/version.yml.template +48 -0
- package/payload/workflows/common/PROJECT-COMMON-AUTO-CHANGELOG-CONTROL.yaml +303 -0
- package/payload/workflows/common/PROJECT-COMMON-README-VERSION-UPDATE.yaml +293 -0
- package/payload/workflows/common/PROJECT-COMMON-RELEASE-PUBLISH.yaml +289 -0
- package/payload/workflows/common/PROJECT-COMMON-VERSION-CONTROL.yaml +192 -0
- package/payload/workflows/common/secret-backup/PROJECT-COMMON-SECRET-FILE-UPLOAD.yaml +209 -0
- package/payload/workflows/flutter/PROJECT-FLUTTER-ANDROID-FIREBASE-CICD.yaml +591 -0
- package/payload/workflows/flutter/PROJECT-FLUTTER-ANDROID-PLAYSTORE-CICD.yaml +700 -0
- package/payload/workflows/flutter/PROJECT-FLUTTER-ANDROID-SELFHOSTED-CICD.yaml +308 -0
- package/payload/workflows/flutter/PROJECT-FLUTTER-ANDROID-TEST-APK.yaml +992 -0
- package/payload/workflows/flutter/PROJECT-FLUTTER-CI.yaml +689 -0
- package/payload/workflows/flutter/PROJECT-FLUTTER-IOS-TEST-TESTFLIGHT.yaml +987 -0
- package/payload/workflows/flutter/PROJECT-FLUTTER-IOS-TESTFLIGHT.yaml +471 -0
- package/payload/workflows/flutter/PROJECT-FLUTTER-SUH-LAB-APP-BUILD-TRIGGER.yaml +500 -0
- package/payload/workflows/next/PROJECT-NEXT-CI.yaml +185 -0
- package/payload/workflows/next/PROJECT-NEXT-CICD.yaml +271 -0
- package/payload/workflows/python/PROJECT-PYTHON-CI.yaml +81 -0
- package/payload/workflows/python/PROJECT-PYTHON-PR-PREVIEW.yaml +2191 -0
- package/payload/workflows/python/PROJECT-PYTHON-SIMPLE-CICD.yaml +386 -0
- package/payload/workflows/react/PROJECT-REACT-CI.yaml +194 -0
- package/payload/workflows/react/PROJECT-REACT-CICD.yaml +255 -0
- package/payload/workflows/spring/PROJECT-SPRING-GITHUB-PACKAGES-PUBLISH.yml +84 -0
- package/payload/workflows/spring/nexus/PROJECT-SPRING-NEXUS-CI.yml +316 -0
- package/payload/workflows/spring/nexus/PROJECT-SPRING-NEXUS-PUBLISH.yml +58 -0
- package/payload/workflows/spring/server-deploy/PROJECT-SPRING-NONSTOP-NGINX-CICD.yaml +535 -0
- package/payload/workflows/spring/server-deploy/PROJECT-SPRING-NONSTOP-TRAEFIK-CICD.yaml +437 -0
- package/payload/workflows/spring/server-deploy/PROJECT-SPRING-PR-PREVIEW.yaml +2277 -0
- package/payload/workflows/spring/server-deploy/PROJECT-SPRING-SIMPLE-CICD.yaml +425 -0
- package/src/cli/args.js +95 -0
- package/src/cli/help.js +27 -0
- package/src/commands/full.js +53 -0
- package/src/commands/interactive.js +276 -0
- package/src/commands/revert.js +60 -0
- package/src/commands/version.js +31 -0
- package/src/commands/workflows.js +49 -0
- package/src/context.js +26 -0
- package/src/core/assets.js +47 -0
- package/src/core/branches.js +60 -0
- package/src/core/branding.js +15 -0
- package/src/core/breaking-check.js +65 -0
- package/src/core/breaking.js +26 -0
- package/src/core/copy/coderabbit.js +26 -0
- package/src/core/copy/gitignore.js +55 -0
- package/src/core/copy/readme.js +30 -0
- package/src/core/copy/simple.js +23 -0
- package/src/core/copy/workflows.js +233 -0
- package/src/core/detect-fs.js +106 -0
- package/src/core/detect.js +62 -0
- package/src/core/fsutil.js +46 -0
- package/src/core/options-ask.js +99 -0
- package/src/core/paths-resolve.js +261 -0
- package/src/core/paths.js +16 -0
- package/src/core/version-yml.js +190 -0
- package/src/core/wizard-env.js +101 -0
- package/src/core/wizard-labels.js +107 -0
- package/src/index.js +162 -0
- package/src/ui/ansi.js +26 -0
- package/src/ui/banner.js +29 -0
- package/src/ui/env-plan.js +207 -0
- package/src/ui/prompts.js +99 -0
- package/src/ui/readline-engine.js +257 -0
- package/src/ui/status-cards.js +56 -0
- package/src/ui/summary.js +127 -0
|
@@ -0,0 +1,425 @@
|
|
|
1
|
+
# ===================================================================
|
|
2
|
+
# Spring Boot CI/CD 배포 (SSH + Docker)
|
|
3
|
+
# ===================================================================
|
|
4
|
+
#
|
|
5
|
+
# deploy 브랜치 푸시 시 Docker 이미지를 빌드하고
|
|
6
|
+
# SSH로 접속 가능한 서버(Synology·AWS EC2·VPS 등)에 자동 배포합니다.
|
|
7
|
+
#
|
|
8
|
+
# ===================================================================
|
|
9
|
+
# 🔑 필수 GitHub Secrets
|
|
10
|
+
# ===================================================================
|
|
11
|
+
# APPLICATION_PROD_YML (선택): application-prod.yml 내용
|
|
12
|
+
# DOCKERHUB_USERNAME: DockerHub 사용자명
|
|
13
|
+
# DOCKERHUB_TOKEN: DockerHub 액세스 토큰
|
|
14
|
+
# SERVER_HOST: 배포 서버 주소
|
|
15
|
+
# SERVER_USER: SSH 사용자명
|
|
16
|
+
# SERVER_PASSWORD: SSH 비밀번호 (SSH_AUTH_METHOD=password일 때)
|
|
17
|
+
# SSH_KEY: SSH 개인키 .pem 내용 (SSH_AUTH_METHOD=key일 때, AWS EC2 등)
|
|
18
|
+
# ===================================================================
|
|
19
|
+
#
|
|
20
|
+
# 🔧 환경변수 설정 (env 섹션에서 설정)
|
|
21
|
+
# ===================================================================
|
|
22
|
+
#
|
|
23
|
+
# 🌐 포트 및 컨테이너:
|
|
24
|
+
# CONTAINER_INTERNAL_PORT: Spring Boot 내부 포트 (기본: 8080)
|
|
25
|
+
# DEPLOY_PORT: 배포 포트
|
|
26
|
+
# CONTAINER_NAME: 컨테이너 이름 (기본: PROJECT_NAME)
|
|
27
|
+
#
|
|
28
|
+
# 📦 볼륨 마운트:
|
|
29
|
+
# ENABLE_VOLUME_MOUNT: 볼륨 마운트 활성화 (true/false)
|
|
30
|
+
# VOLUME_HOST_PATH: 호스트 볼륨 경로
|
|
31
|
+
# VOLUME_CONTAINER_PATH: 컨테이너 내부 마운트 경로
|
|
32
|
+
#
|
|
33
|
+
# 🔌 SSH 연결:
|
|
34
|
+
# SSH_PORT: SSH 포트 (기본: 2022)
|
|
35
|
+
# SSH_COMMAND_TIMEOUT: SSH 명령 타임아웃 (기본: 600s)
|
|
36
|
+
# SSH_CONNECTION_TIMEOUT: SSH 연결 타임아웃 (기본: 60s)
|
|
37
|
+
#
|
|
38
|
+
# ⏱️ 헬스체크:
|
|
39
|
+
# HEALTHCHECK_WAIT_SECONDS: 초기화 대기 시간 (기본: 10초)
|
|
40
|
+
# HEALTHCHECK_PATH: HTTP 엔드포인트 경로 (비어있으면 로그만 사용)
|
|
41
|
+
# HEALTHCHECK_MAX_RETRIES: HTTP 재시도 횟수 (기본: 5회)
|
|
42
|
+
# HEALTHCHECK_RETRY_INTERVAL: 재시도 간격 (기본: 3초)
|
|
43
|
+
# HEALTHCHECK_LOG_PATTERN: Fallback 로그 검색 패턴
|
|
44
|
+
#
|
|
45
|
+
# 💡 HEALTHCHECK_PATH 예시:
|
|
46
|
+
# Spring Boot (Actuator): "/actuator/health"
|
|
47
|
+
# 커스텀: "/api/v1/health"
|
|
48
|
+
#
|
|
49
|
+
# 💡 HEALTHCHECK_LOG_PATTERN 예시:
|
|
50
|
+
# Spring Boot: "Tomcat started on port"
|
|
51
|
+
# ===================================================================
|
|
52
|
+
#
|
|
53
|
+
# 🌿 브랜치별 배포:
|
|
54
|
+
# - deploy 브랜치: 자동 배포 (DEPLOY_PORT 사용)
|
|
55
|
+
# - {{MAIN_BRANCH}} 브랜치: 수동 실행만 가능
|
|
56
|
+
# ===================================================================
|
|
57
|
+
|
|
58
|
+
name: PROJECT-SPRING-SIMPLE-CICD
|
|
59
|
+
|
|
60
|
+
# ===================================================================
|
|
61
|
+
# 트리거 설정
|
|
62
|
+
# ===================================================================
|
|
63
|
+
# deploy 브랜치에 push할 때 자동으로 CI/CD가 실행됩니다.
|
|
64
|
+
# {{MAIN_BRANCH}} 브랜치는 workflow_dispatch로 수동 실행만 가능합니다.
|
|
65
|
+
on:
|
|
66
|
+
push:
|
|
67
|
+
branches:
|
|
68
|
+
- "{{MAIN_BRANCH}}" # 배포 환경 (DEPLOY_PORT 사용)
|
|
69
|
+
# @wizard paths-anchor (모노레포일 때 integrator가 paths 필터를 여기 주입)
|
|
70
|
+
workflow_dispatch: # 수동 실행 허용
|
|
71
|
+
|
|
72
|
+
# ===================================================================
|
|
73
|
+
# 환경 변수 설정
|
|
74
|
+
# ===================================================================
|
|
75
|
+
env:
|
|
76
|
+
# 🔧 프로젝트 설정 - 실제 프로젝트명으로 변경하세요
|
|
77
|
+
PROJECT_NAME: "__PROJECT_NAME__" # @wizard ask:@repo
|
|
78
|
+
|
|
79
|
+
# ☁️ Spring Boot 설정
|
|
80
|
+
SPRING_PROFILE: "prod"
|
|
81
|
+
JAVA_VERSION: "__JAVA_VERSION__" # @wizard ask:21
|
|
82
|
+
GRADLE_OPTS: "-Dspring.profiles.active=prod"
|
|
83
|
+
|
|
84
|
+
# 📁 경로 설정
|
|
85
|
+
APPLICATION_YML_DIR: "__APPLICATION_YML_DIR__" # @wizard auto:spring-app-yml-dir
|
|
86
|
+
DOCKERFILE_PATH: "./Dockerfile"
|
|
87
|
+
|
|
88
|
+
# 🌐 포트 설정
|
|
89
|
+
CONTAINER_INTERNAL_PORT: "8080" # Spring Boot 내부 포트 (고정값)
|
|
90
|
+
DEPLOY_PORT: "__DEPLOY_PORT__" # @wizard ask:8080
|
|
91
|
+
|
|
92
|
+
# 🐳 컨테이너 이름 설정
|
|
93
|
+
# 비워두면 PROJECT_NAME을 기본값으로 사용 (예: mapsy-back)
|
|
94
|
+
# 커스텀 이름을 원하면 직접 입력 (예: "my-custom-container")
|
|
95
|
+
CONTAINER_NAME: ""
|
|
96
|
+
|
|
97
|
+
# 📦 볼륨 마운트 설정
|
|
98
|
+
# true: 볼륨 마운트 활성화 (디렉토리 없으면 자동 생성)
|
|
99
|
+
# false: 볼륨 마운트 비활성화 (기본값)
|
|
100
|
+
ENABLE_VOLUME_MOUNT: "false"
|
|
101
|
+
VOLUME_HOST_PATH: "/volume1/projects/__PROJECT_NAME__" # @wizard ask:/volume1/projects/__PROJECT_NAME__
|
|
102
|
+
VOLUME_CONTAINER_PATH: "/mnt/__PROJECT_NAME__" # @wizard ask:/mnt/__PROJECT_NAME__
|
|
103
|
+
|
|
104
|
+
# 🔐 SSH 인증 방식: "password"(Synology·일반 서버) | "key"(AWS EC2·.pem)
|
|
105
|
+
# password → SERVER_PASSWORD secret 사용
|
|
106
|
+
# key → SSH_KEY secret 사용 (.pem 파일 전체 내용을 secret 값으로)
|
|
107
|
+
SSH_AUTH_METHOD: "password" # @wizard ask:password
|
|
108
|
+
|
|
109
|
+
# 🔌 SSH 연결 설정
|
|
110
|
+
SSH_PORT: "2022" # SSH 포트 (Synology 기본: 2022)
|
|
111
|
+
SSH_COMMAND_TIMEOUT: "600s" # SSH 명령 실행 타임아웃
|
|
112
|
+
SSH_CONNECTION_TIMEOUT: "60s" # SSH 연결 타임아웃
|
|
113
|
+
|
|
114
|
+
# ⏱️ 헬스체크 설정
|
|
115
|
+
HEALTHCHECK_WAIT_SECONDS: "10" # 컨테이너 초기화 대기 시간 (초)
|
|
116
|
+
HEALTHCHECK_PATH: "/actuator/health" # HTTP 엔드포인트 경로 (비어있으면 로그 패턴만 사용)
|
|
117
|
+
HEALTHCHECK_MAX_RETRIES: "5" # HTTP 재시도 횟수
|
|
118
|
+
HEALTHCHECK_RETRY_INTERVAL: "3" # 재시도 간격 (초)
|
|
119
|
+
HEALTHCHECK_LOG_PATTERN: "Tomcat started on port" # Fallback 로그 검색 패턴
|
|
120
|
+
|
|
121
|
+
jobs:
|
|
122
|
+
# ===================================================================
|
|
123
|
+
# 빌드 작업
|
|
124
|
+
# ===================================================================
|
|
125
|
+
build:
|
|
126
|
+
name: Spring Boot 애플리케이션 빌드
|
|
127
|
+
runs-on: ubuntu-latest
|
|
128
|
+
|
|
129
|
+
steps:
|
|
130
|
+
# 1. 소스코드 체크아웃
|
|
131
|
+
- name: 코드 체크아웃
|
|
132
|
+
uses: actions/checkout@v5
|
|
133
|
+
|
|
134
|
+
# 2. Java 개발 환경 설정
|
|
135
|
+
- name: Java 설정
|
|
136
|
+
uses: actions/setup-java@v4
|
|
137
|
+
with:
|
|
138
|
+
java-version: ${{ env.JAVA_VERSION }}
|
|
139
|
+
distribution: 'temurin'
|
|
140
|
+
cache: 'gradle'
|
|
141
|
+
|
|
142
|
+
# 3. Gradle 실행 권한 부여
|
|
143
|
+
- name: Gradle Wrapper 실행권한 부여
|
|
144
|
+
run: chmod +x gradlew
|
|
145
|
+
|
|
146
|
+
# 4. Spring Boot 운영 환경 설정 파일 생성
|
|
147
|
+
# GitHub Secrets에서 APPLICATION_PROD_YML 값을 읽어와서
|
|
148
|
+
# 리소스 디렉토리에 파일로 생성
|
|
149
|
+
- name: application-prod.yml 생성
|
|
150
|
+
run: |
|
|
151
|
+
# 리소스 디렉토리 생성 (환경변수 사용)
|
|
152
|
+
mkdir -p ${{ env.APPLICATION_YML_DIR }}
|
|
153
|
+
|
|
154
|
+
# GitHub Secrets의 APPLICATION_PROD_YML 내용을 파일로 저장
|
|
155
|
+
cat << 'EOF' > ./${{ env.APPLICATION_YML_DIR }}/application-prod.yml
|
|
156
|
+
${{ secrets.APPLICATION_PROD_YML }}
|
|
157
|
+
EOF
|
|
158
|
+
|
|
159
|
+
echo "✅ application-prod.yml 파일 생성 완료 (${{ env.APPLICATION_YML_DIR }})"
|
|
160
|
+
|
|
161
|
+
# 5. Gradle 빌드 실행
|
|
162
|
+
# 테스트는 제외하고 운영 프로파일로 빌드
|
|
163
|
+
- name: Build with Gradle
|
|
164
|
+
run: ./gradlew clean build -x test ${{ env.GRADLE_OPTS }}
|
|
165
|
+
|
|
166
|
+
# 6. Docker 빌드 환경 설정
|
|
167
|
+
- name: Docker 빌드환경 설정
|
|
168
|
+
uses: docker/setup-buildx-action@v3
|
|
169
|
+
|
|
170
|
+
# 7. DockerHub 로그인
|
|
171
|
+
- name: DockerHub 로그인
|
|
172
|
+
uses: docker/login-action@v3
|
|
173
|
+
with:
|
|
174
|
+
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
|
175
|
+
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
|
176
|
+
|
|
177
|
+
# 8. Docker 레이어 캐싱 설정
|
|
178
|
+
# 빌드 속도 향상을 위한 캐시 설정
|
|
179
|
+
- name: Cache Docker layers
|
|
180
|
+
uses: actions/cache@v4
|
|
181
|
+
with:
|
|
182
|
+
path: /tmp/.buildx-cache
|
|
183
|
+
key: ${{ runner.os }}-buildx-${{ hashFiles('Dockerfile') }}
|
|
184
|
+
restore-keys: |
|
|
185
|
+
${{ runner.os }}-buildx-
|
|
186
|
+
|
|
187
|
+
# 9. Docker 이미지 빌드 및 푸시
|
|
188
|
+
# 브랜치명을 태그로 사용하여 환경별로 이미지 관리
|
|
189
|
+
- name: Docker 이미지 빌드 및 푸시
|
|
190
|
+
uses: docker/build-push-action@v5
|
|
191
|
+
with:
|
|
192
|
+
context: .
|
|
193
|
+
file: ${{ env.DOCKERFILE_PATH }}
|
|
194
|
+
push: true
|
|
195
|
+
tags: ${{ secrets.DOCKERHUB_USERNAME }}/${{ env.PROJECT_NAME }}:${{ github.ref_name }}
|
|
196
|
+
cache-from: type=local,src=/tmp/.buildx-cache
|
|
197
|
+
cache-to: type=local,dest=/tmp/.buildx-cache-new,mode=max
|
|
198
|
+
|
|
199
|
+
# 10. Docker 캐시 정리
|
|
200
|
+
- name: Move Docker cache
|
|
201
|
+
run: |
|
|
202
|
+
rm -rf /tmp/.buildx-cache
|
|
203
|
+
mv /tmp/.buildx-cache-new /tmp/.buildx-cache
|
|
204
|
+
|
|
205
|
+
# ===================================================================
|
|
206
|
+
# 배포 작업
|
|
207
|
+
# ===================================================================
|
|
208
|
+
deploy:
|
|
209
|
+
name: 원격 서버 배포
|
|
210
|
+
needs: build
|
|
211
|
+
runs-on: ubuntu-latest
|
|
212
|
+
|
|
213
|
+
steps:
|
|
214
|
+
# SSH를 통한 원격 서버 배포 실행
|
|
215
|
+
- name: Deploy
|
|
216
|
+
uses: appleboy/ssh-action@v1.0.3
|
|
217
|
+
env:
|
|
218
|
+
SSH_AUTH_METHOD: ${{ env.SSH_AUTH_METHOD }}
|
|
219
|
+
with:
|
|
220
|
+
host: ${{ secrets.SERVER_HOST }}
|
|
221
|
+
username: ${{ secrets.SERVER_USER }}
|
|
222
|
+
password: ${{ secrets.SERVER_PASSWORD }}
|
|
223
|
+
key: ${{ secrets.SSH_KEY }}
|
|
224
|
+
port: ${{ env.SSH_PORT }}
|
|
225
|
+
envs: SSH_AUTH_METHOD
|
|
226
|
+
script: |
|
|
227
|
+
set -e
|
|
228
|
+
|
|
229
|
+
# ============================================================
|
|
230
|
+
# 배포 환경 변수 설정
|
|
231
|
+
# ============================================================
|
|
232
|
+
echo "🔧 환경변수 설정.."
|
|
233
|
+
export PATH=$PATH:/usr/local/bin
|
|
234
|
+
export PW=${{ secrets.SERVER_PASSWORD }}
|
|
235
|
+
|
|
236
|
+
# 🔐 SSH 인증 방식에 따른 sudo 추상화
|
|
237
|
+
# password 모드: echo $PW | sudo -S (Synology 등 sudo 비밀번호 필요 서버)
|
|
238
|
+
# key 모드: sudo 직접 (AWS EC2 등 passwordless sudo 서버)
|
|
239
|
+
SSH_AUTH_METHOD="${SSH_AUTH_METHOD:-password}"
|
|
240
|
+
if [ "${SSH_AUTH_METHOD}" = "key" ]; then
|
|
241
|
+
SUDO() { sudo "$@"; }
|
|
242
|
+
else
|
|
243
|
+
SUDO() { echo "$PW" | sudo -S "$@"; }
|
|
244
|
+
fi
|
|
245
|
+
echo "🔐 SSH 인증 방식: ${SSH_AUTH_METHOD}"
|
|
246
|
+
|
|
247
|
+
# GitHub에서 전달받은 브랜치명
|
|
248
|
+
BRANCH=${{ github.ref_name }}
|
|
249
|
+
|
|
250
|
+
# 프로젝트 설정
|
|
251
|
+
PROJECT_NAME="${{ env.PROJECT_NAME }}"
|
|
252
|
+
|
|
253
|
+
# ============================================================
|
|
254
|
+
# 포트 및 컨테이너명 설정
|
|
255
|
+
# ============================================================
|
|
256
|
+
PORT=${{ env.DEPLOY_PORT }}
|
|
257
|
+
|
|
258
|
+
# 컨테이너 이름 설정 (환경변수가 비어있으면 PROJECT_NAME을 기본값으로 사용)
|
|
259
|
+
if [ -z "${{ env.CONTAINER_NAME }}" ]; then
|
|
260
|
+
CONTAINER_NAME="${PROJECT_NAME}"
|
|
261
|
+
echo "ℹ️ CONTAINER_NAME이 설정되지 않아 PROJECT_NAME을 사용합니다: $CONTAINER_NAME"
|
|
262
|
+
else
|
|
263
|
+
CONTAINER_NAME="${{ env.CONTAINER_NAME }}"
|
|
264
|
+
echo "ℹ️ 사용자 지정 CONTAINER_NAME 사용: $CONTAINER_NAME"
|
|
265
|
+
fi
|
|
266
|
+
|
|
267
|
+
echo "🚀 배포 환경으로 배포합니다 (브랜치: $BRANCH)"
|
|
268
|
+
|
|
269
|
+
# 설정 정보 출력
|
|
270
|
+
echo "📋 배포 설정 정보:"
|
|
271
|
+
echo " - 브랜치: $BRANCH"
|
|
272
|
+
echo " - 프로젝트: $PROJECT_NAME"
|
|
273
|
+
echo " - 컨테이너 이름: $CONTAINER_NAME"
|
|
274
|
+
echo " - 포트: $PORT"
|
|
275
|
+
echo " - Docker 이미지: ${{ secrets.DOCKERHUB_USERNAME }}/${PROJECT_NAME}:${BRANCH}"
|
|
276
|
+
|
|
277
|
+
# ============================================================
|
|
278
|
+
# Docker 이미지 풀 (Pull)
|
|
279
|
+
# ============================================================
|
|
280
|
+
echo "⬇️ Docker 이미지 풀: ${{ secrets.DOCKERHUB_USERNAME }}/${PROJECT_NAME}:${BRANCH}"
|
|
281
|
+
SUDO docker pull ${{ secrets.DOCKERHUB_USERNAME }}/${PROJECT_NAME}:${BRANCH}
|
|
282
|
+
|
|
283
|
+
# ============================================================
|
|
284
|
+
# 기존 컨테이너 정리
|
|
285
|
+
# ============================================================
|
|
286
|
+
echo "🧹 컨테이너 $CONTAINER_NAME 존재 여부 확인 중..."
|
|
287
|
+
|
|
288
|
+
# 동일한 이름의 컨테이너가 존재하는지 확인
|
|
289
|
+
if SUDO docker ps -a --format '{{.Names}}' | grep -Eq "^${CONTAINER_NAME}\$"; then
|
|
290
|
+
echo "⚠️ 컨테이너 $CONTAINER_NAME 이(가) 존재합니다. 중지 및 삭제 중..."
|
|
291
|
+
SUDO docker rm -f $CONTAINER_NAME
|
|
292
|
+
echo "✅ 컨테이너 $CONTAINER_NAME 이(가) 삭제되었습니다."
|
|
293
|
+
else
|
|
294
|
+
echo "ℹ️ 존재하는 컨테이너 $CONTAINER_NAME 이(가) 없습니다."
|
|
295
|
+
fi
|
|
296
|
+
|
|
297
|
+
# ============================================================
|
|
298
|
+
# 새 컨테이너 실행
|
|
299
|
+
# ============================================================
|
|
300
|
+
echo "🚀 새로운 컨테이너 $CONTAINER_NAME 실행 중..."
|
|
301
|
+
|
|
302
|
+
# 볼륨 마운트 옵션 설정
|
|
303
|
+
VOLUME_MOUNT_OPTION=""
|
|
304
|
+
if [ "${{ env.ENABLE_VOLUME_MOUNT }}" == "true" ]; then
|
|
305
|
+
# 디렉토리가 없으면 생성
|
|
306
|
+
if [ ! -d "${{ env.VOLUME_HOST_PATH }}" ]; then
|
|
307
|
+
echo "📁 볼륨 마운트 디렉토리 생성: ${{ env.VOLUME_HOST_PATH }}"
|
|
308
|
+
SUDO mkdir -p "${{ env.VOLUME_HOST_PATH }}"
|
|
309
|
+
fi
|
|
310
|
+
VOLUME_MOUNT_OPTION="-v ${{ env.VOLUME_HOST_PATH }}:${{ env.VOLUME_CONTAINER_PATH }}"
|
|
311
|
+
echo "📦 볼륨 마운트 활성화: $VOLUME_MOUNT_OPTION"
|
|
312
|
+
else
|
|
313
|
+
echo "ℹ️ 볼륨 마운트 비활성화됨"
|
|
314
|
+
fi
|
|
315
|
+
|
|
316
|
+
# Docker 컨테이너 실행
|
|
317
|
+
# - 포트 매핑: ${PORT}:${{ env.CONTAINER_INTERNAL_PORT }} (외부:내부)
|
|
318
|
+
# - 시간대: Asia/Seoul
|
|
319
|
+
# - Spring 프로파일: ${{ env.SPRING_PROFILE }}
|
|
320
|
+
# - 볼륨 마운트: ENABLE_VOLUME_MOUNT=true일 때만 활성화
|
|
321
|
+
SUDO docker run -d \
|
|
322
|
+
-p ${PORT}:${{ env.CONTAINER_INTERNAL_PORT }} \
|
|
323
|
+
--name $CONTAINER_NAME \
|
|
324
|
+
-e TZ=Asia/Seoul \
|
|
325
|
+
-e "SPRING_PROFILES_ACTIVE=${{ env.SPRING_PROFILE }}" \
|
|
326
|
+
-v /etc/localtime:/etc/localtime:ro \
|
|
327
|
+
$VOLUME_MOUNT_OPTION \
|
|
328
|
+
${{ secrets.DOCKERHUB_USERNAME }}/${PROJECT_NAME}:${BRANCH}
|
|
329
|
+
|
|
330
|
+
# ============================================================
|
|
331
|
+
# 컨테이너 헬스체크
|
|
332
|
+
# ============================================================
|
|
333
|
+
echo "⏳ 컨테이너 초기화 대기 중... (${{ env.HEALTHCHECK_WAIT_SECONDS }}초)"
|
|
334
|
+
sleep ${{ env.HEALTHCHECK_WAIT_SECONDS }}
|
|
335
|
+
|
|
336
|
+
# 1단계: 컨테이너 프로세스 확인
|
|
337
|
+
echo "🏥 컨테이너 헬스체크 중..."
|
|
338
|
+
if ! SUDO docker ps --format '{{.Names}}' | grep -Eq "^${CONTAINER_NAME}\$"; then
|
|
339
|
+
echo "❌ 컨테이너 $CONTAINER_NAME 프로세스가 실행되지 않았습니다"
|
|
340
|
+
SUDO docker logs --tail 50 $CONTAINER_NAME || true
|
|
341
|
+
exit 1
|
|
342
|
+
fi
|
|
343
|
+
echo "✅ 컨테이너 프로세스 실행 확인 완료"
|
|
344
|
+
|
|
345
|
+
# 2단계: HTTP 엔드포인트 체크 (설정된 경우)
|
|
346
|
+
HEALTHCHECK_SUCCESS=false
|
|
347
|
+
if [ -n "${{ env.HEALTHCHECK_PATH }}" ]; then
|
|
348
|
+
echo "🌐 HTTP 헬스체크 시작: http://localhost:${PORT}${{ env.HEALTHCHECK_PATH }}"
|
|
349
|
+
|
|
350
|
+
for i in $(seq 1 ${{ env.HEALTHCHECK_MAX_RETRIES }}); do
|
|
351
|
+
echo " 시도 $i/${{ env.HEALTHCHECK_MAX_RETRIES }}..."
|
|
352
|
+
|
|
353
|
+
HTTP_CODE=$(curl -s -o /dev/null -w "%{http_code}" http://localhost:${PORT}${{ env.HEALTHCHECK_PATH }} || echo "000")
|
|
354
|
+
|
|
355
|
+
if [ "$HTTP_CODE" = "200" ]; then
|
|
356
|
+
echo "✅ HTTP 헬스체크 성공 (응답 코드: $HTTP_CODE)"
|
|
357
|
+
HEALTHCHECK_SUCCESS=true
|
|
358
|
+
break
|
|
359
|
+
else
|
|
360
|
+
echo "⚠️ HTTP 헬스체크 실패 (응답 코드: $HTTP_CODE)"
|
|
361
|
+
if [ $i -lt ${{ env.HEALTHCHECK_MAX_RETRIES }} ]; then
|
|
362
|
+
echo " ${{ env.HEALTHCHECK_RETRY_INTERVAL }}초 후 재시도..."
|
|
363
|
+
sleep ${{ env.HEALTHCHECK_RETRY_INTERVAL }}
|
|
364
|
+
fi
|
|
365
|
+
fi
|
|
366
|
+
done
|
|
367
|
+
|
|
368
|
+
# HTTP 체크 실패 시 fallback으로 로그 패턴 매칭
|
|
369
|
+
if [ "$HEALTHCHECK_SUCCESS" = "false" ]; then
|
|
370
|
+
echo "⚠️ HTTP 헬스체크 최종 실패, 로그 패턴 매칭으로 fallback..."
|
|
371
|
+
fi
|
|
372
|
+
else
|
|
373
|
+
echo "ℹ️ HEALTHCHECK_PATH가 설정되지 않아 로그 패턴 매칭만 사용합니다"
|
|
374
|
+
fi
|
|
375
|
+
|
|
376
|
+
# 3단계: 로그 패턴 매칭 (HTTP 실패 시 또는 HTTP 체크 비활성화 시)
|
|
377
|
+
if [ "$HEALTHCHECK_SUCCESS" = "false" ]; then
|
|
378
|
+
echo "📋 로그 패턴 매칭 시작: '${{ env.HEALTHCHECK_LOG_PATTERN }}'"
|
|
379
|
+
|
|
380
|
+
if SUDO docker logs $CONTAINER_NAME 2>&1 | grep -q "${{ env.HEALTHCHECK_LOG_PATTERN }}"; then
|
|
381
|
+
echo "✅ 로그 패턴 매칭 성공: 애플리케이션이 정상적으로 시작되었습니다"
|
|
382
|
+
HEALTHCHECK_SUCCESS=true
|
|
383
|
+
else
|
|
384
|
+
echo "❌ 로그 패턴 매칭 실패: 애플리케이션 시작 로그를 찾을 수 없습니다"
|
|
385
|
+
echo ""
|
|
386
|
+
echo "📋 디버깅 정보:"
|
|
387
|
+
echo " - 검색 패턴: ${{ env.HEALTHCHECK_LOG_PATTERN }}"
|
|
388
|
+
echo " - 컨테이너: $CONTAINER_NAME"
|
|
389
|
+
echo " - 이미지: ${{ secrets.DOCKERHUB_USERNAME }}/${PROJECT_NAME}:${BRANCH}"
|
|
390
|
+
echo " - 포트: ${PORT}:${{ env.CONTAINER_INTERNAL_PORT }}"
|
|
391
|
+
echo " - 볼륨: $VOLUME_MOUNT_OPTION"
|
|
392
|
+
echo " - 프로파일: ${{ env.SPRING_PROFILE }}"
|
|
393
|
+
echo ""
|
|
394
|
+
echo "📋 컨테이너 로그 (최근 50줄):"
|
|
395
|
+
SUDO docker logs --tail 50 $CONTAINER_NAME || true
|
|
396
|
+
exit 1
|
|
397
|
+
fi
|
|
398
|
+
fi
|
|
399
|
+
|
|
400
|
+
# 최종 성공 메시지
|
|
401
|
+
if [ "$HEALTHCHECK_SUCCESS" = "true" ]; then
|
|
402
|
+
echo "✅ 헬스체크 완료: 컨테이너 $CONTAINER_NAME 이(가) 정상적으로 실행 중입니다"
|
|
403
|
+
fi
|
|
404
|
+
|
|
405
|
+
# ============================================================
|
|
406
|
+
# 배포 완료 확인
|
|
407
|
+
# ============================================================
|
|
408
|
+
echo "✅ 배포가 성공적으로 완료되었습니다!"
|
|
409
|
+
echo ""
|
|
410
|
+
echo "📋 배포 결과 요약:"
|
|
411
|
+
echo " 🎯 프로젝트: $PROJECT_NAME"
|
|
412
|
+
echo " 🌿 브랜치: $BRANCH"
|
|
413
|
+
echo " 🐳 컨테이너: $CONTAINER_NAME"
|
|
414
|
+
echo " 🌐 포트: $PORT"
|
|
415
|
+
echo " ⏰ 배포 시간: $(date '+%Y-%m-%d %H:%M:%S')"
|
|
416
|
+
echo ""
|
|
417
|
+
echo "🔗 접속 URL: http://${{ secrets.SERVER_HOST }}:${PORT}"
|
|
418
|
+
|
|
419
|
+
# ===================================================================
|
|
420
|
+
# 📝 사용 방법
|
|
421
|
+
# ===================================================================
|
|
422
|
+
# 1. GitHub Secrets 설정 (DOCKERHUB_*, SERVER_*)
|
|
423
|
+
# 2. env 섹션에서 PROJECT_NAME, DEPLOY_PORT 설정
|
|
424
|
+
# 3. deploy 브랜치에 push → 자동 CI/CD 실행
|
|
425
|
+
# ===================================================================
|
package/src/cli/args.js
ADDED
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
// CLI 인자 파싱 (.sh top-level while-case 등가) — template_integrator.sh 842~920.
|
|
2
|
+
import { VALID_TYPES } from "../context.js";
|
|
3
|
+
|
|
4
|
+
// argv(process.argv.slice(2)) → 파싱 결과. 오류 시 throw(호출부에서 exit 1).
|
|
5
|
+
export function parseArgs(argv) {
|
|
6
|
+
const result = {
|
|
7
|
+
mode: "interactive",
|
|
8
|
+
version: "", // 통합 대상 프로젝트의 초기 버전 (--project-version)
|
|
9
|
+
types: [],
|
|
10
|
+
primaryType: "",
|
|
11
|
+
includeNexus: null, // null=미설정
|
|
12
|
+
includeSecretBackup: null,
|
|
13
|
+
includeCodeRabbit: null, // --coderabbit / --no-coderabbit (기본 false — DESIGN-SPEC §4 질문②)
|
|
14
|
+
pathsCsv: "", // "flutter=app,react=client" 원문 (정규화는 resolve 단계)
|
|
15
|
+
mainBranch: "", // 릴리스 브랜치 (--main-branch). 빈값=감지된 default branch
|
|
16
|
+
developBranch: "", // 개발 브랜치 (--develop-branch). 빈값=develop
|
|
17
|
+
force: false,
|
|
18
|
+
help: false,
|
|
19
|
+
showVersion: false, // -v/--version → 패키지 버전 출력 (npm 관례)
|
|
20
|
+
};
|
|
21
|
+
const args = [...argv];
|
|
22
|
+
while (args.length > 0) {
|
|
23
|
+
const a = args.shift();
|
|
24
|
+
switch (a) {
|
|
25
|
+
case "-m": case "--mode":
|
|
26
|
+
result.mode = args.shift() ?? ""; break;
|
|
27
|
+
case "-v": case "--version":
|
|
28
|
+
// npm 관례: -v/--version 은 패키지 버전 출력. (초기 버전 지정은 --project-version)
|
|
29
|
+
result.showVersion = true; break;
|
|
30
|
+
case "--project-version":
|
|
31
|
+
result.version = args.shift() ?? ""; break;
|
|
32
|
+
case "-t": case "--type": {
|
|
33
|
+
const csv = args.shift() ?? "";
|
|
34
|
+
const seen = new Set();
|
|
35
|
+
const types = [];
|
|
36
|
+
for (let t of csv.split(",")) {
|
|
37
|
+
t = t.replace(/\s/g, "");
|
|
38
|
+
if (t === "") continue;
|
|
39
|
+
if (seen.has(t)) continue; // dedup
|
|
40
|
+
if (!VALID_TYPES.includes(t)) {
|
|
41
|
+
throw new CliError(`지원하지 않는 타입: '${t}'\n지원 타입: ${VALID_TYPES.join(" ")}`);
|
|
42
|
+
}
|
|
43
|
+
seen.add(t);
|
|
44
|
+
types.push(t);
|
|
45
|
+
}
|
|
46
|
+
if (types.length === 0) throw new CliError("--type 인자가 비어 있습니다");
|
|
47
|
+
result.types = types;
|
|
48
|
+
result.primaryType = types[0];
|
|
49
|
+
break;
|
|
50
|
+
}
|
|
51
|
+
case "--force": result.force = true; break;
|
|
52
|
+
case "--nexus": result.includeNexus = true; break;
|
|
53
|
+
case "--no-nexus": result.includeNexus = false; break;
|
|
54
|
+
case "--secret-backup": result.includeSecretBackup = true; break;
|
|
55
|
+
case "--no-secret-backup": result.includeSecretBackup = false; break;
|
|
56
|
+
case "--coderabbit": result.includeCodeRabbit = true; break;
|
|
57
|
+
case "--no-coderabbit": result.includeCodeRabbit = false; break;
|
|
58
|
+
case "--paths": result.pathsCsv = args.shift() ?? ""; break;
|
|
59
|
+
case "--main-branch": result.mainBranch = args.shift() ?? ""; break;
|
|
60
|
+
case "--develop-branch": result.developBranch = args.shift() ?? ""; break;
|
|
61
|
+
case "-h": case "--help": result.help = true; break;
|
|
62
|
+
default:
|
|
63
|
+
throw new CliError(`알 수 없는 옵션: ${a}`);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
return result;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export class CliError extends Error {}
|
|
70
|
+
|
|
71
|
+
// 경로 정규화 (.sh resolve_project_paths §3.4): 앞뒤 공백·\→/·끝 /·앞 ./ 제거, 빈값→"."
|
|
72
|
+
export function normalizePath(p) {
|
|
73
|
+
let s = String(p).trim();
|
|
74
|
+
s = s.replace(/\\/g, "/");
|
|
75
|
+
s = s.replace(/\/+$/, ""); // 끝 /
|
|
76
|
+
s = s.replace(/^\.\//, ""); // 앞 ./
|
|
77
|
+
return s === "" ? "." : s;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// "flutter=app,react=client" → Map<type, normalizedPath>. 타입 검증(무효 → throw).
|
|
81
|
+
export function parsePathsCsv(csv) {
|
|
82
|
+
const map = new Map();
|
|
83
|
+
if (!csv) return map;
|
|
84
|
+
for (const pair of csv.split(",")) {
|
|
85
|
+
if (pair.trim() === "") continue;
|
|
86
|
+
const eq = pair.indexOf("=");
|
|
87
|
+
const type = (eq >= 0 ? pair.slice(0, eq) : pair).trim();
|
|
88
|
+
const rawPath = eq >= 0 ? pair.slice(eq + 1) : "";
|
|
89
|
+
if (!VALID_TYPES.includes(type)) {
|
|
90
|
+
throw new CliError(`--paths에 지원하지 않는 타입: '${type}'`);
|
|
91
|
+
}
|
|
92
|
+
map.set(type, normalizePath(rawPath));
|
|
93
|
+
}
|
|
94
|
+
return map;
|
|
95
|
+
}
|
package/src/cli/help.js
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
// --help 텍스트.
|
|
2
|
+
export const HELP_TEXT = `project-auto-wizard — One command DevOps: GitHub-native AI 릴리스 자동화 설치 마법사
|
|
3
|
+
|
|
4
|
+
사용법:
|
|
5
|
+
npx project-auto-wizard [옵션]
|
|
6
|
+
|
|
7
|
+
옵션:
|
|
8
|
+
-m, --mode MODE 통합 모드 (full | version | workflows | revert)
|
|
9
|
+
기본: interactive (대화형). revert = 설치물 제거(되돌리기)
|
|
10
|
+
-t, --type CSV 프로젝트 타입 csv (예: spring,react,python)
|
|
11
|
+
지원: spring flutter next react react-native
|
|
12
|
+
react-native-expo node python basic
|
|
13
|
+
--project-version V 통합 대상의 초기 버전 (예: 1.0.0). 미지정 시 자동 감지
|
|
14
|
+
--paths "t=p,..." 타입별 프로젝트 경로 (모노레포). 예: flutter=app,react=client
|
|
15
|
+
--main-branch B 릴리스 브랜치 (기본: 감지된 default branch)
|
|
16
|
+
--develop-branch B 개발 브랜치 (기본: develop). 릴리스 브랜치와 같으면 trunk-based 모드
|
|
17
|
+
--nexus / --no-nexus Nexus 라이브러리 publish 워크플로우 포함/제외
|
|
18
|
+
--secret-backup / --no-secret-backup Secret 백업 워크플로우 포함/제외
|
|
19
|
+
--coderabbit / --no-coderabbit CodeRabbit PR 요약을 릴리스 노트 1순위로 사용 (기본: 사용 안 함)
|
|
20
|
+
--force 모든 확인 생략, 비대화형 기본값 사용
|
|
21
|
+
-v, --version project-auto-wizard 버전 출력
|
|
22
|
+
-h, --help 이 도움말 표시
|
|
23
|
+
|
|
24
|
+
예시:
|
|
25
|
+
npx project-auto-wizard --mode full --force --type spring,react
|
|
26
|
+
npx project-auto-wizard --mode workflows --type flutter --paths "flutter=app"
|
|
27
|
+
`;
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
// full 모드 오케스트레이터 (.sh execute_integration full case 등가).
|
|
2
|
+
// 복사 순서: workflows(+env 치환) → version.yml → readme → scripts → coderabbit → gitignore
|
|
3
|
+
// (원본의 util/issue/discussion/setup-guide/config 설치는 project-auto-wizard 스코프에서 제외 — DESIGN-SPEC §2)
|
|
4
|
+
import { join } from "node:path";
|
|
5
|
+
import { writeText } from "../core/fsutil.js";
|
|
6
|
+
import { PATHS } from "../core/paths.js";
|
|
7
|
+
import { buildVersionYml } from "../core/version-yml.js";
|
|
8
|
+
import { readVersionYmlTemplate } from "../core/assets.js";
|
|
9
|
+
import { markerForType } from "../core/detect.js";
|
|
10
|
+
import { addVersionSectionToReadme } from "../core/copy/readme.js";
|
|
11
|
+
import { copyWorkflows } from "../core/copy/workflows.js";
|
|
12
|
+
import { copyScripts } from "../core/copy/simple.js";
|
|
13
|
+
import { copyCoderabbit } from "../core/copy/coderabbit.js";
|
|
14
|
+
import { ensureGitignore } from "../core/copy/gitignore.js";
|
|
15
|
+
|
|
16
|
+
// context: { version, types, paths:Map, branch, versionCode, includeNexus, includeSecretBackup,
|
|
17
|
+
// force, repoName, resolvers, now, today }
|
|
18
|
+
// payloadRoot: 패키지 payload/ 루트. targetRoot: 통합 대상.
|
|
19
|
+
export function runFull(context, payloadRoot, targetRoot = ".", hooks = {}) {
|
|
20
|
+
const { version, types = [], paths = new Map(), branch = "main", versionCode = 1,
|
|
21
|
+
force = true, now, today, templateVersion = "unknown",
|
|
22
|
+
includeNexus = false, includeSecretBackup = false, includeCodeRabbit = false } = context;
|
|
23
|
+
|
|
24
|
+
// project_paths 마커 계산 (.sh existing_marker_in_dir 등가 — 대표 마커명)
|
|
25
|
+
const pathMarkers = new Map();
|
|
26
|
+
for (const [t] of paths) pathMarkers.set(t, markerForType(t));
|
|
27
|
+
|
|
28
|
+
// 1. 워크플로우 복사 (+ env 치환) — deploy 블록에 쓸 ask 값을 수집한다.
|
|
29
|
+
// hooks.decisions: 대화형 충돌 3지선 결정 Map (미지정=skip — 현행 force 동작)
|
|
30
|
+
const wfCounters = copyWorkflows(context, payloadRoot, targetRoot, hooks);
|
|
31
|
+
const deployValues = wfCounters.deployValues || new Map(); // Map<type, Map<key,value>>
|
|
32
|
+
|
|
33
|
+
// 2. version.yml 생성 (payload/version.yml.template 렌더링 — 전체 재생성 전략 D4)
|
|
34
|
+
writeText(join(targetRoot, PATHS.versionFile),
|
|
35
|
+
buildVersionYml({
|
|
36
|
+
templateText: readVersionYmlTemplate(payloadRoot),
|
|
37
|
+
version, types, paths, pathMarkers, branch, branches: context.branches, versionCode, now, today,
|
|
38
|
+
deployValues,
|
|
39
|
+
templateOptions: { templateVersion, includeNexus, includeSecretBackup, includeCodeRabbit: includeCodeRabbit === true, optionsDate: today },
|
|
40
|
+
}));
|
|
41
|
+
|
|
42
|
+
// 3. README 버전 섹션
|
|
43
|
+
addVersionSectionToReadme(version, targetRoot);
|
|
44
|
+
|
|
45
|
+
// 4. scripts (payload/scripts/*.py → .github/scripts/)
|
|
46
|
+
copyScripts(payloadRoot, targetRoot);
|
|
47
|
+
|
|
48
|
+
// 5. coderabbit (opt-in true일 때만 — DESIGN-SPEC §4 질문②) / gitignore
|
|
49
|
+
if (includeCodeRabbit === true) copyCoderabbit(payloadRoot, { force }, targetRoot);
|
|
50
|
+
ensureGitignore(targetRoot);
|
|
51
|
+
|
|
52
|
+
return { workflows: wfCounters };
|
|
53
|
+
}
|