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,27 +1,27 @@
1
1
  # project-auto-wizard:managed-workflow
2
- # Flutter 테스트 앱 빌드 트리거 워크플로우
2
+ # Flutter test app build trigger workflow
3
3
  #
4
- # 이 워크플로우는 PR 또는 이슈에 댓글로 빌드 명령어를 작성하면
5
- # Android와 iOS 테스트 앱 빌드를 자동으로 트리거합니다.
4
+ # When a build command is posted as a comment on a PR or issue, this workflow
5
+ # triggers the Android and iOS test app builds automatically.
6
6
  #
7
- # 주요 특징:
8
- # - PR/이슈 댓글 감지: "/wizard app build / apk build / ios build" 키워드 감지 (어순 무관: build app 도 동작)
9
- # - repository_dispatch 이벤트로 빌드 워크플로우 트리거
10
- # - PR/이슈 번호 + 빌드 횟수로 고유한 빌드 번호 생성 (예: 38700, 38701...)
11
- # - 브랜치명에서 이슈 번호 자동 추출 (YYYYMMDD_#이슈번호_내용 형식)
12
- # - 테스트 버전은 version.yml에서 동적으로 읽어 사용 (빌드 번호로 운영/테스트 구분)
7
+ # Highlights:
8
+ # - Detects PR/issue comments: "/wizard app build / apk build / ios build" keywords (word order does not matter: "build app" works too)
9
+ # - Triggers the build workflows through repository_dispatch events
10
+ # - Builds a unique build number from the PR/issue number + build count (e.g. 38700, 38701...)
11
+ # - Extracts the issue number from the branch name automatically (YYYYMMDD_#issue-number_title format)
12
+ # - The test version is read dynamically from version.yml (production and test builds are told apart by build number)
13
13
  #
14
- # 사용 방법:
15
- # - PR에 댓글 작성: "/wizard app build" (Android + iOS 모두 빌드)
16
- # - PR에 댓글 작성: "/wizard apk build" (Android만 빌드)
17
- # - PR에 댓글 작성: "/wizard ios build" (iOS만 빌드)
18
- # - 이슈에 댓글 작성: 위 명령어 동일 (Issue Helper 댓글 필요)
19
- # - 어순은 무관: "build app" / "build apk" / "build ios"로 적어도 동일하게 동작
20
- # - 특정 브랜치 지정: "/wizard app build <브랜치명>" (명령어 뒤에 브랜치명을 적으면 해당 브랜치를 빌드)
21
- # 예: "/wizard apk build 20260609_#349_feature" → 해당 브랜치로 Android 빌드
22
- # 브랜치명을 생략하면 PR head 또는 이슈의 Issue Helper 댓글 브랜치를 사용
14
+ # Usage:
15
+ # - Comment on a PR: "/wizard app build" (builds Android + iOS)
16
+ # - Comment on a PR: "/wizard apk build" (Android only)
17
+ # - Comment on a PR: "/wizard ios build" (iOS only)
18
+ # - Comment on an issue: same commands (an Issue Helper comment is required)
19
+ # - Word order does not matter: "build app" / "build apk" / "build ios" behave the same
20
+ # - To build a specific branch: "/wizard app build <branch-name>" (a branch name after the command builds that branch)
21
+ # e.g. "/wizard apk build 20260609_#12_feature" -> Android build of that branch
22
+ # If the branch name is omitted, the PR head or the branch in the issue's Issue Helper comment is used
23
23
  #
24
- # 트리거되는 워크플로우:
24
+ # Workflows triggered:
25
25
  # - PROJECT-FLUTTER-ANDROID-TEST-APK.yaml (event_type: build-android-app)
26
26
  # - PROJECT-FLUTTER-IOS-TEST-TESTFLIGHT.yaml (event_type: build-ios-app)
27
27
 
@@ -38,8 +38,8 @@ permissions:
38
38
 
39
39
  jobs:
40
40
  trigger-builds:
41
- name: 빌드 워크플로우 트리거
42
- # /wizard app build / /wizard apk build / /wizard ios build 키워드 감지 (어순 무관)
41
+ name: Trigger build workflows
42
+ # Detect the /wizard app build / /wizard apk build / /wizard ios build keywords (word order does not matter)
43
43
  if: |
44
44
  contains(github.event.comment.body, '/wizard') &&
