project-auto-wizard 0.13.3 → 0.14.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.
Files changed (112) hide show
  1. package/README.md +1 -1
  2. package/bin/project-auto-wizard.js +18 -4
  3. package/package.json +2 -2
  4. package/payload/config/breaking-changes.json +28 -14
  5. package/payload/config/wizard-prompts.yml +102 -46
  6. package/payload/flutter-app/android/fastlane/Fastfile.playstore +44 -32
  7. package/payload/flutter-app/ios/ExportOptions.plist +8 -8
  8. package/payload/flutter-app/ios/fastlane/Fastfile +57 -43
  9. package/payload/scripts/changelog_manager.py +296 -257
  10. package/payload/scripts/issue_helper.py +54 -50
  11. package/payload/scripts/messages.py +2279 -0
  12. package/payload/scripts/truncate_release_notes.py +7 -6
  13. package/payload/scripts/version_manager.py +64 -61
  14. package/payload/version.yml.template +2 -1
  15. package/payload/workflows/common/PROJECT-COMMON-AI-PR-SUMMARY.yaml +17 -12
  16. package/payload/workflows/common/PROJECT-COMMON-AUTO-CHANGELOG-CONTROL.yaml +59 -34
  17. package/payload/workflows/common/PROJECT-COMMON-ISSUE-HELPER.yaml +17 -15
  18. package/payload/workflows/common/PROJECT-COMMON-README-VERSION-UPDATE.yaml +17 -10
  19. package/payload/workflows/common/PROJECT-COMMON-RELEASE-PUBLISH.yaml +54 -44
  20. package/payload/workflows/common/PROJECT-COMMON-VERSION-CONTROL.yaml +24 -17
  21. package/payload/workflows/flutter/PROJECT-FLUTTER-ANDROID-FIREBASE-CICD.yaml +194 -184
  22. package/payload/workflows/flutter/PROJECT-FLUTTER-ANDROID-PLAYSTORE-CICD.yaml +226 -213
  23. package/payload/workflows/flutter/PROJECT-FLUTTER-ANDROID-SELFHOSTED-CICD.yaml +46 -42
  24. package/payload/workflows/flutter/PROJECT-FLUTTER-ANDROID-TEST-APK.yaml +334 -241
  25. package/payload/workflows/flutter/PROJECT-FLUTTER-APP-BUILD-TRIGGER.yaml +152 -128
  26. package/payload/workflows/flutter/PROJECT-FLUTTER-CI.yaml +246 -188
  27. package/payload/workflows/flutter/PROJECT-FLUTTER-IOS-TEST-TESTFLIGHT.yaml +353 -238
  28. package/payload/workflows/flutter/PROJECT-FLUTTER-IOS-TESTFLIGHT.yaml +96 -85
  29. package/payload/workflows/go/PROJECT-GO-CI.yaml +47 -36
  30. package/payload/workflows/go/PROJECT-GO-PR-PREVIEW.yaml +945 -508
  31. package/payload/workflows/go/PROJECT-GO-SIMPLE-CICD.yaml +155 -133
  32. package/payload/workflows/next/PROJECT-NEXT-CI.yaml +76 -60
  33. package/payload/workflows/next/PROJECT-NEXT-CICD.yaml +97 -77
  34. package/payload/workflows/python/PROJECT-PYTHON-CI.yaml +62 -47
  35. package/payload/workflows/python/PROJECT-PYTHON-PR-PREVIEW.yaml +942 -505
  36. package/payload/workflows/python/PROJECT-PYTHON-SIMPLE-CICD.yaml +148 -126
  37. package/payload/workflows/react/PROJECT-REACT-CI.yaml +86 -70
  38. package/payload/workflows/react/PROJECT-REACT-CICD.yaml +112 -92
  39. package/payload/workflows/spring/PROJECT-SPRING-CI.yml +112 -82
  40. package/payload/workflows/spring/server-deploy/PROJECT-SPRING-NONSTOP-NGINX-CICD.yaml +180 -150
  41. package/payload/workflows/spring/server-deploy/PROJECT-SPRING-NONSTOP-TRAEFIK-CICD.yaml +161 -131
  42. package/payload/workflows/spring/server-deploy/PROJECT-SPRING-PR-PREVIEW.yaml +970 -549
  43. package/payload/workflows/spring/server-deploy/PROJECT-SPRING-SIMPLE-CICD.yaml +193 -162
  44. package/src/cli/args.js +98 -52
  45. package/src/cli/help.js +8 -41
  46. package/src/commands/doctor.js +85 -89
  47. package/src/commands/dry-run.js +63 -43
  48. package/src/commands/full.js +79 -77
  49. package/src/commands/install-settings.js +16 -15
  50. package/src/commands/interactive-flutter.js +20 -19
  51. package/src/commands/interactive.js +135 -132
  52. package/src/commands/purge.js +47 -42
  53. package/src/commands/status.js +36 -36
  54. package/src/commands/uninstall.js +43 -41
  55. package/src/context.js +12 -11
  56. package/src/core/assets.js +12 -11
  57. package/src/core/baseline.js +27 -27
  58. package/src/core/branches.js +29 -27
  59. package/src/core/branding.js +7 -6
  60. package/src/core/breaking-check.js +22 -21
  61. package/src/core/breaking.js +15 -8
  62. package/src/core/copy/app-files.js +5 -4
  63. package/src/core/copy/flutter-app.js +8 -8
  64. package/src/core/copy/gitignore.js +58 -50
  65. package/src/core/copy/readme.js +45 -35
  66. package/src/core/copy/simple.js +16 -15
  67. package/src/core/copy/workflows.js +131 -124
  68. package/src/core/deploy-style.js +51 -48
  69. package/src/core/detect-fs.js +57 -57
  70. package/src/core/detect.js +67 -65
  71. package/src/core/errors.js +2 -2
  72. package/src/core/flutter-doctor.js +17 -16
  73. package/src/core/flutter-hooks.js +33 -32
  74. package/src/core/flutter-options.js +34 -33
  75. package/src/core/fsutil.js +12 -12
  76. package/src/core/installed-stores.js +4 -4
  77. package/src/core/logger.js +42 -40
  78. package/src/core/paths-resolve.js +88 -86
  79. package/src/core/paths.js +10 -10
  80. package/src/core/release-options.js +8 -8
  81. package/src/core/removal-exec.js +14 -13
  82. package/src/core/removal-plan.js +37 -37
  83. package/src/core/types.js +47 -47
  84. package/src/core/verify.js +44 -38
  85. package/src/core/version-yml.js +76 -68
  86. package/src/core/wizard-env.js +46 -46
  87. package/src/core/wizard-labels.js +63 -41
  88. package/src/i18n/catalog/en/cli.js +50 -0
  89. package/src/i18n/catalog/en/commands.js +187 -0
  90. package/src/i18n/catalog/en/copy.js +19 -0
  91. package/src/i18n/catalog/en/core.js +74 -0
  92. package/src/i18n/catalog/en/core2.js +15 -0
  93. package/src/i18n/catalog/en/ui.js +221 -0
  94. package/src/i18n/catalog/en.js +12 -0
  95. package/src/i18n/catalog/index.js +5 -0
  96. package/src/i18n/catalog/ko/cli.js +50 -0
  97. package/src/i18n/catalog/ko/commands.js +187 -0
  98. package/src/i18n/catalog/ko/copy.js +19 -0
  99. package/src/i18n/catalog/ko/core.js +74 -0
  100. package/src/i18n/catalog/ko/core2.js +15 -0
  101. package/src/i18n/catalog/ko/ui.js +221 -0
  102. package/src/i18n/catalog/ko.js +10 -0
  103. package/src/i18n/index.js +44 -0
  104. package/src/i18n/languages.js +9 -0
  105. package/src/index.js +128 -94
  106. package/src/ui/ansi.js +7 -7
  107. package/src/ui/banner.js +7 -6
  108. package/src/ui/env-plan.js +82 -81
  109. package/src/ui/prompts.js +97 -122
  110. package/src/ui/readline-engine.js +59 -58
  111. package/src/ui/status-cards.js +37 -31
  112. package/src/ui/summary.js +89 -88
