project-auto-wizard 0.5.0 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,384 @@
1
+ # project-auto-wizard:managed-workflow
2
+ # ===================================================================
3
+ # Go CI/CD 배포 (SSH + Docker)
4
+ # ===================================================================
5
+ #
6
+ # {{MAIN_BRANCH}} 브랜치 푸시 시 Docker 이미지를 빌드하고
7
+ # SSH로 접속 가능한 서버(Synology·AWS EC2·VPS 등)에 자동 배포합니다.
8
+ #
9
+ # ===================================================================
10
+ # 🔑 필수 GitHub Secrets
11
+ # ===================================================================
12
+ # ENV_FILE: .env 파일 내용
13
+ # DOCKERHUB_USERNAME: DockerHub 사용자명
14
+ # DOCKERHUB_TOKEN: DockerHub 액세스 토큰
15
+ # SERVER_HOST: 배포 서버 주소
16
+ # SERVER_USER: SSH 사용자명
17
+ # SERVER_PASSWORD: SSH 비밀번호 (SSH_AUTH_METHOD=password일 때)
18
+ # SSH_KEY: SSH 개인키 .pem 내용 (SSH_AUTH_METHOD=key일 때, AWS EC2 등)
19
+ # ===================================================================
20
+ #
21
+ # 🔧 환경변수 설정 (env 섹션에서 설정)
22
+ # ===================================================================
23
+ #
24
+ # 🌐 포트 및 컨테이너:
25
+ # CONTAINER_INTERNAL_PORT: Go 앱 내부 포트 (기본: 8080)
26
+ # DEPLOY_PORT: 배포 포트
27
+ # CONTAINER_NAME: 컨테이너 이름 (기본: PROJECT_NAME)
28
+ #
29
+ # 📦 볼륨 마운트:
30
+ # ENABLE_VOLUME_MOUNT: 볼륨 마운트 활성화 (true/false)
31
+ # VOLUME_HOST_PATH: 호스트 볼륨 경로
32
+ # VOLUME_CONTAINER_PATH: 컨테이너 내부 마운트 경로
33
+ #
34
+ # 🔌 SSH 연결:
35
+ # SSH_PORT: SSH 포트 (기본: 2022)
36
+ # SSH_COMMAND_TIMEOUT: SSH 명령 타임아웃 (기본: 600s)
37
+ # SSH_CONNECTION_TIMEOUT: SSH 연결 타임아웃 (기본: 60s)
38
+ #
39
+ # ⏱️ 헬스체크:
40
+ # HEALTHCHECK_WAIT_SECONDS: 초기화 대기 시간 (기본: 10초)
41
+ # HEALTHCHECK_PATH: HTTP 엔드포인트 경로 (비어있으면 로그만 사용)
42
+ # HEALTHCHECK_MAX_RETRIES: HTTP 재시도 횟수 (기본: 5회)
43
+ # HEALTHCHECK_RETRY_INTERVAL: 재시도 간격 (기본: 3초)
44
+ # HEALTHCHECK_LOG_PATTERN: Fallback 로그 검색 패턴
45
+ #
46
+ # 💡 HEALTHCHECK_PATH 예시:
47
+ # Go (net/http 등): "/health"
48
+ # 커스텀: "/api/v1/health"
49
+ # ===================================================================
50
+
51
+ name: PROJECT-GO-SIMPLE-CICD
52
+
53
+ # ===================================================================
54
+ # 트리거 설정
55
+ # ===================================================================
56
+ on:
57
+ push:
58
+ branches:
59
+ - "{{MAIN_BRANCH}}" # 프로덕션 배포
60
+ # @wizard paths-anchor (모노레포일 때 integrator가 paths 필터를 여기 주입)
61
+ workflow_dispatch: # 수동 실행 허용
62
+
63
+ # ===================================================================
64
+ # 환경 변수 설정
65
+ # ===================================================================
66
+ env:
67
+ # 프로젝트 고유 식별자
68
+ PROJECT_NAME: "__PROJECT_NAME__" # @wizard ask:@repo
69
+
70
+ # 🌐 포트 설정
71
+ DEPLOY_PORT: "__DEPLOY_PORT__" # @wizard ask:8080
72
+
73
+ # 🐳 컨테이너 이름 설정 (비워두면 PROJECT_NAME 사용)
74
+ CONTAINER_NAME: ""
75
+
76
+ # 🗂️ 볼륨 마운트 설정 (빈값이면 마운트 안함)
77
+ ENABLE_VOLUME_MOUNT: "false"
78
+ VOLUME_HOST_PATH: "/volume1/projects/__PROJECT_NAME__" # @wizard ask:/volume1/projects/__PROJECT_NAME__
79
+ VOLUME_CONTAINER_PATH: "/mnt/__PROJECT_NAME__" # @wizard ask:/mnt/__PROJECT_NAME__
80
+
81
+ # 🔐 SSH 인증 방식: "password"(Synology·일반 서버) | "key"(AWS EC2·.pem)
82
+ # password → SERVER_PASSWORD secret 사용
83
+ # key → SSH_KEY secret 사용 (.pem 파일 전체 내용을 secret 값으로)
84
+ SSH_AUTH_METHOD: "password" # @wizard ask:password
85
+
86
+ # 🔌 SSH 연결 설정
87
+ SSH_PORT: "__SSH_PORT__" # @wizard ask:2022
88
+ SSH_COMMAND_TIMEOUT: "600s" # SSH 명령 실행 타임아웃
89
+ SSH_CONNECTION_TIMEOUT: "60s" # SSH 연결 타임아웃
90
+
91
+ # 🌐 컨테이너 포트 설정
92
+ CONTAINER_INTERNAL_PORT: "8080" # Go 앱 내부 포트
93
+
94
+ # ⏱️ 헬스체크 설정
95
+ HEALTHCHECK_WAIT_SECONDS: "10" # 컨테이너 초기화 대기 시간 (초)
96
+ HEALTHCHECK_PATH: "/health" # HTTP 엔드포인트 경로 (비어있으면 로그 패턴만 사용)
97
+ HEALTHCHECK_MAX_RETRIES: "5" # HTTP 재시도 횟수
98
+ HEALTHCHECK_RETRY_INTERVAL: "3" # 재시도 간격 (초)
99
+ HEALTHCHECK_LOG_PATTERN: "listening on" # Fallback 로그 검색 패턴 (프로젝트 로그 문구에 맞게 조정)
100
+
101
+ jobs:
102
+ # ===================================================================
103
+ # 빌드 작업
104
+ # ===================================================================
105
+ build:
106
+ name: Go 애플리케이션 빌드
107
+ runs-on: ubuntu-latest
108
+
109
+ steps:
110
+ # 1. 소스코드 체크아웃
111
+ - name: 코드 체크아웃
112
+ uses: actions/checkout@v7
113
+
114
+ # 2. 디스크 공간 확보
115
+ - name: 디스크 공간 확보
116
+ run: |
117
+ echo "📊 빌드 전 디스크 사용량:"
118
+ df -h
119
+
120
+ echo "🧹 불필요한 파일 정리 중..."
121
+ sudo rm -rf /usr/share/dotnet || true
122
+ sudo rm -rf /opt/ghc || true
123
+ sudo rm -rf /usr/local/share/boost || true
124
+ sudo rm -rf "$AGENT_TOOLSDIRECTORY" || true
125
+
126
+ echo "🐳 Docker 시스템 정리 중..."
127
+ docker system prune -af --volumes || true
128
+
129
+ echo "📊 정리 후 디스크 사용량:"
130
+ df -h
131
+
132
+ # 3. .env 파일 생성
133
+ - name: .env 파일 생성
134
+ run: |
135
+ cat > .env << 'EOF'
136
+ ${{ secrets.ENV_FILE }}
137
+ EOF
138
+ echo "✅ .env 파일이 생성되었습니다"
139
+
140
+ # 4. Docker 빌드 환경 설정
141
+ - name: Docker 빌드환경 설정
142
+ uses: docker/setup-buildx-action@v3
143
+
144
+ # 5. DockerHub 로그인
145
+ - name: DockerHub 로그인
146
+ uses: docker/login-action@v3
147
+ with:
148
+ username: ${{ secrets.DOCKERHUB_USERNAME }}
149
+ password: ${{ secrets.DOCKERHUB_TOKEN }}
150
+
151
+ # 6. Docker 이미지 빌드 및 푸시
152
+ - name: Docker 이미지 빌드 및 푸시
153
+ uses: docker/build-push-action@v5
154
+ with:
155
+ context: .
156
+ file: ./Dockerfile
157
+ push: true
158
+ tags: ${{ secrets.DOCKERHUB_USERNAME }}/${{ env.PROJECT_NAME }}:${{ github.ref_name }}
159
+ # GitHub Actions 캐시 사용 (로컬 디스크 공간 절약)
160
+ cache-from: type=gha
161
+ cache-to: type=gha,mode=max
162
+
163
+ # 7. 빌드 후 정리
164
+ - name: 빌드 후 정리
165
+ if: always()
166
+ run: |
167
+ echo "🧹 빌드 후 정리 중..."
168
+ docker system prune -f || true
169
+ echo "📊 최종 디스크 사용량:"
170
+ df -h
171
+
172
+ # ===================================================================
173
+ # 배포 작업
174
+ # ===================================================================
175
+ deploy:
176
+ name: 원격 서버 배포
177
+ needs: build
178
+ runs-on: ubuntu-latest
179
+
180
+ steps:
181
+ # SSH 통한 원격 서버 배포 실행
182
+ - name: Deploy
183
+ uses: appleboy/ssh-action@v1.0.3
184
+ env:
185
+ SSH_AUTH_METHOD: ${{ env.SSH_AUTH_METHOD }}
186
+ with:
187
+ host: ${{ secrets.SERVER_HOST }}
188
+ username: ${{ secrets.SERVER_USER }}
189
+ password: ${{ secrets.SERVER_PASSWORD }}
190
+ key: ${{ secrets.SSH_KEY }}
191
+ port: ${{ env.SSH_PORT }}
192
+ command_timeout: ${{ env.SSH_COMMAND_TIMEOUT }}
193
+ timeout: ${{ env.SSH_CONNECTION_TIMEOUT }}
194
+ envs: SSH_AUTH_METHOD
195
+ script: |
196
+ set -e
197
+
198
+ # ============================================================
199
+ # 배포 환경 변수 설정
200
+ # ============================================================
201
+ echo "🔧 환경변수 설정.."
202
+ export PATH=$PATH:/usr/local/bin
203
+ export PW=${{ secrets.SERVER_PASSWORD }}
204
+
205
+ # 🔐 SSH 인증 방식에 따른 sudo 추상화
206
+ # password 모드: echo $PW | sudo -S (Synology 등 sudo 비밀번호 필요 서버)
207
+ # key 모드: sudo 직접 (AWS EC2 등 passwordless sudo 서버)
208
+ SSH_AUTH_METHOD="${SSH_AUTH_METHOD:-password}"
209
+ if [ "${SSH_AUTH_METHOD}" = "key" ]; then
210
+ SUDO() { sudo "$@"; }
211
+ else
212
+ SUDO() { echo "$PW" | sudo -S "$@"; }
213
+ fi
214
+ echo "🔐 SSH 인증 방식: ${SSH_AUTH_METHOD}"
215
+
216
+ # GitHub에서 전달받은 브랜치명
217
+ BRANCH=${{ github.ref_name }}
218
+
219
+ # 프로젝트 설정
220
+ PROJECT_NAME="${{ env.PROJECT_NAME }}"
221
+
222
+ # ============================================================
223
+ # 포트 및 컨테이너명 설정
224
+ # ============================================================
225
+ PORT=${{ env.DEPLOY_PORT }}
226
+
227
+ # 컨테이너 이름 설정
228
+ if [ -z "${{ env.CONTAINER_NAME }}" ]; then
229
+ CONTAINER_NAME="${PROJECT_NAME}"
230
+ echo "ℹ️ CONTAINER_NAME이 설정되지 않아 PROJECT_NAME을 사용합니다: $CONTAINER_NAME"
231
+ else
232
+ CONTAINER_NAME="${{ env.CONTAINER_NAME }}"
233
+ echo "ℹ️ 사용자 지정 CONTAINER_NAME 사용: $CONTAINER_NAME"
234
+ fi
235
+
236
+ echo "🚀 배포 환경으로 배포합니다 (브랜치: $BRANCH)"
237
+
238
+ # 설정 정보 출력
239
+ echo "📋 배포 설정 정보:"
240
+ echo " - 브랜치: $BRANCH"
241
+ echo " - 프로젝트: $PROJECT_NAME"
242
+ echo " - 컨테이너 이름: $CONTAINER_NAME"
243
+ echo " - 포트: $PORT"
244
+ echo " - Docker 이미지: ${{ secrets.DOCKERHUB_USERNAME }}/${PROJECT_NAME}:${BRANCH}"
245
+
246
+ # ============================================================
247
+ # Docker 이미지 풀 (Pull)
248
+ # ============================================================
249
+ echo "⬇️ Docker 이미지 풀: ${{ secrets.DOCKERHUB_USERNAME }}/${PROJECT_NAME}:${BRANCH}"
250
+ SUDO docker pull ${{ secrets.DOCKERHUB_USERNAME }}/${PROJECT_NAME}:${BRANCH}
251
+
252
+ # ============================================================
253
+ # 기존 컨테이너 정리
254
+ # ============================================================
255
+ echo "🧹 컨테이너 $CONTAINER_NAME 존재 여부 확인 중..."
256
+
257
+ # 동일한 이름의 컨테이너가 존재하는지 확인
258
+ if SUDO docker ps -a --format '{{.Names}}' | grep -Eq "^${CONTAINER_NAME}\$"; then
259
+ echo "⚠️ 컨테이너 $CONTAINER_NAME 이(가) 존재합니다. 중지 및 삭제 중..."
260
+ SUDO docker rm -f $CONTAINER_NAME
261
+ echo "✅ 컨테이너 $CONTAINER_NAME 이(가) 삭제되었습니다."
262
+ else
263
+ echo "ℹ️ 존재하는 컨테이너 $CONTAINER_NAME 이(가) 없습니다."
264
+ fi
265
+
266
+ # ============================================================
267
+ # 새 컨테이너 실행
268
+ # ============================================================
269
+ echo "🚀 새로운 컨테이너 $CONTAINER_NAME 실행 중..."
270
+
271
+ # Docker 컨테이너 실행
272
+ # - 포트 매핑: ${PORT}:${{ env.CONTAINER_INTERNAL_PORT }} (외부:내부)
273
+ # - 시간대: Asia/Seoul
274
+ # - 볼륨 마운트: 로컬 시간 동기화 및 프로젝트 데이터
275
+ # ⚠️ .env 파일은 이미 Docker 이미지에 포함되어 있습니다
276
+
277
+ # 볼륨 마운트 옵션 구성
278
+ VOLUME_OPTS="-v /etc/localtime:/etc/localtime:ro"
279
+ if [ "${{ env.ENABLE_VOLUME_MOUNT }}" == "true" ]; then
280
+ # 디렉토리가 없으면 생성
281
+ if [ ! -d "${{ env.VOLUME_HOST_PATH }}" ]; then
282
+ echo "📁 볼륨 마운트 디렉토리 생성: ${{ env.VOLUME_HOST_PATH }}"
283
+ SUDO mkdir -p "${{ env.VOLUME_HOST_PATH }}"
284
+ fi
285
+ VOLUME_OPTS="$VOLUME_OPTS -v ${{ env.VOLUME_HOST_PATH }}:${{ env.VOLUME_CONTAINER_PATH }}"
286
+ echo "📦 볼륨 마운트 활성화: $VOLUME_OPTS"
287
+ else
288
+ echo "ℹ️ 볼륨 마운트 비활성화됨"
289
+ fi
290
+
291
+ SUDO docker run -d \
292
+ -p ${PORT}:${{ env.CONTAINER_INTERNAL_PORT }} \
293
+ --name $CONTAINER_NAME \
294
+ -e TZ=Asia/Seoul \
295
+ $VOLUME_OPTS \
296
+ ${{ secrets.DOCKERHUB_USERNAME }}/${PROJECT_NAME}:${BRANCH}
297
+
298
+ # ============================================================
299
+ # 컨테이너 헬스체크
300
+ # ============================================================
301
+ echo "⏳ 컨테이너 초기화 대기 중... (${{ env.HEALTHCHECK_WAIT_SECONDS }}초)"
302
+ sleep ${{ env.HEALTHCHECK_WAIT_SECONDS }}
303
+
304
+ # 1단계: 컨테이너 프로세스 확인
305
+ echo "🏥 컨테이너 헬스체크 중..."
306
+ if ! SUDO docker ps --format '{{.Names}}' | grep -Eq "^${CONTAINER_NAME}\$"; then
307
+ echo "❌ 컨테이너 $CONTAINER_NAME 프로세스가 실행되지 않았습니다"
308
+ SUDO docker logs --tail 50 $CONTAINER_NAME || true
309
+ exit 1
310
+ fi
311
+ echo "✅ 컨테이너 프로세스 실행 확인 완료"
312
+
313
+ # 2단계: HTTP 엔드포인트 체크 (설정된 경우)
314
+ HEALTHCHECK_SUCCESS=false
315
+ if [ -n "${{ env.HEALTHCHECK_PATH }}" ]; then
316
+ echo "🌐 HTTP 헬스체크 시작: http://localhost:${PORT}${{ env.HEALTHCHECK_PATH }}"
317
+
318
+ for i in $(seq 1 ${{ env.HEALTHCHECK_MAX_RETRIES }}); do
319
+ echo " 시도 $i/${{ env.HEALTHCHECK_MAX_RETRIES }}..."
320
+
321
+ HTTP_CODE=$(curl -s -o /dev/null -w "%{http_code}" http://localhost:${PORT}${{ env.HEALTHCHECK_PATH }} || echo "000")
322
+
323
+ if [ "$HTTP_CODE" = "200" ]; then
324
+ echo "✅ HTTP 헬스체크 성공 (응답 코드: $HTTP_CODE)"
325
+ HEALTHCHECK_SUCCESS=true
326
+ break
327
+ else
328
+ echo "⚠️ HTTP 헬스체크 실패 (응답 코드: $HTTP_CODE)"
329
+ if [ $i -lt ${{ env.HEALTHCHECK_MAX_RETRIES }} ]; then
330
+ echo " ${{ env.HEALTHCHECK_RETRY_INTERVAL }}초 후 재시도..."
331
+ sleep ${{ env.HEALTHCHECK_RETRY_INTERVAL }}
332
+ fi
333
+ fi
334
+ done
335
+
336
+ # HTTP 체크 실패 시 fallback으로 로그 패턴 매칭
337
+ if [ "$HEALTHCHECK_SUCCESS" = "false" ]; then
338
+ echo "⚠️ HTTP 헬스체크 최종 실패, 로그 패턴 매칭으로 fallback..."
339
+ fi
340
+ else
341
+ echo "ℹ️ HEALTHCHECK_PATH가 설정되지 않아 로그 패턴 매칭만 사용합니다"
342
+ fi
343
+
344
+ # 3단계: 로그 패턴 매칭 (HTTP 실패 시 또는 HTTP 체크 비활성화 시)
345
+ if [ "$HEALTHCHECK_SUCCESS" = "false" ]; then
346
+ echo "📋 로그 패턴 매칭 시작: '${{ env.HEALTHCHECK_LOG_PATTERN }}'"
347
+
348
+ if SUDO docker logs $CONTAINER_NAME 2>&1 | grep -q "${{ env.HEALTHCHECK_LOG_PATTERN }}"; then
349
+ echo "✅ 로그 패턴 매칭 성공: 애플리케이션이 정상적으로 시작되었습니다"
350
+ HEALTHCHECK_SUCCESS=true
351
+ else
352
+ echo "❌ 로그 패턴 매칭 실패: 애플리케이션 시작 로그를 찾을 수 없습니다"
353
+ echo ""
354
+ echo "📋 디버깅 정보:"
355
+ echo " - 검색 패턴: ${{ env.HEALTHCHECK_LOG_PATTERN }}"
356
+ echo " - 컨테이너: $CONTAINER_NAME"
357
+ echo " - 이미지: ${{ secrets.DOCKERHUB_USERNAME }}/${PROJECT_NAME}:${BRANCH}"
358
+ echo " - 포트: ${PORT}:${{ env.CONTAINER_INTERNAL_PORT }}"
359
+ echo " - 볼륨: $VOLUME_OPTS"
360
+ echo ""
361
+ echo "📋 컨테이너 로그 (최근 50줄):"
362
+ SUDO docker logs --tail 50 $CONTAINER_NAME || true
363
+ exit 1
364
+ fi
365
+ fi
366
+
367
+ # 최종 성공 메시지
368
+ if [ "$HEALTHCHECK_SUCCESS" = "true" ]; then
369
+ echo "✅ 헬스체크 완료: 컨테이너 $CONTAINER_NAME 이(가) 정상적으로 실행 중입니다"
370
+ fi
371
+
372
+ # ============================================================
373
+ # 배포 완료 확인
374
+ # ============================================================
375
+ echo "✅ 배포가 성공적으로 완료되었습니다!"
376
+ echo ""
377
+ echo "📋 배포 결과 요약:"
378
+ echo " 🎯 프로젝트: $PROJECT_NAME"
379
+ echo " 🌿 브랜치: $BRANCH"
380
+ echo " 🐳 컨테이너: $CONTAINER_NAME"
381
+ echo " 🌐 포트: $PORT"
382
+ echo " ⏰ 배포 시간: $(date '+%Y-%m-%d %H:%M:%S')"
383
+ echo ""
384
+ echo "🔗 접속 URL: http://${{ secrets.SERVER_HOST }}:${PORT}"
@@ -24,6 +24,9 @@ permissions:
24
24
  issues: write
25
25
  checks: write
26
26
 
27
+ env:
28
+ JAVA_VERSION: "__JAVA_VERSION__" # @wizard ask:@jdk
29
+
27
30
  jobs:
28
31
  build-check:
29
32
  name: Build Verification
@@ -35,10 +38,10 @@ jobs:
35
38
  with:
36
39
  fetch-depth: 0
37
40
 
38
- - name: Setup JDK 17
41
+ - name: Setup JDK
39
42
  uses: actions/setup-java@v5
40
43
  with:
41
- java-version: '17'
44
+ java-version: ${{ env.JAVA_VERSION }}
42
45
  distribution: 'temurin'
43
46
 
44
47
  - name: Validate Gradle Wrapper
@@ -16,16 +16,26 @@ on:
16
16
  tags:
17
17
  - 'v*.*.*'
18
18
 
19
+ concurrency:
20
+ group: nexus-publish-${{ github.ref }}
21
+ cancel-in-progress: false
22
+
23
+ permissions:
24
+ contents: read
25
+
26
+ env:
27
+ JAVA_VERSION: "__JAVA_VERSION__" # @wizard ask:@jdk
28
+
19
29
  jobs:
20
30
  publish:
21
31
  runs-on: ubuntu-latest
22
32
  steps:
23
33
  - uses: actions/checkout@v7
24
34
 
25
- - name: Set up JDK 17
35
+ - name: Set up JDK
26
36
  uses: actions/setup-java@v5
27
37
  with:
28
- java-version: '17'
38
+ java-version: ${{ env.JAVA_VERSION }}
29
39
  distribution: 'temurin'
30
40
 
31
41
  - name: Setup Gradle
package/src/cli/help.js CHANGED
@@ -11,7 +11,7 @@ export const HELP_TEXT = `project-auto-wizard — One command DevOps: GitHub-nat
11
11
  status = 설치 상태·드리프트 확인(읽기 전용). doctor = 환경 진단(읽기 전용)
12
12
  -t, --type CSV 프로젝트 타입 csv (예: spring,react,python)
13
13
  지원: spring flutter next react react-native
14
- react-native-expo node python basic
14
+ react-native-expo node python basic go
15
15
  --project-version V 통합 대상의 초기 버전 (예: 1.0.0). 미지정 시 자동 감지
16
16
  --paths "t=p,..." 타입별 프로젝트 경로 (모노레포). 예: flutter=app,react=client
17
17
  --main-branch B 릴리스 브랜치 (기본: 감지된 default branch)
@@ -116,18 +116,15 @@ export function runDoctor(cwd = process.cwd(), { exec = defaultExec } = {}) {
116
116
  add(hasPat
117
117
  ? { name: "WORKFLOW_PAT secret", label: "WORKFLOW_PAT", purpose: "자동 태그·Release 발행", status: "OK", value: "등록됨" }
118
118
  : {
119
- name: "WORKFLOW_PAT secret", label: "WORKFLOW_PAT", purpose: "자동 태그·Release 발행", status: "WARN",
120
- value: "secret이 등록되어 있지 않습니다.",
121
- impact: [
122
- "PR 자동 머지 태그·Release 워크플로가 이어지지 않습니다.",
123
- "(GitHub 정책상 기본 토큰으로 만든 커밋은 다음 워크플로를 깨우지 못합니다)",
124
- ],
125
- actions: [
126
- "개인 액세스 토큰 발급 (scopes: repo, workflow)",
127
- "레포 Settings → Secrets and variables → Actions",
128
- "New repository secret · 이름은 WORKFLOW_PAT",
119
+ // 조치가 필요 없으므로 WARN이 아니라 INFO다 Workflow permissions 항목과 동일 이유:
120
+ // 폴백(wait-for-merge-and-trigger-release / Trigger NPM-PUBLISH)이 GITHUB_TOKEN만으로
121
+ // 파이프라인을 끝까지 이어가므로, 없는 장애를 경고로 띄우지 않는다(이슈 #105).
122
+ name: "WORKFLOW_PAT secret", label: "WORKFLOW_PAT", purpose: "자동 태그·Release 발행", status: "INFO",
123
+ note: [
124
+ "secret이 없어도 폴백이 자동으로 이어받아 태그·Release까지 진행됩니다 — 실제 병합 후 최대 ~20초 정도 더 걸릴 뿐입니다.",
125
+ "속도를 더 원한다면 PAT을 등록할 수 있습니다 — 반드시 개인 계정이 아닌 조직 bot/machine 계정으로 발급하세요 (scopes: repo, workflow).",
126
+ "등록: 레포 Settings Secrets and variables → Actions → New repository secret · 이름은 WORKFLOW_PAT",
129
127
  ],
130
- doc: DOC.postInstall,
131
128
  });
132
129
 
133
130
  const mergeSettings = exec("gh", ["api", `repos/${owner}/${repo}`, "--jq", ".allow_merge_commit"]);
package/src/context.js CHANGED
@@ -1,7 +1,7 @@
1
1
  // 마법사 전역 상태를 하나의 객체로 명시화 (bash 전역 변수군 대체)
2
2
  export const VALID_TYPES = [
3
3
  "spring", "flutter", "next", "react",
4
- "react-native", "react-native-expo", "node", "python", "basic",
4
+ "react-native", "react-native-expo", "node", "python", "basic", "go",
5
5
  ];
6
6
 
7
7
  // --mode 화이트리스트 (issue #19) — 알 수 없는 값은 부수효과(브랜치 조회 등) 이전에 즉시 거부해야 한다.
@@ -24,6 +24,7 @@ export function detectTypesFromMarkers({ has, read }) {
24
24
  if (has("pubspec.yaml")) types.push("flutter");
25
25
  if (has("build.gradle") || has("build.gradle.kts") || has("pom.xml")) types.push("spring");
26
26
  if (has("pyproject.toml") || has("setup.py") || has("requirements.txt")) types.push("python");
27
+ if (has("go.mod")) types.push("go");
27
28
  if (has("package.json")) {
28
29
  const cls = classifyPackageText(read ? read("package.json") : "");
29
30
  if (cls === "node") { if (types.length === 0) types.push("node"); }
@@ -73,7 +74,7 @@ export function versionFromPom(content) {
73
74
  }
74
75
 
75
76
  export function markerForType(type) {
76
- return { flutter: "pubspec.yaml", "react-native-expo": "app.json", python: "pyproject.toml", spring: "build.gradle" }[type] || "package.json";
77
+ return { flutter: "pubspec.yaml", "react-native-expo": "app.json", python: "pyproject.toml", spring: "build.gradle", go: "go.mod" }[type] || "package.json";
77
78
  }
78
79
 
79
80
  export function extraMarkers(type) {
@@ -18,7 +18,7 @@ const isCancel = (v) => typeof v === "symbol";
18
18
  // 타입의 대표 마커 파일명 (.sh marker_for_type L1220~1229 등가).
19
19
  // detect.js는 미지 타입에 package.json을 기본 반환하지만 .sh는 빈 문자열 — 등가를 위해 래핑.
20
20
  const KNOWN_MARKER_TYPES = new Set([
21
- "flutter", "react", "next", "node", "react-native", "react-native-expo", "python", "spring",
21
+ "flutter", "react", "next", "node", "react-native", "react-native-expo", "python", "spring", "go",
22
22
  ]);
23
23
  export function markerForType(type) {
24
24
  return KNOWN_MARKER_TYPES.has(type) ? baseMarkerForType(type) : "";
@@ -79,6 +79,7 @@ export function findTypePathCandidates(root, type) {
79
79
  "react-native-expo": ["app.json"],
80
80
  python: ["pyproject.toml", "setup.py", "requirements.txt"],
81
81
  spring: ["build.gradle", "build.gradle.kts", "pom.xml"],
82
+ go: ["go.mod"],
82
83
  };
83
84
  const names = namesByType[type];
84
85
  if (!names) return [];
package/src/ui/prompts.js CHANGED
@@ -48,7 +48,7 @@ export async function editMenu({ showOptional = false } = {}) {
48
48
  return engine.select({ message: "어떤 항목을 수정할까요?", options });
49
49
  }
50
50
 
51
- const ALL_TYPES = ["spring", "flutter", "next", "react", "react-native", "react-native-expo", "node", "python", "basic"];
51
+ const ALL_TYPES = ["spring", "flutter", "next", "react", "react-native", "react-native-expo", "node", "python", "basic", "go"];
52
52
 
53
53
  // 타입 멀티선택.
54
54
  export async function selectTypes(current = []) {
package/src/ui/summary.js CHANGED
@@ -156,10 +156,11 @@ export function printSummary(ctx) {
156
156
  err("");
157
157
  }
158
158
 
159
- err(` ${num()} 릴리스 automerge용 PAT (선택 — 없으면 GITHUB_TOKEN 사용)`);
159
+ err(` ${num()} 릴리스 automerge용 PAT (선택 — 없으면 GITHUB_TOKEN 폴백으로 자동 진행)`);
160
160
  err(" → Repository Settings > Secrets > Actions");
161
161
  err(" → Secret Name: WORKFLOW_PAT (Scopes: repo, workflow)");
162
- err(" → GITHUB_TOKEN 머지는 후속 워크플로우를 트리거하지 않습니다");
162
+ err(" → 등록 개인 계정이 아닌 조직 bot/machine 계정으로 발급하세요");
163
+ err(" → 없어도 자동 복구되며, 있으면 병합~Release 반영이 조금 더 빠릅니다");
163
164
  err("");
164
165
  err(` ${num()} GitHub Actions 권한 확인`);
165
166
  err(" → Settings > Actions > Workflow permissions: Read and write");