45
45
  (
@@ -50,11 +50,25 @@ jobs:
50
50
  runs-on: ubuntu-latest
51
51
 
52
52
  steps:
53
- - name: 댓글에 👀 리액션 추가
53
+ # Messages come from the default branch (issue_comment runs there); only the message catalog and version.yml are needed
54
+ - name: Checkout messages
55
+ uses: actions/checkout@v7
56
+ with:
57
+ sparse-checkout: |
58
+ .github/scripts/messages.py
59
+ version.yml
60
+ sparse-checkout-cone-mode: false
61
+
62
+ - name: Load messages
63
+ run: echo "PAW_MSG=$(python3 "$GITHUB_WORKSPACE/.github/scripts/messages.py" dump app_trigger.)" >> "$GITHUB_ENV"
64
+
65
+ - name: Add 👀 reaction to the comment
54
66
  uses: actions/github-script@v9
55
67
  with:
56
68
  github-token: ${{ secrets.GITHUB_TOKEN }}
57
69
  script: |
70
+ const M = JSON.parse(process.env.PAW_MSG);
71
+ const msg = (k, p = {}) => M[k].replace(/\{(\w+)\}/g, (s, n) => (n in p ? p[n] : s));
58
72
  const commentId = context.payload.comment.id;
59
73
 
60
74
  await github.rest.reactions.createForIssueComment({
@@ -64,15 +78,17 @@ jobs:
64
78
  content: 'eyes'
65
79
  });
66
80
 
67
- console.log('👀 댓글에 확인 리액션 추가 완료');
81
+ console.log(msg('app_trigger.reaction_done'));
68
82
 
69
- - name: 빌드 타입 판별
83
+ - name: Detect build type
70
84
  id: build_type
71
85
  uses: actions/github-script@v9
72
86
  with:
73
87
  github-token: ${{ secrets.GITHUB_TOKEN }}
74
88
  script: |
75
- // 키워드 판별은 소문자로(대소문자 무관 인식), 브랜치명 캡처는 원본 본문에서(대소문자 보존)
89
+ const M = JSON.parse(process.env.PAW_MSG);
90
+ const msg = (k, p = {}) => M[k].replace(/\{(\w+)\}/g, (s, n) => (n in p ? p[n] : s));
91
+ // Keywords are matched on the lower-cased text (case-insensitive); the branch name is captured from the original body (case preserved)
76
92
  const comment = context.payload.comment.body.toLowerCase();
77
93
  const rawComment = context.payload.comment.body;
78
94
 
@@ -82,29 +98,29 @@ jobs:
82
98
  let customBranch = '';
83
99
  let m;
84
100
 
85
- // 명령어 키워드 뒤의 같은 줄 토큰을 옵셔널 브랜치 인자로 캡처
86
- // (줄바꿈 너머 단어가 브랜치로 오인되지 않도록 [^\S\r\n]+ 로 같은 줄 공백만 허용)
87
- // 브랜치명은 rawComment에서 i 플래그로 다시 추출 → Feature/MyBranch 같은 대문자 브랜치 보존
101
+ // Capture the same-line token after the command keyword as an optional branch argument
102
+ // (only same-line whitespace is allowed via [^\S\r\n]+ so a word on the next line is not mistaken for a branch)
103
+ // The branch name is extracted again from rawComment with the i flag -> uppercase branches such as Feature/MyBranch are preserved
88
104
  //
89
- // 어순은 양방향 지원: "{타입} build"(app/apk/ios build)와 "build {타입}"(build app 등) 모두 허용.
90
- // apk가 app으로 오인되지 않도록 app 토큰은 \b 단어경계로 정확히 끊는다(apk·application 제외).
91
- // apk/ios를 app보다 먼저 검사해, "apk build"가 app 분기로 새지 않게 한다.
105
+ // Both word orders are supported: "{type} build" (app/apk/ios build) and "build {type}" (build app, etc.).
106
+ // The app token is cut at a \b word boundary so apk is not mistaken for app (apk and application are excluded).
107
+ // apk/ios are checked before app so "apk build" does not fall into the app branch.
92
108
  if (comment.match(/\/wizard[^\S\r\n]+apk[^\S\r\n]+build/) || comment.match(/\/wizard[^\S\r\n]+build[^\S\r\n]+apk/)) {
93
- // /wizard apk build [브랜치] (또는 build apk) → Android만
109
+ // /wizard apk build [branch] (or build apk) -> Android only
94
110
  buildAndroid = true;
95
111
  buildType = 'apk';
96
112
  m = rawComment.match(/\/wizard[^\S\r\n]+apk[^\S\r\n]+build(?:[^\S\r\n]+(\S+))?/i)
97
113
  || rawComment.match(/\/wizard[^\S\r\n]+build[^\S\r\n]+apk(?:[^\S\r\n]+(\S+))?/i);
98
114
  customBranch = (m && m[1]) || '';
99
115
  } else if (comment.match(/\/wizard[^\S\r\n]+ios[^\S\r\n]+build/) || comment.match(/\/wizard[^\S\r\n]+build[^\S\r\n]+ios/)) {
100
- // /wizard ios build [브랜치] (또는 build ios) → iOS만
116
+ // /wizard ios build [branch] (or build ios) -> iOS only
101
117
  buildIos = true;
102
118
  buildType = 'ios';
103
119
  m = rawComment.match(/\/wizard[^\S\r\n]+ios[^\S\r\n]+build(?:[^\S\r\n]+(\S+))?/i)
104
120
  || rawComment.match(/\/wizard[^\S\r\n]+build[^\S\r\n]+ios(?:[^\S\r\n]+(\S+))?/i);
105
121
  customBranch = (m && m[1]) || '';
106
122
  } else if (comment.match(/\/wizard[^\S\r\n]+app\b[^\S\r\n]+build/) || comment.match(/\/wizard[^\S\r\n]+build[^\S\r\n]+app\b/)) {
107
- // /wizard app build [브랜치] (또는 build app) → Android + iOS 모두
123
+ // /wizard app build [branch] (or build app) -> Android + iOS
108
124
  buildAndroid = true;
109
125
  buildIos = true;
110
126
  buildType = 'app';
@@ -118,12 +134,12 @@ jobs:
118
134
  core.setOutput('buildType', buildType);
119
135
  core.setOutput('customBranch', customBranch);
120
136
 
121
- console.log(`📱 빌드 타입: ${buildType} (Android=${buildAndroid}, iOS=${buildIos})`);
137
+ console.log(msg('app_trigger.build_type', { type: buildType, android: buildAndroid, ios: buildIos }));
122
138
  if (customBranch) {
123
- console.log(`🌿 댓글에서 지정된 브랜치: ${customBranch}`);
139
+ console.log(msg('app_trigger.branch_from_comment', { branch: customBranch }));
124
140
  }
125
141
 
126
- - name: PR/이슈 정보 확인 및 추출
142
+ - name: Resolve PR/issue info
127
143
  id: source_info
128
144
  uses: actions/github-script@v9
129
145
  env:
@@ -131,6 +147,8 @@ jobs:
131
147
  with:
132
148
  github-token: ${{ secrets.GITHUB_TOKEN }}
133
149
  script: |
150
+ const M = JSON.parse(process.env.PAW_MSG);
151
+ const msg = (k, p = {}) => M[k].replace(/\{(\w+)\}/g, (s, n) => (n in p ? p[n] : s));
134
152
  const customBranch = (process.env.CUSTOM_BRANCH || '').trim();
135
153
  const issueNumber = context.payload.issue.number;
136
154
  let sourceType = '';
@@ -139,7 +157,7 @@ jobs:
139
157
  let headSha = '';
140
158
  let relatedIssueNumber = '';
141
159
 
142
- // 1. 먼저 PR인지 확인
160
+ // 1. Check whether it is a PR first
143
161
  try {
144
162
  const pr = await github.rest.pulls.get({
145
163
  owner: context.repo.owner,
@@ -153,34 +171,34 @@ jobs:
153
171
  headSha = pr.data.head.sha;
154
172
 
155
173
  if (customBranch) {
156
- console.log(`🌿 댓글 지정 브랜치 우선 적용: ${customBranch} (PR head=${pr.data.head.ref})`);
174
+ console.log(msg('app_trigger.branch_override', { branch: customBranch, head: pr.data.head.ref }));
157
175
  }
158
176
 
159
- console.log(`✅ PR 확인: #${sourceNumber}`);
160
- console.log(`🌿 브랜치명: ${branchName}`);
161
- console.log(`📝 커밋 해시: ${headSha}`);
177
+ console.log(msg('app_trigger.pr_confirmed', { number: sourceNumber }));
178
+ console.log(msg('app_trigger.branch_name', { branch: branchName }));
179
+ console.log(msg('app_trigger.commit_sha', { sha: headSha }));
162
180
 
163
- // 브랜치명에서 이슈 번호 추출 (#280 형식)
181
+ // Extract the issue number from the branch name (`<date>_#<number>_...` format)
164
182
  const issueMatch = branchName.match(/#(\d+)/);
165
183
  relatedIssueNumber = issueMatch ? issueMatch[1] : '';
166
184
 
167
185
  if (relatedIssueNumber) {
168
- console.log(`📌 추출된 이슈 번호: #${relatedIssueNumber}`);
186
+ console.log(msg('app_trigger.issue_extracted', { number: relatedIssueNumber }));
169
187
  }
170
188
 
171
189
  } catch (error) {
172
- console.log('ℹ️ PR이 아닙니다. 이슈에서 브랜치 정보를 찾습니다...');
190
+ console.log(msg('app_trigger.not_pr'));
173
191
 
174
- // 2. PR이 아니면 이슈에서 "Issue Helper" 댓글 찾기
192
+ // 2. If it is not a PR, look for the "Issue Helper" comment on the issue
175
193
  sourceType = 'ISSUE';
176
194
  sourceNumber = issueNumber.toString();
177
195
  relatedIssueNumber = issueNumber.toString();
178
196
 
179
- // 댓글에서 브랜치를 직접 지정한 경우 Guide 댓글 탐색을 건너뛴다
197
+ // If the branch is given directly in the comment, skip searching for the Guide comment
180
198
  if (customBranch) {
181
199
  branchName = customBranch;
182
- console.log(`🌿 댓글 지정 브랜치 사용 (Guide 댓글 건너뜀): ${branchName}`);
183
- console.log(`📌 관련 이슈 번호: #${relatedIssueNumber}`);
200
+ console.log(msg('app_trigger.custom_branch_used', { branch: branchName }));
201
+ console.log(msg('app_trigger.related_issue', { number: relatedIssueNumber }));
184
202
 
185
203
  core.setOutput('found', 'true');
186
204
  core.setOutput('sourceType', sourceType);
@@ -199,43 +217,44 @@ jobs:
199
217
  per_page: 100
200
218
  });
201
219
 
202
- // "Issue Helper" 댓글 찾기
220
+ // Find the "Issue Helper" comment
203
221
  const guideComment = comments.data.find(c =>
204
222
  c.body.includes('<!-- project-auto-wizard issue helper -->')
205
223
  );
206
224
 
207
225
  if (!guideComment) {
208
- console.log('❌ "Issue Helper" 댓글을 찾을 수 없습니다.');
226
+ console.log(msg('app_trigger.helper_missing_log'));
209
227
  core.setOutput('found', 'false');
210
- core.setOutput('errorMessage', '이슈에서 "Issue Helper" 댓글을 찾을 수 없습니다. 브랜치 정보가 포함된 댓글이 필요합니다.');
228
+ core.setOutput('errorMessage', msg('app_trigger.helper_missing_error'));
211
229
  return;
212
230
  }
213
231
 
214
- console.log('✅ "Issue Helper" 댓글 발견');
232
+ console.log(msg('app_trigger.helper_found'));
215
233
 
216
- // 브랜치명 추출 (### 브랜치 다음 ``` 블록 내용)
217
- const branchMatch = guideComment.body.match(/### 브랜치(?:명)?\s*```\s*([\s\S]*?)\s*```/);
234
+ // Extract the branch name (content of the ``` block after the branch heading).
235
+ // The heading is matched in English and Korean (the Korean words are written as \u escapes) so comments written before a language change still parse.
236
+ const branchMatch = guideComment.body.match(/### (?:\uBE0C\uB79C\uCE58(?:\uBA85)?|Branch(?: name)?)\s*```\s*([\s\S]*?)\s*```/i);
218
237
 
219
238
  if (!branchMatch) {
220
- console.log('❌ 브랜치 정보를 파싱할 수 없습니다.');
239
+ console.log(msg('app_trigger.branch_parse_log'));
221
240
  core.setOutput('found', 'false');
222
- core.setOutput('errorMessage', '"Issue Helper" 댓글에서 브랜치 정보를 파싱할 수 없습니다. 댓글 형식을 확인해주세요.');
241
+ core.setOutput('errorMessage', msg('app_trigger.branch_parse_error'));
223
242
  return;
224
243
  }
225
244
 
226
245
  branchName = branchMatch[1].trim();
227
- console.log(`🌿 추출된 브랜치명: ${branchName}`);
228
- console.log(`✅ 이슈 #${sourceNumber}에서 빌드 정보 추출 완료`);
246
+ console.log(msg('app_trigger.branch_extracted', { branch: branchName }));
247
+ console.log(msg('app_trigger.build_info_extracted', { number: sourceNumber }));
229
248
 
230
249
  } catch (commentError) {
231
- console.log('❌ 이슈 댓글 조회 중 오류 발생:', commentError.message);
250
+ console.log(msg('app_trigger.comments_error_log'), commentError.message);
232
251
  core.setOutput('found', 'false');
233
- core.setOutput('errorMessage', `이슈 댓글 조회 중 오류가 발생했습니다: ${commentError.message}`);
252
+ core.setOutput('errorMessage', msg('app_trigger.comments_error', { error: commentError.message }));
234
253
  return;
235
254
  }
236
255
  }
237
256
 
238
- // 결과 출력
257
+ // Publish the results
239
258
  core.setOutput('found', 'true');
240
259
  core.setOutput('sourceType', sourceType);
241
260
  core.setOutput('sourceNumber', sourceNumber);
@@ -243,24 +262,18 @@ jobs:
243
262
  core.setOutput('headSha', headSha);
244
263
  core.setOutput('relatedIssueNumber', relatedIssueNumber);
245
264
 
246
- - name: 브랜치 정보를 찾을 수 없는 경우 에러 댓글 작성
265
+ - name: Post an error comment when the branch info is not found
247
266
  if: steps.source_info.outputs.found != 'true'
248
267
  uses: actions/github-script@v9
249
268
  with:
250
269
  github-token: ${{ secrets.GITHUB_TOKEN }}
251
270
  script: |
271
+ const M = JSON.parse(process.env.PAW_MSG);
272
+ const msg = (k, p = {}) => M[k].replace(/\{(\w+)\}/g, (s, n) => (n in p ? p[n] : s));
252
273
  const issueNumber = context.payload.issue.number;
253
274
  const errorMessage = '${{ steps.source_info.outputs.errorMessage }}';
254
275
 
255
- const body = `❌ **앱 빌드 트리거 실패**
256
-
257
- ${errorMessage}
258
-
259
- **해결 방법:**
260
- - PR에서 빌드하는 경우: PR 페이지에서 댓글을 작성해주세요.
261
- - 이슈에서 빌드하는 경우: "Issue Helper" 댓글이 있어야 합니다.
262
- - 댓글에 \`### 브랜치\` 섹션과 브랜치명이 포함되어 있어야 합니다.
263
- - 또는 댓글에 브랜치명을 직접 지정할 수 있습니다: \`/wizard app build <브랜치명>\``;
276
+ const body = msg('app_trigger.trigger_failed_body', { error: errorMessage });
264
277
 
265
278
  await github.rest.issues.createComment({
266
279
  owner: context.repo.owner,
@@ -271,18 +284,20 @@ jobs:
271
284
 
272
285
  core.setFailed(errorMessage);
273
286
 
274
- - name: 브랜치 존재 여부 확인
287
+ - name: Check that the branch exists
275
288
  id: check_branch
276
289
  if: steps.source_info.outputs.found == 'true'
277
290
  uses: actions/github-script@v9
278
291
  with:
279
292
  github-token: ${{ secrets.GITHUB_TOKEN }}
280
293
  script: |
294
+ const M = JSON.parse(process.env.PAW_MSG);
295
+ const msg = (k, p = {}) => M[k].replace(/\{(\w+)\}/g, (s, n) => (n in p ? p[n] : s));
281
296
  const branchName = '${{ steps.source_info.outputs.branchName }}';
282
297
  const runId = '${{ github.run_id }}';
283
298
  const runUrl = `https://github.com/${context.repo.owner}/${context.repo.repo}/actions/runs/${runId}`;
284
299
 
285
- console.log(`🔍 브랜치 존재 여부 확인: ${branchName}`);
300
+ console.log(msg('app_trigger.branch_check_log', { branch: branchName }));
286
301
 
287
302
  try {
288
303
  await github.rest.repos.getBranch({
@@ -290,45 +305,47 @@ jobs:
290
305
  repo: context.repo.repo,
291
306
  branch: branchName
292
307
  });
293
- console.log(`✅ 브랜치 존재 확인됨: ${branchName}`);
308
+ console.log(msg('app_trigger.branch_exists_log', { branch: branchName }));
294
309
  core.setOutput('exists', 'true');
295
310
  } catch (error) {
296
311
  if (error.status === 404) {
297
- console.log(`❌ 브랜치를 찾을 수 없음: ${branchName}`);
312
+ console.log(msg('app_trigger.branch_not_found_log', { branch: branchName }));
298
313
  core.setOutput('exists', 'false');
299
- core.setOutput('errorMessage', `브랜치 \`${branchName}\`를 찾을 수 없습니다.`);
314
+ core.setOutput('errorMessage', msg('app_trigger.branch_not_found_error', { branch: branchName }));
300
315
  } else {
301
- console.log(`❌ 브랜치 확인 중 오류: ${error.message}`);
316
+ console.log(msg('app_trigger.branch_check_error_log', { error: error.message }));
302
317
  core.setOutput('exists', 'false');
303
- core.setOutput('errorMessage', `브랜치 확인 중 오류가 발생했습니다: ${error.message}`);
318
+ core.setOutput('errorMessage', msg('app_trigger.branch_check_error', { error: error.message }));
304
319
  }
305
320
  }
306
321
 
307
- - name: 브랜치가 존재하지 않는 경우 에러 댓글 작성
322
+ - name: Post an error comment when the branch does not exist
308
323
  if: steps.source_info.outputs.found == 'true' && steps.check_branch.outputs.exists == 'false'
309
324
  uses: actions/github-script@v9
310
325
  with:
311
326
  github-token: ${{ secrets.GITHUB_TOKEN }}
312
327
  script: |
328
+ const M = JSON.parse(process.env.PAW_MSG);
329
+ const msg = (k, p = {}) => M[k].replace(/\{(\w+)\}/g, (s, n) => (n in p ? p[n] : s));
313
330
  const issueNumber = context.payload.issue.number;
314
331
  const branchName = '${{ steps.source_info.outputs.branchName }}';
315
332
  const runId = '${{ github.run_id }}';
316
333
  const runUrl = `https://github.com/${context.repo.owner}/${context.repo.repo}/actions/runs/${runId}`;
317
334
 
318
335
  const body = [
319
- '❌ **앱 빌드 트리거 실패 - 브랜치를 찾을 수 없습니다**',
336
+ msg('app_trigger.branch_missing_title'),
320
337
  '',
321
- '| 항목 | 값 |',
338
+ msg('app_trigger.table_head'),
322
339
  '|------|-----|',
323
- `| **요청된 브랜치** | \`${branchName}\` |`,
324
- `| **이슈/PR** | #${issueNumber} |`,
340
+ msg('app_trigger.row_requested_branch', { branch: branchName }),
341
+ msg('app_trigger.row_issue_pr', { number: issueNumber }),
325
342
  '',
326
- '### 💡 확인 사항',
327
- '1. 브랜치가 원격 저장소에 push되었는지 확인하세요',
328
- '2. "Issue Helper" 댓글의 브랜치명이 올바른지 확인하세요',
329
- '3. 브랜치명에 오타가 없는지 확인하세요',
343
+ msg('app_trigger.hint_title'),
344
+ msg('app_trigger.hint_pushed'),
345
+ msg('app_trigger.hint_helper_branch'),
346
+ msg('app_trigger.hint_typo'),
330
347
  '',
331
- `🔗 [워크플로우 로그](${runUrl})`
348
+ msg('app_trigger.workflow_log_link', { url: runUrl })
332
349
  ].join('\n');
333
350
 
334
351
  await github.rest.issues.createComment({
@@ -338,23 +355,25 @@ jobs:
338
355
  body: body
339
356
  });
340
357
 
341
- core.setFailed(`브랜치를 찾을 수 없습니다: ${branchName}`);
358
+ core.setFailed(msg('app_trigger.branch_not_found_fail', { branch: branchName }));
342
359
 
343
- - name: 빌드 횟수 조회 및 빌드 번호 생성
360
+ - name: Look up the build count and generate the build number
344
361
  id: build_number_calc
345
362
  if: steps.source_info.outputs.found == 'true' && steps.check_branch.outputs.exists == 'true'
346
363
  uses: actions/github-script@v9
347
364
  with:
348
365
  github-token: ${{ secrets.GITHUB_TOKEN }}
349
366
  script: |
367
+ const M = JSON.parse(process.env.PAW_MSG);
368
+ const msg = (k, p = {}) => M[k].replace(/\{(\w+)\}/g, (s, n) => (n in p ? p[n] : s));
350
369
  const sourceNumber = parseInt('${{ steps.source_info.outputs.sourceNumber }}');
351
370
  const sourceType = '${{ steps.source_info.outputs.sourceType }}';
352
371
  const buildType = '${{ steps.build_type.outputs.buildType }}';
353
372
 
354
- console.log(`📊 ${sourceType} #${sourceNumber}의 빌드 횟수 조회 중...`);
355
- console.log(`📱 빌드 타입: ${buildType}`);
373
+ console.log(msg('app_trigger.count_lookup', { type: sourceType, number: sourceNumber }));
374
+ console.log(msg('app_trigger.count_build_type', { type: buildType }));
356
375
 
357
- // PR/이슈의 모든 댓글 조회
376
+ // Fetch every comment on the PR/issue
358
377
  const comments = await github.rest.issues.listComments({
359
378
  owner: context.repo.owner,
360
379
  repo: context.repo.repo,
@@ -362,35 +381,35 @@ jobs:
362
381
  per_page: 100
363
382
  });
364
383
 
365
- // 빌드 타입에 따라 해당 플랫폼의 빌드 횟수 카운트
366
- // 어순 무관 카운트: includes('build') && includes('{타입}') 로 "ios build"·"build ios" 모두 잡는다.
367
- // iOS: ios 빌드 + app(공용) 빌드 통합 (App Store Connect는 앱 전체에서 빌드 번호 unique)
368
- // Android: apk 빌드 + app(공용) 빌드 통합 (Play Store도 동일)
369
- // App: 모든 빌드 명령어 통합 카운트
384
+ // Count the builds of the matching platform for the build type
385
+ // Order-independent counting: includes('build') && includes('{type}') catches both "ios build" and "build ios".
386
+ // iOS: ios builds + app (shared) builds combined (App Store Connect requires unique build numbers across the whole app)
387
+ // Android: apk builds + app (shared) builds combined (same for the Play Store)
388
+ // App: every build command counted together
370
389
  let buildCount = 0;
371
390
 
372
391
  if (buildType === 'ios') {
373
- // iOS 빌드: ios 빌드 + app 공용 빌드 둘 다 카운트 (어순 무관)
392
+ // iOS build: count both ios builds and shared app builds (word order does not matter)
374
393
  buildCount = comments.data.filter(c => {
375
394
  const body = c.body.toLowerCase();
376
395
  return body.includes('/wizard') && body.includes('build') && (
377
396
  body.includes('ios') || body.includes('app')
378
397
  );
379
398
  }).length;
380
- console.log(`📊 iOS 빌드 카운트: ios + app(공용) 통합`);
399
+ console.log(msg('app_trigger.count_ios'));
381
400
 
382
401
  } else if (buildType === 'apk') {
383
- // Android 빌드: apk 빌드 + app 공용 빌드 둘 다 카운트 (어순 무관)
402
+ // Android build: count both apk builds and shared app builds (word order does not matter)
384
403
  buildCount = comments.data.filter(c => {
385
404
  const body = c.body.toLowerCase();
386
405
  return body.includes('/wizard') && body.includes('build') && (
387
406
  body.includes('apk') || body.includes('app')
388
407
  );
389
408
  }).length;
390
- console.log(`📊 Android 빌드 카운트: apk + app(공용) 통합`);
409
+ console.log(msg('app_trigger.count_android'));
391
410
 
392
411
  } else if (buildType === 'app') {
393
- // 전체 앱 빌드: 모든 빌드 명령어 카운트 (어순 무관)
412
+ // Full app build: count every build command (word order does not matter)
394
413
  buildCount = comments.data.filter(c => {
395
414
  const body = c.body.toLowerCase();
396
415
  return body.includes('/wizard') && body.includes('build') && (
@@ -399,19 +418,19 @@ jobs:
399
418
  body.includes('app')
400
419
  );
401
420
  }).length;
402
- console.log(`📊 전체 앱 빌드 카운트: 모든 빌드 명령어 통합`);
421
+ console.log(msg('app_trigger.count_app'));
403
422
  }
404
423
 
405
- // 빌드 번호: 소스번호 + 2자리 카운트 (38800, 38801, 38802...)
424
+ // Build number: source number + 2-digit count (38800, 38801, 38802...)
406
425
  const buildNumber = `${sourceNumber}${String(buildCount).padStart(2, '0')}`;
407
426
 
408
- console.log(`📊 ${sourceType} #${sourceNumber}: ${buildType} ${buildCount}번째 빌드`);
409
- console.log(`📦 생성된 빌드 번호: ${buildNumber}`);
427
+ console.log(msg('app_trigger.count_result', { type: sourceType, number: sourceNumber, build_type: buildType, count: buildCount }));
428
+ console.log(msg('app_trigger.build_number_generated', { number: buildNumber }));
410
429
 
411
430
  core.setOutput('buildCount', buildCount.toString());
412
431
  core.setOutput('buildNumber', buildNumber);
413
432
 
414
- - name: Android 빌드 워크플로우 트리거
433
+ - name: Trigger the Android build workflow
415
434
  if: |
416
435
  steps.source_info.outputs.found == 'true' &&
417
436
  steps.check_branch.outputs.exists == 'true' &&
@@ -420,17 +439,19 @@ jobs:
420
439
  with:
421
440
  github-token: ${{ secrets.GITHUB_TOKEN }}
422
441
  script: |
442
+ const M = JSON.parse(process.env.PAW_MSG);
443
+ const msg = (k, p = {}) => M[k].replace(/\{(\w+)\}/g, (s, n) => (n in p ? p[n] : s));
423
444
  const sourceType = '${{ steps.source_info.outputs.sourceType }}';
424
445
  const sourceNumber = '${{ steps.source_info.outputs.sourceNumber }}';
425
446
  const branchName = '${{ steps.source_info.outputs.branchName }}';
426
447
  const relatedIssueNumber = '${{ steps.source_info.outputs.relatedIssueNumber }}';
427
448
  const buildNumber = '${{ steps.build_number_calc.outputs.buildNumber }}';
428
449
 
429
- console.log(`🚀 Android 빌드 워크플로우 트리거 시작...`);
430
- console.log(` ${sourceType} 번호: #${sourceNumber}`);
431
- console.log(` 빌드 번호: ${buildNumber}`);
432
- console.log(` 브랜치: ${branchName}`);
433
- console.log(` 관련 이슈 번호: ${relatedIssueNumber || '없음'}`);
450
+ console.log(msg('app_trigger.dispatch_start', { platform: 'Android' }));
451
+ console.log(msg('app_trigger.dispatch_source_number', { type: sourceType, number: sourceNumber }));
452
+ console.log(msg('app_trigger.dispatch_build_number', { number: buildNumber }));
453
+ console.log(msg('app_trigger.dispatch_branch', { branch: branchName }));
454
+ console.log(msg('app_trigger.dispatch_related_issue', { issue: relatedIssueNumber || msg('app_trigger.none') }));
434
455
 
435
456
  await github.rest.repos.createDispatchEvent({
436
457
  owner: context.repo.owner,
@@ -447,9 +468,9 @@ jobs:
447
468
  }
448
469
  });
449
470
 
450
- console.log('✅ Android 빌드 워크플로우 트리거 완료');
471
+ console.log(msg('app_trigger.dispatch_done', { platform: 'Android' }));
451
472
 
452
- - name: iOS 빌드 워크플로우 트리거
473
+ - name: Trigger the iOS build workflow
453
474
  if: |
454
475
  steps.source_info.outputs.found == 'true' &&
455
476
  steps.check_branch.outputs.exists == 'true' &&
@@ -458,17 +479,19 @@ jobs:
458
479
  with:
459
480
  github-token: ${{ secrets.GITHUB_TOKEN }}
460
481
  script: |
482
+ const M = JSON.parse(process.env.PAW_MSG);
483
+ const msg = (k, p = {}) => M[k].replace(/\{(\w+)\}/g, (s, n) => (n in p ? p[n] : s));
461
484
  const sourceType = '${{ steps.source_info.outputs.sourceType }}';
462
485
  const sourceNumber = '${{ steps.source_info.outputs.sourceNumber }}';
463
486
  const branchName = '${{ steps.source_info.outputs.branchName }}';
464
487
  const relatedIssueNumber = '${{ steps.source_info.outputs.relatedIssueNumber }}';
465
488
  const buildNumber = '${{ steps.build_number_calc.outputs.buildNumber }}';
466
489
 
467
- console.log(`🚀 iOS 빌드 워크플로우 트리거 시작...`);
468
- console.log(` ${sourceType} 번호: #${sourceNumber}`);
469
- console.log(` 빌드 번호: ${buildNumber}`);
470
- console.log(` 브랜치: ${branchName}`);
471
- console.log(` 관련 이슈 번호: ${relatedIssueNumber || '없음'}`);
490
+ console.log(msg('app_trigger.dispatch_start', { platform: 'iOS' }));
491
+ console.log(msg('app_trigger.dispatch_source_number', { type: sourceType, number: sourceNumber }));
492
+ console.log(msg('app_trigger.dispatch_build_number', { number: buildNumber }));
493
+ console.log(msg('app_trigger.dispatch_branch', { branch: branchName }));
494
+ console.log(msg('app_trigger.dispatch_related_issue', { issue: relatedIssueNumber || msg('app_trigger.none') }));
472
495
 
473
496
  await github.rest.repos.createDispatchEvent({
474
497
  owner: context.repo.owner,
@@ -485,13 +508,14 @@ jobs:
485
508
  }
486
509
  });
487
510
 
488
- console.log('✅ iOS 빌드 워크플로우 트리거 완료');
511
+ console.log(msg('app_trigger.dispatch_done', { platform: 'iOS' }));
489
512
 
490
- # 트리거 완료 댓글은 제거됨 - Android/iOS 각각의 진행상황 댓글이 이 역할을 대체
491
- - name: 트리거 완료 로그
513
+ # The trigger-complete comment was removed - the per-platform progress comments (Android/iOS) take over that role
514
+ - name: Log trigger completion
492
515
  if: steps.source_info.outputs.found == 'true' && steps.check_branch.outputs.exists == 'true'
493
516
  run: |
494
- echo "✅ Android 및 iOS 빌드 워크플로우 트리거 완료"
495
- echo "📦 빌드 번호: ${{ steps.build_number_calc.outputs.buildNumber }}"
496
- echo "🌿 브랜치: ${{ steps.source_info.outputs.branchName }}"
497
- echo "📋 각 플랫폼별 진행상황 댓글이 자동으로 생성됩니다."
517
+ m() { python3 "$GITHUB_WORKSPACE/.github/scripts/messages.py" get "$@"; }
518
+ m app_trigger.done_log
519
+ m app_trigger.done_build_number number="${{ steps.build_number_calc.outputs.buildNumber }}"
520
+ m app_trigger.done_branch branch="${{ steps.source_info.outputs.branchName }}"
521
+ m app_trigger.done_progress_note