@@ -1,62 +1,62 @@
1
1
  # project-auto-wizard:managed-workflow
2
- # Spring Boot 무중단 배포 (Traefik Blue-Green, SSH + Docker)
2
+ # Spring Boot zero-downtime deploy (Traefik Blue-Green, SSH + Docker)
3
3
  #
4
- # deploy 브랜치 푸시 시 Docker 이미지를 빌드하고
5
- # Traefik 라벨 기반 Blue-Green 토글로 서버(SSH 접속 가능한 호스트)에 무중단 배포합니다.
4
+ # On a push to the deploy branch, builds a Docker image and
5
+ # deploys it with no downtime to a server (any SSH-reachable host) using a Traefik label based Blue-Green toggle.
6
6
  #
7
- # 본 워크플로우는 무중단 배포 옵션입니다.
8
- # 기본 배포는 PROJECT-SPRING-SIMPLE-CICD.yaml 입니다.
9
- # 전환하려면 아래 push.branches 주석 해제 + SIMPLE-CICD 의 push 트리거 주석 처리.
7
+ # This workflow is the zero-downtime deploy option.
8
+ # The default deploy is PROJECT-SPRING-SIMPLE-CICD.yaml.
9
+ # To switch, uncomment push.branches below and comment out the push trigger in SIMPLE-CICD.
10
10
  #
11
- # 필수 GitHub Secrets
12
- # APPLICATION_PROD_YML (선택): application-prod.yml 내용
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 등)
11
+ # Required GitHub Secrets
12
+ # APPLICATION_PROD_YML (optional): contents of application-prod.yml
13
+ # DOCKERHUB_USERNAME: DockerHub username
14
+ # DOCKERHUB_TOKEN: DockerHub access token
15
+ # SERVER_HOST: deployment server address
16
+ # SERVER_USER: SSH username
17
+ # SERVER_PASSWORD: SSH password (when SSH_AUTH_METHOD=password)
18
+ # SSH_KEY: contents of the SSH private key .pem (when SSH_AUTH_METHOD=key, e.g. AWS EC2)
19
19
  #
20
- # 환경변수 설정 (env 섹션에서 설정)
20
+ # Environment variables (set in the env section)
21
21
  #
22
- # 포트 및 컨테이너:
23
- # PROJECT_NAME: 프로젝트명 (컨테이너/이미지/Traefik router 이름)
24
- # SERVICE_DOMAIN: 외부 진입 도메인
25
- # CONTAINER_INTERNAL_PORT: Spring Boot 내부 포트 (기본: 8080)
22
+ # Ports and containers:
23
+ # PROJECT_NAME: project name (container/image/Traefik router name)
24
+ # SERVICE_DOMAIN: external entry domain
25
+ # CONTAINER_INTERNAL_PORT: Spring Boot internal port (default: 8080)
26
26
  #
27
27
  # Traefik:
28
- # TRAEFIK_NETWORK: Traefik docker 네트워크 (기본: traefik-network)
29
- # TRAEFIK_ENTRYPOINT: Traefik entrypoint 이름 (기본: web)
30
- # TRAEFIK_INTERNAL_PORT: NAS 호스트의 Traefik 진입 포트 (기본: 8079)
31
- # EXTRA_NETWORKS: 추가 docker 네트워크 (공백 구분, 예: selenium-chrome-network)
28
+ # TRAEFIK_NETWORK: Traefik docker network (default: traefik-network)
29
+ # TRAEFIK_ENTRYPOINT: Traefik entrypoint name (default: web)
30
+ # TRAEFIK_INTERNAL_PORT: Traefik entry port on the NAS host (default: 8079)
31
+ # EXTRA_NETWORKS: extra docker networks (space separated, e.g. selenium-chrome-network)
32
32
  #
33
- # 볼륨/스토리지:
34
- # ENABLE_VOLUME_MOUNT: 볼륨 마운트 여부 (true/false)
35
- # VOLUME_HOST_PATH: 호스트 볼륨 경로
36
- # VOLUME_CONTAINER_PATH: 컨테이너 내부 마운트 경로
33
+ # Volume/storage:
34
+ # ENABLE_VOLUME_MOUNT: whether to mount a volume (true/false)
35
+ # VOLUME_HOST_PATH: host volume path
36
+ # VOLUME_CONTAINER_PATH: mount path inside the container
37
37
  #
38
- # SSH 연결:
39
- # SSH_PORT: SSH 포트 (기본: 2022)
40
- # SSH_COMMAND_TIMEOUT: 명령 타임아웃 (기본: 600s)
41
- # SSH_CONNECTION_TIMEOUT: 연결 타임아웃 (기본: 60s)
38
+ # SSH connection:
39
+ # SSH_PORT: SSH port (default: 2022)
40
+ # SSH_COMMAND_TIMEOUT: command timeout (default: 600s)
41
+ # SSH_CONNECTION_TIMEOUT: connection timeout (default: 60s)
42
42
  #
43
- # 헬스체크:
44
- # HEALTHCHECK_PATH: Traefik 통한 GET 호출 path (기본: /)
45
- # HEALTHCHECK_ACCEPT_CODES: 통과 인정 HTTP 코드 (| 구분, 기본: 200|301|302|308)
46
- # HEALTHCHECK_MAX_RETRIES: 최대 시도 횟수 (기본: 36)
47
- # HEALTHCHECK_RETRY_INTERVAL: 재시도 간격 초 (기본: 5)
48
- # IN_FLIGHT_WAIT: old 컨테이너 제거 전 대기 초 (기본: 10)
43
+ # Health check:
44
+ # HEALTHCHECK_PATH: path for the GET call through Traefik (default: /)
45
+ # HEALTHCHECK_ACCEPT_CODES: HTTP codes accepted as healthy (| separated, default: 200|301|302|308)
46
+ # HEALTHCHECK_MAX_RETRIES: max attempts (default: 36)
47
+ # HEALTHCHECK_RETRY_INTERVAL: retry interval in seconds (default: 5)
48
+ # IN_FLIGHT_WAIT: seconds to wait before removing the old container (default: 10)
49
49
  #
50
- # 사전 조건:
51
- # - 배포 서버에 Traefik 컨테이너가 traefik-network 에 띄워져 있어야 함
52
- # - 역방향 프록시: ${SERVICE_DOMAIN}:443 → localhost:${TRAEFIK_INTERNAL_PORT}
53
- # (예: Synology DSM 역방향 프록시, Nginx, Caddy 등)
50
+ # Prerequisites:
51
+ # - a Traefik container must be running on the deploy server in traefik-network
52
+ # - reverse proxy: ${SERVICE_DOMAIN}:443 -> localhost:${TRAEFIK_INTERNAL_PORT}
53
+ # (e.g. Synology DSM reverse proxy, Nginx, Caddy)
54
54
 
55
55
  name: PROJECT-SPRING-NONSTOP-TRAEFIK-CICD
56
56
 
57
- # 트리거 설정
58
- # deploy 브랜치 push 자동 트리거는 주석 처리되어 있습니다.
59
- # 본 워크플로우로 전환하려면 아래 push.branches 주석을 해제하세요.
57
+ # Trigger settings
58
+ # The automatic trigger on a push to the deploy branch is commented out.
59
+ # To switch to this workflow, uncomment push.branches below.
60
60
  on:
61
61
  # push:
62
62
  # branches:
@@ -64,48 +64,48 @@ on:
64
64
  workflow_dispatch:
65
65
 
66
66
  env:
67
- # 프로젝트 설정 - 실제 프로젝트명으로 변경하세요
67
+ # Project settings - change to your actual project name
68
68
  PROJECT_NAME: "__PROJECT_NAME__" # @wizard ask:@repo
69
- # 모노레포(--paths)에서 이 프로젝트가 있는 하위 경로. 빌드·Docker context의 기준 ('.'이면 레포 루트)
69
+ # Subdirectory of this project in a monorepo (--paths). Base for the build and Docker context ('.' = repo root)
70
70
  PROJECT_PATH: "." # @wizard auto:project-path
71
- # 모노레포(PROJECT_PATH가 '.'이 아님)에서는 이미지·컨테이너·도메인 이름에 '-spring'를 붙여
72
- # 같은 레포의 다른 타입 서비스와 이름이 겹치지 않게 한다 (예: my-app-spring)
71
+ # In a monorepo (PROJECT_PATH is not '.') '-spring' is appended to image, container and domain names
72
+ # so they do not collide with services of other types in the same repo (e.g. my-app-spring)
73
73
  SERVICE_DOMAIN: "__SERVICE_DOMAIN__" # @wizard ask:example.com
74
74
 
75
- # Spring Boot 설정
75
+ # Spring Boot settings
76
76
  SPRING_PROFILE: "prod"
77
77
  JAVA_VERSION: "__JAVA_VERSION__" # @wizard ask:@jdk
78
78
  GRADLE_OPTS: "-Dspring.profiles.active=prod"
79
79
 
80
- # 경로 설정
80
+ # Path settings
81
81
  APPLICATION_YML_DIR: "__APPLICATION_YML_DIR__" # @wizard auto:spring-app-yml-dir
82
- DOCKERFILE_PATH: "./Dockerfile" # PROJECT_PATH 기준 상대 경로
82
+ DOCKERFILE_PATH: "./Dockerfile" # relative to PROJECT_PATH
83
83
 
84
- # 포트 및 컨테이너 설정
84
+ # Port and container settings
85
85
  CONTAINER_INTERNAL_PORT: "8080"
86
86
 
87
- # Traefik 설정 (서버의 Traefik 환경 구축 후 수정 금지)
87
+ # Traefik settings (do not change after the server's Traefik environment is set up)
88
88
  TRAEFIK_NETWORK: "traefik-network"
89
89
  TRAEFIK_ENTRYPOINT: "web"
90
90
  TRAEFIK_INTERNAL_PORT: "8079"
91
91
  EXTRA_NETWORKS: ""
92
92
 
93
- # 볼륨 마운트 설정
93
+ # Volume mount settings
94
94
  ENABLE_VOLUME_MOUNT: "false" # @wizard ask:false
95
95
  VOLUME_HOST_PATH: "/volume1/projects/__PROJECT_NAME__" # @wizard ask:/volume1/projects/__PROJECT_NAME__
96
96
  VOLUME_CONTAINER_PATH: "/mnt/__PROJECT_NAME__" # @wizard ask:/mnt/__PROJECT_NAME__
97
97
 
98
- # SSH 인증 방식: "password"(Synology·일반 서버) | "key"(AWS EC2·.pem)
99
- # password → SERVER_PASSWORD secret 사용
100
- # key → SSH_KEY secret 사용 (.pem 파일 전체 내용을 secret 값으로)
98
+ # SSH auth method: "password" (Synology / generic servers) | "key" (AWS EC2 / .pem)
99
+ # password -> uses the SERVER_PASSWORD secret
100
+ # key -> uses the SSH_KEY secret (the full .pem contents as the secret value)
101
101
  SSH_AUTH_METHOD: "password" # @wizard ask:password
102
102
 
103
- # SSH 연결 설정
103
+ # SSH connection settings
104
104
  SSH_PORT: "__SSH_PORT__" # @wizard ask:2022
105
105
  SSH_COMMAND_TIMEOUT: "600s"
106
106
  SSH_CONNECTION_TIMEOUT: "60s"
107
107
 
108
- # 헬스체크 설정
108
+ # Health check settings
109
109
  HEALTHCHECK_PATH: "/"
110
110
  HEALTHCHECK_ACCEPT_CODES: "200|301|302|308"
111
111
  HEALTHCHECK_MAX_RETRIES: "36"
@@ -113,17 +113,17 @@ env:
113
113
  IN_FLIGHT_WAIT: "10"
114
114
 
115
115
  jobs:
116
- # 빌드 작업
116
+ # Build job
117
117
  build:
118
- name: Spring Boot 애플리케이션 빌드
118
+ name: Build Spring Boot application
119
119
  runs-on: ubuntu-latest
120
120
 
121
121
  steps:
122
- - name: 코드 체크아웃
122
+ - name: Checkout code
123
123
  uses: actions/checkout@v7
124
124
 
125
- # 필수 Secret·Dockerfile이 없으면 docker 로그인 같은 서드파티 오류 대신 빠진 항목을 알려 주고 멈춘다
126
- - name: 배포 사전 점검 (Secret·Dockerfile)
125
+ # If a required Secret or the Dockerfile is missing, name it and stop instead of failing later in a third-party step such as docker login
126
+ - name: Deploy pre-check (Secrets and Dockerfile)
127
127
  working-directory: ${{ env.PROJECT_PATH }}
128
128
  env:
129
129
  DOCKERHUB_USERNAME: ${{ secrets.DOCKERHUB_USERNAME }}
@@ -134,11 +134,12 @@ jobs:
134
134
  SSH_KEY: ${{ secrets.SSH_KEY }}
135
135
  DOCKERFILE: ${{ env.DOCKERFILE_PATH }}
136
136
  run: |
137
+ m() { python3 "$GITHUB_WORKSPACE/.github/scripts/messages.py" get "$@"; }
137
138
  MISSING=""
138
139
  for NAME in DOCKERHUB_USERNAME DOCKERHUB_TOKEN SERVER_HOST SERVER_USER; do
139
140
  [ -n "${!NAME}" ] || MISSING="$MISSING $NAME"
140
141
  done
141
- # SSH_AUTH_METHOD=key면 SSH_KEY, 아니면 SERVER_PASSWORD가 필요하다
142
+ # SSH_AUTH_METHOD=key needs SSH_KEY, anything else needs SERVER_PASSWORD
142
143
  if [ "${SSH_AUTH_METHOD:-password}" = "key" ]; then
143
144
  [ -n "$SSH_KEY" ] || MISSING="$MISSING SSH_KEY"
144
145
  else
@@ -146,45 +147,46 @@ jobs:
146
147
  fi
147
148
  FAILED=false
148
149
  if [ -n "$MISSING" ]; then
149
- echo "::error title=필수 Secret 누락::등록되지 않은 GitHub Secret:$MISSING — Settings → Secrets and variables → Actions에서 등록하세요"
150
+ echo "::error title=$(m cicd.precheck_secret_title)::$(m cicd.precheck_secret_body missing="$MISSING")"
150
151
  FAILED=true
151
152
  fi
152
153
  if [ ! -f "$DOCKERFILE" ]; then
153
- echo "::error title=Dockerfile 없음::${PROJECT_PATH}/${DOCKERFILE#./} 파일이 없습니다. 이 워크플로우는 Docker 이미지로 배포하므로 Dockerfile이 필요합니다 (서버 배포를 하지 않는다면 이 워크플로우 파일을 삭제하세요)"
154
+ echo "::error title=$(m cicd.precheck_dockerfile_title)::$(m cicd.precheck_dockerfile_body path="${PROJECT_PATH}/${DOCKERFILE#./}")"
154
155
  FAILED=true
155
156
  fi
156
157
  if [ "$FAILED" = "true" ]; then
157
158
  exit 1
158
159
  fi
159
- echo "✅ 사전 점검 통과: 필수 Secret과 Dockerfile이 모두 있습니다"
160
+ m cicd.precheck_ok
160
161
 
161
- - name: Java 설정
162
+ - name: Set up Java
162
163
  uses: actions/setup-java@v5
163
164
  with:
164
165
  java-version: ${{ env.JAVA_VERSION }}
165
166
  distribution: 'temurin'
166
167
  cache: 'gradle'
167
168
 
168
- - name: Gradle Wrapper 실행권한 부여
169
+ - name: Make Gradle wrapper executable
169
170
  working-directory: ${{ env.PROJECT_PATH }}
170
171
  run: chmod +x gradlew
171
172
 
172
- - name: application-prod.yml 생성
173
+ - name: Generate application-prod.yml
173
174
  run: |
175
+ m() { python3 "$GITHUB_WORKSPACE/.github/scripts/messages.py" get "$@"; }
174
176
  mkdir -p ${{ env.APPLICATION_YML_DIR }}
175
177
  cat << 'EOF' > ./${{ env.APPLICATION_YML_DIR }}/application-prod.yml
176
178
  ${{ secrets.APPLICATION_PROD_YML }}
177
179
  EOF
178
- echo "✅ application-prod.yml 파일 생성 완료 (${{ env.APPLICATION_YML_DIR }})"
180
+ m wf_spring_deploy.prod_yml_created dir="${{ env.APPLICATION_YML_DIR }}"
179
181
 
180
182
  - name: Build with Gradle
181
183
  working-directory: ${{ env.PROJECT_PATH }}
182
184
  run: ./gradlew clean build -x test ${{ env.GRADLE_OPTS }}
183
185
 
184
- - name: Docker 빌드환경 설정
186
+ - name: Set up Docker Buildx
185
187
  uses: docker/setup-buildx-action@v3
186
188
 
187
- - name: DockerHub 로그인
189
+ - name: Log in to DockerHub
188
190
  uses: docker/login-action@v3
189
191
  with:
190
192
  username: ${{ secrets.DOCKERHUB_USERNAME }}
@@ -198,7 +200,7 @@ jobs:
198
200
  restore-keys: |
199
201
  ${{ runner.os }}-buildx-
200
202
 
201
- - name: Docker 이미지 빌드 및 푸시
203
+ - name: Build and push Docker image
202
204
  uses: docker/build-push-action@v5
203
205
  with:
204
206
  context: ${{ env.PROJECT_PATH }}
@@ -213,16 +215,34 @@ jobs:
213
215
  rm -rf /tmp/.buildx-cache
214
216
  mv /tmp/.buildx-cache-new /tmp/.buildx-cache
215
217
 
216
- # 배포 작업 (Blue-Green 무중단)
218
+ # Deploy job (Blue-Green, zero downtime)
217
219
  deploy:
218
- name: Blue-Green 무중단 배포
220
+ name: Blue-Green zero-downtime deploy
219
221
  needs: build
220
222
  runs-on: ubuntu-latest
221
223
 
222
224
  steps:
225
+ # The deploy script runs on the server, which has no messages.py:
226
+ # render the message catalog here and pass it along as a base64 env var
227
+ - name: Checkout message catalog
228
+ uses: actions/checkout@v7
229
+ with:
230
+ sparse-checkout: |
231
+ .github/scripts/messages.py
232
+ version.yml
233
+ sparse-checkout-cone-mode: false
234
+
235
+ - name: Load messages
236
+ run: |
237
+ MSGS="$(python3 "$GITHUB_WORKSPACE/.github/scripts/messages.py" dump wf_spring_deploy. \
238
+ | python3 -c 'import json, shlex, sys; d = json.load(sys.stdin); print("\n".join("M_%s=%s" % (k.replace(".", "_"), shlex.quote(v)) for k, v in d.items()))' \
239
+ | base64 | tr -d '\n')"
240
+ echo "PAW_MSG_B64=$MSGS" >> "$GITHUB_ENV"
241
+
223
242
  - name: Deploy
224
243
  uses: appleboy/ssh-action@v1.0.3
225
244
  env:
245
+ PAW_MSG_B64: ${{ env.PAW_MSG_B64 }}
226
246
  PROJECT_NAME: ${{ env.PROJECT_PATH != '.' && format('{0}-spring', env.PROJECT_NAME) || env.PROJECT_NAME }}
227
247
  SERVICE_DOMAIN: ${{ env.SERVICE_DOMAIN }}
228
248
  CONTAINER_INTERNAL_PORT: ${{ env.CONTAINER_INTERNAL_PORT }}
@@ -251,38 +271,48 @@ jobs:
251
271
  port: ${{ env.SSH_PORT }}
252
272
  command_timeout: ${{ env.SSH_COMMAND_TIMEOUT }}
253
273
  timeout: ${{ env.SSH_CONNECTION_TIMEOUT }}
254
- envs: PROJECT_NAME,SERVICE_DOMAIN,CONTAINER_INTERNAL_PORT,SPRING_PROFILE,ENABLE_VOLUME_MOUNT,VOLUME_HOST_PATH,VOLUME_CONTAINER_PATH,EXTRA_NETWORKS,TRAEFIK_NETWORK,TRAEFIK_ENTRYPOINT,TRAEFIK_INTERNAL_PORT,HEALTHCHECK_PATH,HEALTHCHECK_ACCEPT_CODES,HEALTHCHECK_MAX_RETRIES,HEALTHCHECK_RETRY_INTERVAL,IN_FLIGHT_WAIT,BRANCH_REF,DOCKERHUB_USERNAME,SERVER_PASSWORD,SSH_AUTH_METHOD
274
+ envs: PROJECT_NAME,SERVICE_DOMAIN,CONTAINER_INTERNAL_PORT,SPRING_PROFILE,ENABLE_VOLUME_MOUNT,VOLUME_HOST_PATH,VOLUME_CONTAINER_PATH,EXTRA_NETWORKS,TRAEFIK_NETWORK,TRAEFIK_ENTRYPOINT,TRAEFIK_INTERNAL_PORT,HEALTHCHECK_PATH,HEALTHCHECK_ACCEPT_CODES,HEALTHCHECK_MAX_RETRIES,HEALTHCHECK_RETRY_INTERVAL,IN_FLIGHT_WAIT,BRANCH_REF,DOCKERHUB_USERNAME,SERVER_PASSWORD,SSH_AUTH_METHOD,PAW_MSG_B64
255
275
  script: |
256
276
  set -e
257
277
  export PATH=$PATH:/usr/local/bin
258
278
  export PW="${SERVER_PASSWORD}"
259
279
 
260
- # SSH 인증 방식에 따른 sudo 추상화
280
+ # Messages arrive as base64 shell assignments (the server has no messages.py)
281
+ eval "$(printf '%s' "$PAW_MSG_B64" | base64 -d)"
282
+ m() {
283
+ local _v="M_wf_spring_deploy_$1" _kv
284
+ local _s="${!_v}"
285
+ shift
286
+ for _kv in "$@"; do _s=${_s//\{${_kv%%=*}\}/${_kv#*=}}; done
287
+ printf '%s\n' "$_s"
288
+ }
289
+
290
+ # Abstract sudo by SSH auth method
261
291
  SSH_AUTH_METHOD="${SSH_AUTH_METHOD:-password}"
262
292
  if [ "${SSH_AUTH_METHOD}" = "key" ]; then
263
293
  SUDO() { sudo "$@"; }
264
294
  else
265
295
  SUDO() { echo "$PW" | sudo -S "$@"; }
266
296
  fi
267
- echo "🔐 SSH 인증 방식: ${SSH_AUTH_METHOD}"
297
+ m ssh_auth_method method="${SSH_AUTH_METHOD}"
268
298
 
269
299
  IMAGE="${DOCKERHUB_USERNAME}/${PROJECT_NAME}:${BRANCH_REF}"
270
300
  ROUTER_NAME="${PROJECT_NAME}"
271
301
 
272
302
  echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
273
- echo "🚀 Blue-Green 무중단 배포 시작"
303
+ m traefik_deploy_start
274
304
  echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
275
- echo "📦 프로젝트: ${PROJECT_NAME}"
276
- echo "🌐 도메인: ${SERVICE_DOMAIN}"
277
- echo "🖼️ 이미지: ${IMAGE}"
278
- echo "🔧 Traefik 진입: localhost:${TRAEFIK_INTERNAL_PORT} (entrypoint=${TRAEFIK_ENTRYPOINT})"
305
+ m banner_project name="${PROJECT_NAME}"
306
+ m banner_domain domain="${SERVICE_DOMAIN}"
307
+ m banner_image image="${IMAGE}"
308
+ m traefik_entry port="${TRAEFIK_INTERNAL_PORT}" entrypoint="${TRAEFIK_ENTRYPOINT}"
279
309
  echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
280
310
 
281
- # 1. 최신 이미지 Pull
282
- echo "📥 이미지 Pull: ${IMAGE}"
311
+ # 1. Pull the latest image
312
+ m image_pull image="${IMAGE}"
283
313
  SUDO docker pull "${IMAGE}"
284
314
 
285
- # 2. 현재 active 컨테이너 색 판별 (Traefik 라벨 부착 여부 기준)
315
+ # 2. Determine the color of the active container (by whether it carries the Traefik label)
286
316
  ACTIVE=$(SUDO docker ps \
287
317
  --filter "label=traefik.http.routers.${ROUTER_NAME}.rule" \
288
318
  --filter "name=${PROJECT_NAME}-" \
@@ -295,7 +325,7 @@ jobs:
295
325
  NEW_COLOR="blue"
296
326
  OLD_COLOR="green"
297
327
  else
298
- # 최초 배포 또는 active 없음 → blue 부터 시작
328
+ # First deploy or no active container -> start with blue
299
329
  NEW_COLOR="blue"
300
330
  OLD_COLOR="green"
301
331
  fi
@@ -305,25 +335,25 @@ jobs:
305
335
 
306
336
  echo "🎨 active=${ACTIVE:-none} → new=${NEW_NAME}, old=${OLD_NAME}"
307
337
 
308
- # 3. 동일 슬롯(idle 쪽) 잔존 컨테이너 정리
309
- echo "🗑️ idle 슬롯 정리: ${NEW_NAME}"
338
+ # 3. Clean up a leftover container in the same (idle) slot
339
+ m idle_slot_cleanup name="${NEW_NAME}"
310
340
  SUDO docker rm -f "${NEW_NAME}" 2>/dev/null || true
311
341
 
312
- # 4. 볼륨 마운트 옵션 구성
342
+ # 4. Build the volume mount option
313
343
  VOLUME_MOUNT_OPTION="-v /etc/localtime:/etc/localtime:ro"
314
344
  if [ "${ENABLE_VOLUME_MOUNT}" = "true" ]; then
315
345
  if [ ! -d "${VOLUME_HOST_PATH}" ]; then
316
- echo "📁 볼륨 마운트 디렉토리 생성: ${VOLUME_HOST_PATH}"
346
+ m volume_mkdir path="${VOLUME_HOST_PATH}"
317
347
  SUDO mkdir -p "${VOLUME_HOST_PATH}"
318
348
  fi
319
349
  VOLUME_MOUNT_OPTION="${VOLUME_MOUNT_OPTION} -v ${VOLUME_HOST_PATH}:${VOLUME_CONTAINER_PATH}"
320
- echo "📦 볼륨 마운트 활성화: ${VOLUME_HOST_PATH} → ${VOLUME_CONTAINER_PATH}"
350
+ m volume_enabled host="${VOLUME_HOST_PATH}" container="${VOLUME_CONTAINER_PATH}"
321
351
  else
322
- echo "ℹ️ 볼륨 마운트 비활성화됨"
352
+ m volume_disabled
323
353
  fi
324
354
 
325
- # 5. 새 컨테이너 기동 (Traefik 라벨 포함)
326
- echo "🐳 새 컨테이너 기동: ${NEW_NAME}"
355
+ # 5. Start the new container (with Traefik labels)
356
+ m new_container_start name="${NEW_NAME}"
327
357
  SUDO docker run -d \
328
358
  --name "${NEW_NAME}" \
329
359
  --network "${TRAEFIK_NETWORK}" \
@@ -336,21 +366,21 @@ jobs:
336
366
  ${VOLUME_MOUNT_OPTION} \
337
367
  "${IMAGE}"
338
368
 
339
- # 6. 추가 네트워크 연결 (EXTRA_NETWORKS, 공백 구분)
369
+ # 6. Connect extra networks (EXTRA_NETWORKS, space separated)
340
370
  if [ -n "${EXTRA_NETWORKS}" ]; then
341
371
  for net in ${EXTRA_NETWORKS}; do
342
- echo "🔗 추가 네트워크 연결: ${net}"
372
+ m traefik_extra_network net="${net}"
343
373
  SUDO docker network connect "${net}" "${NEW_NAME}" 2>/dev/null || true
344
374
  done
345
375
  fi
346
376
 
347
- # 7. Traefik 라벨 디버그 출력 (escape 검증용)
348
- echo "🔍 Traefik 라벨 확인:"
377
+ # 7. Print the Traefik labels for debugging (to verify escaping)
378
+ m traefik_labels
349
379
  SUDO docker inspect "${NEW_NAME}" --format '{{range $k, $v := .Config.Labels}} {{$k}} = {{$v}}{{"\n"}}{{end}}' | grep traefik || true
350
380
 
351
- # 8. 헬스체크 — Traefik 통한 라우팅 확인
381
+ # 8. Health check - verify routing through Traefik
352
382
  MAX_WAIT=$((HEALTHCHECK_MAX_RETRIES * HEALTHCHECK_RETRY_INTERVAL))
353
- echo "⏳ 헬스체크 시작 (최대 ${MAX_WAIT}초, ${HEALTHCHECK_MAX_RETRIES}회 × ${HEALTHCHECK_RETRY_INTERVAL}초)"
383
+ m traefik_hc_start max_wait="${MAX_WAIT}" retries="${HEALTHCHECK_MAX_RETRIES}" interval="${HEALTHCHECK_RETRY_INTERVAL}"
354
384
 
355
385
  RETRY_COUNT=0
356
386
  HEALTHCHECK_SUCCESS=false
@@ -362,12 +392,12 @@ jobs:
362
392
  STATUS=$(SUDO docker inspect --format='{{.State.Status}}' "${NEW_NAME}" 2>/dev/null || echo "not_found")
363
393
 
364
394
  if [ "$STATUS" = "exited" ]; then
365
- echo "❌ 컨테이너 비정상 종료 (exited). 로그 200줄:"
395
+ m traefik_exited
366
396
  echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
367
397
  SUDO docker logs --tail 200 "${NEW_NAME}"
368
398
  echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
369
- echo "ℹ️ 컨테이너 ${NEW_NAME} 유지 (자동 삭제 안 함) — 수동 검증/디버깅"
370
- echo "ℹ️ old=${OLD_NAME} 존재 시 그대로 유지 → 사실상 자동 롤백"
399
+ m traefik_kept name="${NEW_NAME}"
400
+ m traefik_rollback_note name="${OLD_NAME}"
371
401
  exit 1
372
402
  fi
373
403
 
@@ -377,64 +407,64 @@ jobs:
377
407
  "http://localhost:${TRAEFIK_INTERNAL_PORT}${HEALTHCHECK_PATH}" 2>/dev/null || echo "000")
378
408
 
379
409
  if echo "$HTTP_CODE" | grep -Eq "^(${HEALTHCHECK_ACCEPT_CODES})$"; then
380
- echo "✅ 헬스체크 통과 (${RETRY_COUNT}/${HEALTHCHECK_MAX_RETRIES}) HTTP=${HTTP_CODE}"
410
+ m traefik_hc_pass n="${RETRY_COUNT}" max="${HEALTHCHECK_MAX_RETRIES}" code="${HTTP_CODE}"
381
411
  HEALTHCHECK_SUCCESS=true
382
412
  break
383
413
  fi
384
414
  fi
385
415
 
386
- echo "⏳ 대기 (${RETRY_COUNT}/${HEALTHCHECK_MAX_RETRIES}) status=${STATUS} http=${HTTP_CODE:-?}"
416
+ m traefik_waiting n="${RETRY_COUNT}" max="${HEALTHCHECK_MAX_RETRIES}" status="${STATUS}" http="${HTTP_CODE:-?}"
387
417
  done
388
418
 
389
419
  if [ "$HEALTHCHECK_SUCCESS" = "false" ]; then
390
- echo "❌ 헬스체크 타임아웃 (${MAX_WAIT}초)"
420
+ m traefik_hc_timeout sec="${MAX_WAIT}"
391
421
  echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
392
- echo "🔍 ${NEW_NAME} 최근 200줄 로그:"
422
+ m traefik_logs_header name="${NEW_NAME}"
393
423
  echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
394
424
  SUDO docker logs --tail 200 "${NEW_NAME}"
395
425
  echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
396
- echo "ℹ️ 컨테이너 ${NEW_NAME} 유지 (자동 삭제 안 함) — 수동 검증/디버깅"
397
- echo "ℹ️ old=${OLD_NAME} 존재 시 그대로 유지 → 사실상 자동 롤백"
426
+ m traefik_kept name="${NEW_NAME}"
427
+ m traefik_rollback_note name="${OLD_NAME}"
398
428
  exit 1
399
429
  fi
400
430
 
401
- # 9. in-flight 처리 + old 컨테이너 자동 제거
431
+ # 9. Let in-flight requests finish, then remove the old container automatically
402
432
  if SUDO docker ps -a --format '{{.Names}}' | grep -Eq "^${OLD_NAME}$"; then
403
- echo "⏱️ in-flight 요청 처리 대기 (${IN_FLIGHT_WAIT}초)"
433
+ m inflight_wait sec="${IN_FLIGHT_WAIT}"
404
434
  sleep ${IN_FLIGHT_WAIT}
405
- echo "🗑️ old=${OLD_NAME} 제거"
435
+ m old_removed name="${OLD_NAME}"
406
436
  SUDO docker rm -f "${OLD_NAME}"
407
437
  else
408
- echo "ℹ️ old=${OLD_NAME} 없음 — 정리 생략"
438
+ m old_absent name="${OLD_NAME}"
409
439
  fi
410
440
 
411
- # 10. dangling 이미지 정리
412
- echo "🧹 dangling(<none>) 이미지 정리..."
441
+ # 10. Clean up dangling images
442
+ m prune_dangling
413
443
  SUDO docker image prune -af
414
444
 
415
445
  echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
416
- echo "✅ 무중단 배포 완료. active=${NEW_NAME}"
417
- echo "🌐 도메인: https://${SERVICE_DOMAIN}"
446
+ m traefik_done name="${NEW_NAME}"
447
+ m banner_domain domain="https://${SERVICE_DOMAIN}"
418
448
  echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
419
449
 
420
- # 사용 방법
450
+ # Usage
421
451
  #
422
- # 1) GitHub Secrets 등록:
452
+ # 1) Register the GitHub Secrets:
423
453
  # APPLICATION_PROD_YML, DOCKERHUB_USERNAME, DOCKERHUB_TOKEN,
424
454
  # SERVER_HOST, SERVER_USER, SERVER_PASSWORD
425
455
  #
426
- # 2) 본 워크플로우의 env 블록에서 프로젝트별 설정 수정:
456
+ # 2) Edit the project-specific settings in this workflow's env block:
427
457
  # - PROJECT_NAME, SERVICE_DOMAIN
428
458
  # - APPLICATION_YML_DIR, CONTAINER_INTERNAL_PORT
429
459
  # - ENABLE_VOLUME_MOUNT, VOLUME_HOST_PATH, VOLUME_CONTAINER_PATH
430
- # - EXTRA_NETWORKS (예: selenium-chrome-network)
460
+ # - EXTRA_NETWORKS (e.g. selenium-chrome-network)
431
461
  # - HEALTHCHECK_PATH, HEALTHCHECK_ACCEPT_CODES
432
462
  #
433
- # 3) 서버 사전 준비:
434
- # - Traefik 컨테이너가 traefik-network 에 띄워져 있어야 함
435
- # - 역방향 프록시: ${SERVICE_DOMAIN}:443 → localhost:${TRAEFIK_INTERNAL_PORT}
436
- # (예: Synology DSM 역방향 프록시, Nginx, Caddy 등)
463
+ # 3) Server preparation:
464
+ # - a Traefik container must be running in traefik-network
465
+ # - reverse proxy: ${SERVICE_DOMAIN}:443 -> localhost:${TRAEFIK_INTERNAL_PORT}
466
+ # (e.g. Synology DSM reverse proxy, Nginx, Caddy)
437
467
  #
438
- # 4) 본 워크플로우로 전환:
439
- # - 본 파일의 push.branches 주석 해제
440
- # - PROJECT-SPRING-SIMPLE-CICD.yaml 의 push 트리거 주석 처리
468
+ # 4) Switch to this workflow:
469
+ # - uncomment push.branches in this file
470
+ # - comment out the push trigger in PROJECT-SPRING-SIMPLE-CICD.yaml