project-auto-wizard 0.13.3 → 0.15.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 (115) hide show
  1. package/README.ja.md +2 -0
  2. package/README.ko.md +2 -0
  3. package/README.md +3 -1
  4. package/README.zh-CN.md +2 -0
  5. package/bin/project-auto-wizard.js +18 -4
  6. package/package.json +2 -2
  7. package/payload/config/breaking-changes.json +28 -14
  8. package/payload/config/wizard-prompts.yml +102 -46
  9. package/payload/flutter-app/android/fastlane/Fastfile.playstore +44 -32
  10. package/payload/flutter-app/ios/ExportOptions.plist +8 -8
  11. package/payload/flutter-app/ios/fastlane/Fastfile +57 -43
  12. package/payload/scripts/changelog_manager.py +304 -265
  13. package/payload/scripts/issue_helper.py +54 -50
  14. package/payload/scripts/messages.py +2585 -0
  15. package/payload/scripts/truncate_release_notes.py +7 -6
  16. package/payload/scripts/version_manager.py +110 -107
  17. package/payload/version.yml.template +2 -1
  18. package/payload/workflows/common/PROJECT-COMMON-AI-PR-SUMMARY.yaml +23 -16
  19. package/payload/workflows/common/PROJECT-COMMON-AUTO-CHANGELOG-CONTROL.yaml +106 -63
  20. package/payload/workflows/common/PROJECT-COMMON-ISSUE-HELPER.yaml +17 -15
  21. package/payload/workflows/common/PROJECT-COMMON-README-VERSION-UPDATE.yaml +55 -45
  22. package/payload/workflows/common/PROJECT-COMMON-RELEASE-PUBLISH.yaml +90 -72
  23. package/payload/workflows/common/PROJECT-COMMON-VERSION-CONTROL.yaml +59 -45
  24. package/payload/workflows/flutter/PROJECT-FLUTTER-ANDROID-FIREBASE-CICD.yaml +194 -184
  25. package/payload/workflows/flutter/PROJECT-FLUTTER-ANDROID-PLAYSTORE-CICD.yaml +226 -213
  26. package/payload/workflows/flutter/PROJECT-FLUTTER-ANDROID-SELFHOSTED-CICD.yaml +46 -42
  27. package/payload/workflows/flutter/PROJECT-FLUTTER-ANDROID-TEST-APK.yaml +334 -241
  28. package/payload/workflows/flutter/PROJECT-FLUTTER-APP-BUILD-TRIGGER.yaml +152 -128
  29. package/payload/workflows/flutter/PROJECT-FLUTTER-CI.yaml +246 -188
  30. package/payload/workflows/flutter/PROJECT-FLUTTER-IOS-TEST-TESTFLIGHT.yaml +353 -238
  31. package/payload/workflows/flutter/PROJECT-FLUTTER-IOS-TESTFLIGHT.yaml +96 -85
  32. package/payload/workflows/go/PROJECT-GO-CI.yaml +47 -36
  33. package/payload/workflows/go/PROJECT-GO-PR-PREVIEW.yaml +945 -508
  34. package/payload/workflows/go/PROJECT-GO-SIMPLE-CICD.yaml +155 -133
  35. package/payload/workflows/next/PROJECT-NEXT-CI.yaml +76 -60
  36. package/payload/workflows/next/PROJECT-NEXT-CICD.yaml +97 -77
  37. package/payload/workflows/python/PROJECT-PYTHON-CI.yaml +62 -47
  38. package/payload/workflows/python/PROJECT-PYTHON-PR-PREVIEW.yaml +942 -505
  39. package/payload/workflows/python/PROJECT-PYTHON-SIMPLE-CICD.yaml +148 -126
  40. package/payload/workflows/react/PROJECT-REACT-CI.yaml +86 -70
  41. package/payload/workflows/react/PROJECT-REACT-CICD.yaml +112 -92
  42. package/payload/workflows/spring/PROJECT-SPRING-CI.yml +112 -82
  43. package/payload/workflows/spring/server-deploy/PROJECT-SPRING-NONSTOP-NGINX-CICD.yaml +180 -150
  44. package/payload/workflows/spring/server-deploy/PROJECT-SPRING-NONSTOP-TRAEFIK-CICD.yaml +161 -131
  45. package/payload/workflows/spring/server-deploy/PROJECT-SPRING-PR-PREVIEW.yaml +970 -549
  46. package/payload/workflows/spring/server-deploy/PROJECT-SPRING-SIMPLE-CICD.yaml +193 -162
  47. package/src/cli/args.js +98 -52
  48. package/src/cli/help.js +8 -41
  49. package/src/commands/doctor.js +90 -89
  50. package/src/commands/dry-run.js +64 -43
  51. package/src/commands/full.js +79 -77
  52. package/src/commands/install-settings.js +16 -15
  53. package/src/commands/interactive-flutter.js +20 -19
  54. package/src/commands/interactive.js +135 -132
  55. package/src/commands/purge.js +47 -42
  56. package/src/commands/status.js +36 -36
  57. package/src/commands/uninstall.js +43 -41
  58. package/src/context.js +12 -11
  59. package/src/core/assets.js +12 -11
  60. package/src/core/baseline.js +27 -27
  61. package/src/core/branches.js +29 -27
  62. package/src/core/branding.js +7 -6
  63. package/src/core/breaking-check.js +22 -21
  64. package/src/core/breaking.js +15 -8
  65. package/src/core/copy/app-files.js +5 -4
  66. package/src/core/copy/flutter-app.js +8 -8
  67. package/src/core/copy/gitignore.js +58 -50
  68. package/src/core/copy/readme.js +102 -36
  69. package/src/core/copy/simple.js +16 -15
  70. package/src/core/copy/workflows.js +131 -124
  71. package/src/core/deploy-style.js +51 -48
  72. package/src/core/detect-fs.js +57 -57
  73. package/src/core/detect.js +67 -65
  74. package/src/core/errors.js +2 -2
  75. package/src/core/flutter-doctor.js +17 -16
  76. package/src/core/flutter-hooks.js +33 -32
  77. package/src/core/flutter-options.js +34 -33
  78. package/src/core/fsutil.js +12 -12
  79. package/src/core/installed-stores.js +4 -4
  80. package/src/core/logger.js +42 -40
  81. package/src/core/paths-resolve.js +88 -86
  82. package/src/core/paths.js +10 -10
  83. package/src/core/release-options.js +8 -8
  84. package/src/core/removal-exec.js +14 -13
  85. package/src/core/removal-plan.js +37 -37
  86. package/src/core/types.js +47 -47
  87. package/src/core/verify.js +44 -38
  88. package/src/core/version-yml.js +76 -68
  89. package/src/core/wizard-env.js +46 -46
  90. package/src/core/wizard-labels.js +63 -41
  91. package/src/i18n/catalog/en/cli.js +50 -0
  92. package/src/i18n/catalog/en/commands.js +188 -0
  93. package/src/i18n/catalog/en/copy.js +20 -0
  94. package/src/i18n/catalog/en/core.js +74 -0
  95. package/src/i18n/catalog/en/core2.js +15 -0
  96. package/src/i18n/catalog/en/ui.js +221 -0
  97. package/src/i18n/catalog/en.js +12 -0
  98. package/src/i18n/catalog/index.js +5 -0
  99. package/src/i18n/catalog/ko/cli.js +50 -0
  100. package/src/i18n/catalog/ko/commands.js +188 -0
  101. package/src/i18n/catalog/ko/copy.js +20 -0
  102. package/src/i18n/catalog/ko/core.js +74 -0
  103. package/src/i18n/catalog/ko/core2.js +15 -0
  104. package/src/i18n/catalog/ko/ui.js +221 -0
  105. package/src/i18n/catalog/ko.js +10 -0
  106. package/src/i18n/index.js +44 -0
  107. package/src/i18n/languages.js +9 -0
  108. package/src/index.js +132 -97
  109. package/src/ui/ansi.js +7 -7
  110. package/src/ui/banner.js +7 -6
  111. package/src/ui/env-plan.js +82 -81
  112. package/src/ui/prompts.js +97 -122
  113. package/src/ui/readline-engine.js +59 -58
  114. package/src/ui/status-cards.js +37 -31
  115. package/src/ui/summary.js +90 -89
@@ -62,14 +62,14 @@ on:
62
62
 
63
63
  permissions:
64
64
  contents: write
65
- # README-VERSION-UPDATE·배포 워크플로우를 workflow_dispatch로 깨운다
65
+ # Wake README-VERSION-UPDATE and the deploy workflows via workflow_dispatch
66
66
  actions: write
67
- # 릴리스 PR을 누가 병합했는지 읽어 배포를 다시 깨울지 정한다
67
+ # Read who merged the release PR to decide whether deploys must be woken again
68
68
  pull-requests: read
69
69
  copilot-requests: write
70
70
 
71
71
  env:
72
- # 스크립트 import가 .github/scripts/__pycache__를 만들어 봇 커밋에 섞이지 않게 한다
72
+ # Keep script imports from creating .github/scripts/__pycache__ so it never ends up in bot commits
73
73
  PYTHONDONTWRITEBYTECODE: "1"
74
74
 
75
75
  jobs:
@@ -94,15 +94,17 @@ jobs:
94
94
  - name: Detect branch mode
95
95
  id: mode
96
96
  run: |
97
+ m() { python3 "$GITHUB_WORKSPACE/.github/scripts/messages.py" get "$@"; }
97
98
  # scoped to the metadata: -> template: -> branches: chain so an
98
99
  # unrelated top-level "mode:" key can never hijack the value
99
100
  MODE=$(python3 -c 'import re; t=open("version.yml",encoding="utf-8").read(); m=re.search(r"metadata:.*?template:.*?branches:.*?mode:\s*\"?([A-Za-z-]+)", t, re.S); print(m.group(1) if m else "pr-flow")' 2>/dev/null || echo "pr-flow")
100
101
  echo "mode=$MODE" >> $GITHUB_OUTPUT
101
- echo "branch mode: $MODE"
102
+ echo "$(m wf_release.branch_mode mode="$MODE")"
102
103
 
103
104
  - name: Release gate
104
105
  id: gate
105
106
  run: |
107
+ m() { python3 "$GITHUB_WORKSPACE/.github/scripts/messages.py" get "$@"; }
106
108
  MODE="${{ steps.mode.outputs.mode }}"
107
109
  EVENT="${{ github.event_name }}"
108
110
  HEAD_MSG=$(git log -1 --pretty=%B)
@@ -122,11 +124,11 @@ jobs:
122
124
 
123
125
  echo "proceed=$PROCEED" >> $GITHUB_OUTPUT
124
126
  if [ "$PROCEED" = "true" ]; then
125
- echo "release gate open (mode=$MODE, event=$EVENT)"
127
+ echo "$(m wf_release.gate_open mode="$MODE" event="$EVENT")"
126
128
  exit 0
127
129
  fi
128
130
 
129
- echo "not a release-confirm push in pr-flow mode — skipping"
131
+ echo "$(m wf_release.gate_closed)"
130
132
 
131
133
  # Drift guard: skipping is normal, but a version.yml that
132
134
  # has run ahead of the newest tag is not — it means the version was
@@ -137,26 +139,26 @@ jobs:
137
139
  LATEST_VERSION="${LATEST_TAG#v}"
138
140
 
139
141
  if [ -z "$FILE_VERSION" ] || [ -z "$LATEST_VERSION" ]; then
140
- echo "drift guard skipped (no version.yml version or no tag yet)"
142
+ echo "$(m wf_release.drift_skipped)"
141
143
  exit 0
142
144
  fi
143
145
 
144
146
  HIGHER=$(printf '%s\n%s\n' "$FILE_VERSION" "$LATEST_VERSION" | sort -V | tail -n 1)
145
147
  if [ "$FILE_VERSION" != "$LATEST_VERSION" ] && [ "$HIGHER" = "$FILE_VERSION" ]; then
146
- echo "::error::version.yml($FILE_VERSION) is ahead of the newest tag($LATEST_TAG) but no release was published — this version will never reach npm. Recover with: Actions → PROJECT-RELEASE-PUBLISH → Run workflow (workflow_dispatch bypasses this gate)."
148
+ echo "::error::$(m wf_release.drift_error file_version="$FILE_VERSION" latest_tag="$LATEST_TAG")"
147
149
  {
148
- echo "### ⚠️ 릴리스 누락 감지"
150
+ echo "$(m wf_release.drift_heading)"
149
151
  echo ""
150
- echo "- version.yml: \`$FILE_VERSION\`"
151
- echo "- 최신 태그: \`$LATEST_TAG\`"
152
+ echo "$(m wf_release.drift_version_line version="$FILE_VERSION")"
153
+ echo "$(m wf_release.drift_tag_line tag="$LATEST_TAG")"
152
154
  echo ""
153
- echo "릴리스 확정 없이 버전만 올라갔습니다. 이 버전은 npm에 배포되지 않습니다."
154
- echo "복구: Actions → PROJECT-RELEASE-PUBLISH → Run workflow"
155
+ echo "$(m wf_release.drift_body)"
156
+ echo "$(m wf_release.drift_recover)"
155
157
  } >> "$GITHUB_STEP_SUMMARY"
156
158
  exit 1
157
159
  fi
158
160
 
159
- echo "drift guard ok (version.yml=$FILE_VERSION, latest tag=$LATEST_TAG)"
161
+ echo "$(m wf_release.drift_ok file_version="$FILE_VERSION" latest_tag="$LATEST_TAG")"
160
162
 
161
163
  - name: Read semver_auto option from version.yml
162
164
  id: semver_options
@@ -165,11 +167,12 @@ jobs:
165
167
  steps.mode.outputs.mode == 'trunk-based' &&
166
168
  github.event_name == 'push'
167
169
  run: |
170
+ m() { python3 "$GITHUB_WORKSPACE/.github/scripts/messages.py" get "$@"; }
168
171
  # scoped to the metadata: -> template: -> options: chain so an
169
172
  # unrelated "semver_auto:" key can never hijack the value
170
173
  SEMVER_AUTO=$(python3 -c 'import re; t=open("version.yml",encoding="utf-8").read(); m=re.search(r"metadata:.*?template:.*?options:.*?semver_auto:\s*\"?(true|false)", t, re.S); print(m.group(1) if m else "false")' 2>/dev/null || echo "false")
171
174
  echo "semver_auto=$SEMVER_AUTO" >> $GITHUB_OUTPUT
172
- echo "semver_auto option: $SEMVER_AUTO"
175
+ echo "$(m wf_common.semver_auto_option semver_auto="$SEMVER_AUTO")"
173
176
 
174
177
  - name: Read copilot_ai option from version.yml
175
178
  id: copilot_options
@@ -178,11 +181,12 @@ jobs:
178
181
  steps.mode.outputs.mode == 'trunk-based' &&
179
182
  github.event_name == 'push'
180
183
  run: |
184
+ m() { python3 "$GITHUB_WORKSPACE/.github/scripts/messages.py" get "$@"; }
181
185
  # scoped to the metadata: -> template: -> options: chain so an
182
186
  # unrelated "copilot_ai:" key can never hijack the value
183
187
  COPILOT_AI=$(python3 -c 'import re; t=open("version.yml",encoding="utf-8").read(); m=re.search(r"metadata:.*?template:.*?options:.*?copilot_ai:\s*\"?(true|false)", t, re.S); print(m.group(1) if m else "false")' 2>/dev/null || echo "false")
184
188
  echo "copilot_ai=$COPILOT_AI" >> $GITHUB_OUTPUT
185
- echo "copilot_ai option: $COPILOT_AI"
189
+ echo "$(m wf_common.copilot_ai_option copilot_ai="$COPILOT_AI")"
186
190
 
187
191
  - name: Install Copilot CLI
188
192
  if: >-
@@ -205,13 +209,14 @@ jobs:
205
209
  GITHUB_TOKEN: ${{ github.token }}
206
210
  COPILOT_AI: ${{ steps.copilot_options.outputs.copilot_ai }}
207
211
  run: |
212
+ m() { python3 "$GITHUB_WORKSPACE/.github/scripts/messages.py" get "$@"; }
208
213
  # Idempotency: on a re-run of a failed job the checked-out HEAD may
209
214
  # already be this workflow's own docs commit — never bump twice.
210
215
  # Jump straight to the common tail (tag + release for the current
211
216
  # version).
212
217
  HEAD_MSG=$(git log -1 --pretty=%s)
213
218
  if printf '%s\n' "$HEAD_MSG" | grep -q "^chore(version): release v.* docs \[skip ci\]"; then
214
- echo "HEAD is already this workflow's release docs commit — skipping re-increment (idempotent re-run)"
219
+ echo "$(m wf_release.head_is_docs_commit)"
215
220
  exit 0
216
221
  fi
217
222
 
@@ -221,17 +226,18 @@ jobs:
221
226
  # release (no previous tag to diff against).
222
227
  LAST_TAG=$(git describe --tags --abbrev=0 2>/dev/null || echo "")
223
228
  if [ -n "$LAST_TAG" ]; then
224
- echo "collecting commits since $LAST_TAG"
229
+ echo "$(m wf_release.collect_since last_tag="$LAST_TAG")"
225
230
  LOG_RANGE="${LAST_TAG}..HEAD"
226
231
  DIFF_BASE="$LAST_TAG"
227
232
  else
228
- echo "no previous tag — collecting the full history"
233
+ echo "$(m wf_release.collect_all)"
229
234
  LOG_RANGE="HEAD"
230
235
  DIFF_BASE="4b825dc642cb6eb9a060e54bf8d69288fbee4904" # git empty tree hash
231
236
  fi
232
237
  git log --pretty=%s "$LOG_RANGE" > commits.txt
233
- # 본문 푸터 BREAKING CHANGE:는 제목 목록 뒤에 한 줄씩 덧붙인다 — 스크립트는 한 줄을
234
- # 한 항목으로 읽으므로 본문 전체(%B)를 넣으면 설명 줄까지 변경 항목으로 잡힌다.
238
+ # Body-footer BREAKING CHANGE: lines are appended after the subject list, one per
239
+ # line. The script reads each line as one entry, so passing the whole body (%B)
240
+ # would turn description lines into change entries.
235
241
  git log --pretty=%b "$LOG_RANGE" | grep -E '^BREAKING[ -]CHANGE[[:space:]]*:' >> commits.txt || true
236
242
  git diff --stat "${DIFF_BASE}..HEAD" > _full_diff_stat.txt
237
243
  if [ "$(wc -l < _full_diff_stat.txt)" -gt 49 ]; then
@@ -246,15 +252,15 @@ jobs:
246
252
  else
247
253
  BUMP="patch"
248
254
  fi
249
- echo "bump level: $BUMP"
255
+ echo "$(m wf_common.bump_level bump="$BUMP")"
250
256
 
251
257
  NEW_VERSION=$(python3 .github/scripts/version_manager.py increment --bump "$BUMP" | tail -n 1)
252
258
  if [ -z "$NEW_VERSION" ]; then
253
- echo "version bump failed"
259
+ echo "$(m wf_common.version_bump_failed)"
254
260
  exit 1
255
261
  fi
256
262
  python3 .github/scripts/version_manager.py sync
257
- echo "release version: $NEW_VERSION ($BUMP)"
263
+ echo "$(m wf_release.release_version new_version="$NEW_VERSION" bump="$BUMP")"
258
264
 
259
265
  python3 .github/scripts/changelog_manager.py ai-summary \
260
266
  --commits-file commits.txt \
@@ -281,13 +287,13 @@ jobs:
281
287
 
282
288
  rm -f pr_body.md summary.md commits.txt diff_stat.txt
283
289
 
284
- # -A는 러너가 만든 임시 파일·캐시까지 사용자 레포에 커밋한다.
285
- # 버전 파일은 이미 추적 중이라 -u로 충분하고, 첫 릴리스에 새로 생기는
286
- # CHANGELOG만 따로 올린다 (.gitignore에 있으면 건너뛴다).
290
+ # -A would also commit runner temp files and caches into the user's repo.
291
+ # Version files are already tracked, so -u is enough; only a CHANGELOG
292
+ # created by the first release is added separately (skipped if ignored).
287
293
  git add -u
288
294
  git ls-files -z --others --exclude-standard -- CHANGELOG.json CHANGELOG.md | xargs -0 -r git add --
289
295
  if git diff --staged --quiet; then
290
- echo "no changes to commit"
296
+ echo "$(m wf_common.no_changes)"
291
297
  else
292
298
  git commit -m "chore(version): release v${NEW_VERSION} docs [skip ci]"
293
299
 
@@ -298,18 +304,18 @@ jobs:
298
304
 
299
305
  while [ $RETRY_COUNT -lt $MAX_RETRIES ]; do
300
306
  RETRY_COUNT=$((RETRY_COUNT + 1))
301
- echo "push attempt $RETRY_COUNT/$MAX_RETRIES..."
307
+ echo "$(m wf_common.push_attempt retry_count="$RETRY_COUNT" max_retries="$MAX_RETRIES")"
302
308
 
303
309
  if git push origin HEAD:{{MAIN_BRANCH}}; then
304
310
  PUSH_SUCCESS=true
305
- echo "release docs pushed"
311
+ echo "$(m wf_release.docs_pushed)"
306
312
  break
307
313
  else
308
- echo "push failed, syncing with remote..."
314
+ echo "$(m wf_common.push_failed_syncing)"
309
315
  if git pull --rebase origin {{MAIN_BRANCH}}; then
310
- echo "rebase ok, retrying push..."
316
+ echo "$(m wf_common.rebase_ok)"
311
317
  else
312
- echo "rebase failed, manual conflict resolution required"
318
+ echo "$(m wf_common.rebase_failed)"
313
319
  git rebase --abort 2>/dev/null || true
314
320
  exit 1
315
321
  fi
@@ -317,7 +323,7 @@ jobs:
317
323
  done
318
324
 
319
325
  if [ "$PUSH_SUCCESS" = false ]; then
320
- echo "push failed after $MAX_RETRIES attempts"
326
+ echo "$(m wf_common.push_gave_up max_retries="$MAX_RETRIES")"
321
327
  exit 1
322
328
  fi
323
329
  fi
@@ -328,9 +334,10 @@ jobs:
328
334
  env:
329
335
  GH_TOKEN: ${{ github.token }}
330
336
  run: |
337
+ m() { python3 "$GITHUB_WORKSPACE/.github/scripts/messages.py" get "$@"; }
331
338
  VERSION=$(python3 .github/scripts/version_manager.py get | tail -n 1)
332
339
  if [ -z "$VERSION" ]; then
333
- echo "failed to read version"
340
+ echo "$(m wf_release.read_version_failed)"
334
341
  exit 1
335
342
  fi
336
343
  echo "version=$VERSION" >> $GITHUB_OUTPUT
@@ -342,32 +349,34 @@ jobs:
342
349
  # "release created" can be re-run and still publish the release.
343
350
  if git rev-parse "v$VERSION" >/dev/null 2>&1; then
344
351
  echo "tag_exists=true" >> $GITHUB_OUTPUT
345
- echo "tag v$VERSION already exists — skipping tag push"
352
+ echo "$(m wf_release.tag_exists version="$VERSION")"
346
353
  else
347
354
  echo "tag_exists=false" >> $GITHUB_OUTPUT
348
355
  fi
349
356
 
350
357
  if gh release view "v$VERSION" >/dev/null 2>&1; then
351
358
  echo "release_exists=true" >> $GITHUB_OUTPUT
352
- echo "release v$VERSION already exists — nothing to publish"
359
+ echo "$(m wf_release.release_exists version="$VERSION")"
353
360
  else
354
361
  echo "release_exists=false" >> $GITHUB_OUTPUT
355
- echo "publishing release v$VERSION"
362
+ echo "$(m wf_release.publishing version="$VERSION")"
356
363
  fi
357
364
 
358
365
  - name: Create and push tag
359
366
  if: steps.gate.outputs.proceed == 'true' && steps.version.outputs.tag_exists != 'true'
360
367
  run: |
368
+ m() { python3 "$GITHUB_WORKSPACE/.github/scripts/messages.py" get "$@"; }
361
369
  VERSION="${{ steps.version.outputs.version }}"
362
370
  git tag "v$VERSION"
363
371
  git push origin "v$VERSION"
364
- echo "tag v$VERSION pushed"
372
+ echo "$(m wf_release.tag_pushed version="$VERSION")"
365
373
 
366
374
  - name: Build release notes (changelog export + GitHub generate-notes)
367
375
  if: steps.gate.outputs.proceed == 'true' && steps.version.outputs.release_exists != 'true'
368
376
  env:
369
377
  GH_TOKEN: ${{ github.token }}
370
378
  run: |
379
+ m() { python3 "$GITHUB_WORKSPACE/.github/scripts/messages.py" get "$@"; }
371
380
  VERSION="${{ steps.version.outputs.version }}"
372
381
 
373
382
  # AI/changelog notes for this version
@@ -395,41 +404,48 @@ jobs:
395
404
  echo "Release v$VERSION" > notes.md
396
405
  fi
397
406
 
398
- echo "release notes assembled ($(wc -c < notes.md) bytes)"
407
+ echo "$(m wf_release.notes_assembled n="$(wc -c < notes.md)")"
399
408
 
400
409
  - name: Create GitHub Release
401
410
  if: steps.gate.outputs.proceed == 'true' && steps.version.outputs.release_exists != 'true'
402
411
  env:
403
- # WORKFLOW_PAT으로 발행해야 release 이벤트가 후속 워크플로우(npm 배포 등)를 트리거한다.
404
- # GITHUB_TOKEN이 만든 이벤트는 GitHub 정책상 다른 워크플로우를 깨우지 못한다.
405
- # PAT이 없는 레포에서도 릴리스 자체는 계속 동작해야 하므로 기본 토큰으로 폴백한다.
412
+ # Publishing with WORKFLOW_PAT lets the release event trigger follow-up workflows
413
+ # (npm publish etc.). Events created by GITHUB_TOKEN never wake other workflows
414
+ # (GitHub policy). Repos without a PAT must still release, so fall back to the
415
+ # default token.
406
416
  GH_TOKEN: ${{ secrets.WORKFLOW_PAT || github.token }}
407
417
  run: |
418
+ m() { python3 "$GITHUB_WORKSPACE/.github/scripts/messages.py" get "$@"; }
408
419
  VERSION="${{ steps.version.outputs.version }}"
409
420
  gh release create "v$VERSION" --title "v$VERSION" --notes-file notes.md
410
- echo "GitHub Release v$VERSION published"
421
+ echo "$(m wf_release.published version="$VERSION")"
411
422
 
412
423
  - name: Trigger README-VERSION-UPDATE
413
- # 봇 토큰(GITHUB_TOKEN)이 만든 push는 README 갱신 워크플로우를 깨우지 못한다 —
414
- # PAT 없는 릴리스 PR 병합, 안전망·trunk-based의 [skip ci] 버전 커밋이 그렇다.
415
- # 릴리스를 새로 발행했을 때 확정된 버전으로 한 번 직접 깨운다 (중복 실행돼도 변경 없으면 커밋 안 함).
424
+ # Pushes made with the bot token (GITHUB_TOKEN) never wake the README update
425
+ # workflow - that covers a release PR merged without a PAT and the [skip ci]
426
+ # version commits of the safety net and trunk-based releases.
427
+ # When a release is newly published, wake it once with the confirmed version
428
+ # (a duplicate run commits nothing if there is no change).
416
429
  if: steps.gate.outputs.proceed == 'true' && steps.version.outputs.release_exists != 'true'
417
430
  env:
418
431
  GH_TOKEN: ${{ github.token }}
419
432
  run: |
433
+ m() { python3 "$GITHUB_WORKSPACE/.github/scripts/messages.py" get "$@"; }
420
434
  if gh workflow run PROJECT-COMMON-README-VERSION-UPDATE.yaml --ref {{MAIN_BRANCH}}; then
421
- echo "README-VERSION-UPDATE workflow_dispatch 트리거 요청 완료"
435
+ echo "$(m wf_release.readme_dispatch_ok)"
422
436
  else
423
- echo "::notice::README-VERSION-UPDATE 워크플로우를 깨우지 못했습니다 (삭제했거나 비활성화됨) — README 버전 갱신을 건너뜁니다"
437
+ echo "::notice::$(m wf_release.readme_dispatch_skip)"
424
438
  fi
425
439
 
426
440
  - name: Trigger deploy workflows
427
- # 봇 토큰(GITHUB_TOKEN)이 병합한 릴리스 PR의 push는 {{MAIN_BRANCH}}의 배포 워크플로우(on: push)를
428
- # 깨우지 못한다 — PAT 없는 기본 설치에서 서버·스토어 배포가 한 번도 돌지 않는다.
429
- # 그런 병합일 때만 배포 워크플로우를 workflow_dispatch로 직접 깨운다. PAT·사람이 병합했다면
430
- # push 이벤트로 이미 실행됐으므로 다시 깨우지 않는다 (중복 배포 방지).
431
- # 대상: 마법사가 설치한 워크플로우 중 push.branches에 {{MAIN_BRANCH}}가 있고 workflow_dispatch가
432
- # 있는 것. 모노레포 paths 필터는 push와 같은 범위(병합 커밋의 변경 파일)로 평가한다.
441
+ # A release PR merged with the bot token (GITHUB_TOKEN) never wakes the
442
+ # {{MAIN_BRANCH}} deploy workflows (on: push) - on a default install without a PAT,
443
+ # server/store deploys would never run. Only for such merges, wake the deploy
444
+ # workflows directly via workflow_dispatch. A PAT or human merge already fired
445
+ # the push event, so they are not woken again (no double deploy).
446
+ # Targets: installed workflows that list {{MAIN_BRANCH}} in push.branches and
447
+ # have workflow_dispatch. Monorepo paths filters are evaluated against the same
448
+ # scope as the push (files changed by the merge commit).
433
449
  if: >-
434
450
  steps.gate.outputs.proceed == 'true' &&
435
451
  steps.version.outputs.release_exists != 'true' &&
@@ -441,33 +457,35 @@ jobs:
441
457
  RELEASE_VERSION: ${{ steps.version.outputs.version }}
442
458
  MAIN_BRANCH: "{{MAIN_BRANCH}}"
443
459
  run: |
444
- # 이 버전의 릴리스 PR 병합 커밋 — 안전망 bump·수동 재발행처럼 병합이 아닌 릴리스는
445
- # 사람의 push가 이미 배포를 깨웠으므로 건너뛴다.
460
+ m() { python3 "$GITHUB_WORKSPACE/.github/scripts/messages.py" get "$@"; }
461
+ # Merge commit of this version's release PR - releases that are not merges
462
+ # (safety-net bump, manual republish) were already deployed by a human push,
463
+ # so they are skipped.
446
464
  MERGE_LINE=$(git log --first-parent --pretty='%H %s' -n 100 | grep -m1 -F " chore(release): v${RELEASE_VERSION} (PR #" || true)
447
465
  if [ -z "$MERGE_LINE" ]; then
448
- echo "v${RELEASE_VERSION}은 릴리스 PR 병합으로 들어온 릴리스가 아닙니다 — 배포는 push 이벤트가 맡습니다"
466
+ echo "$(m wf_release.not_merge_release version="$RELEASE_VERSION")"
449
467
  exit 0
450
468
  fi
451
469
  MERGE_SHA=${MERGE_LINE%% *}
452
470
  PR_NUMBER=$(printf '%s\n' "$MERGE_LINE" | sed -n 's/.*(PR #\([0-9][0-9]*\)).*/\1/p')
453
471
 
454
- # 병합 주체로 push 이벤트가 떴는지 판정한다. 조회에 실패하면 PAT 등록 여부로 대신 판단한다.
472
+ # Decide from the merger whether a push event fired. If the lookup fails, fall back to whether a PAT is registered.
455
473
  MERGED_BY=""
456
474
  if [ -n "$PR_NUMBER" ]; then
457
475
  MERGED_BY=$(gh api "repos/${GITHUB_REPOSITORY}/pulls/${PR_NUMBER}" --jq '.merged_by.login // ""' 2>/dev/null || true)
458
476
  fi
459
477
  case "$MERGED_BY" in
460
- # GITHUB_TOKEN 병합자는 정확히 이 로그인이다 — 접두사만 같은 다른 계정은 사람 병합으로 본다
461
- "github-actions[bot]") echo "PR #${PR_NUMBER}을 봇 토큰이 병합했습니다 — push 이벤트가 배포를 깨우지 못했습니다" ;;
478
+ # A GITHUB_TOKEN merger is exactly this login - another account sharing only the prefix counts as a human merge
479
+ "github-actions[bot]") echo "$(m wf_release.bot_merged pr="$PR_NUMBER")" ;;
462
480
  "")
463
481
  if [ "$HAS_WORKFLOW_PAT" = "true" ]; then
464
- echo "병합 주체를 확인하지 못했고 WORKFLOW_PAT이 있습니다 — push 이벤트가 배포를 실행했다고 보고 건너뜁니다"
482
+ echo "$(m wf_release.merger_unknown_pat)"
465
483
  exit 0
466
484
  fi
467
- echo "병합 주체를 확인하지 못했고 WORKFLOW_PAT이 없습니다 — 봇 토큰 병합으로 봅니다"
485
+ echo "$(m wf_release.merger_unknown_nopat)"
468
486
  ;;
469
487
  *)
470
- echo "PR #${PR_NUMBER}을 ${MERGED_BY}이(가) 병합했습니다 — push 이벤트가 배포를 이미 실행했으므로 건너뜁니다"
488
+ echo "$(m wf_release.human_merged pr="$PR_NUMBER" user="$MERGED_BY")"
471
489
  exit 0
472
490
  ;;
473
491
  esac
@@ -476,7 +494,7 @@ jobs:
476
494
  git diff --name-only "${MERGE_SHA}^1" "$MERGE_SHA" > "$CHANGED_FILES" || rm -f "$CHANGED_FILES"
477
495
  export CHANGED_FILES
478
496
 
479
- # 명령 치환 안의 heredoc은 따옴표 해석이 어긋나므로 파일로 먼저 쓴다
497
+ # A heredoc inside a command substitution misparses quotes, so write it to a file first
480
498
  FIND_DEPLOY="${RUNNER_TEMP:-/tmp}/find_deploy_workflows.py"
481
499
  cat > "$FIND_DEPLOY" <<'PY'
482
500
  import os, re
@@ -488,7 +506,7 @@ jobs:
488
506
  with open(os.environ["CHANGED_FILES"], encoding="utf-8") as f:
489
507
  CHANGED = [l.strip() for l in f if l.strip()]
490
508
 
491
- # YAML 라이브러리 없이 on 블록만 읽는다 — 러너 파이썬에 PyYAML이 있다고 가정하지 않는다.
509
+ # Read only the on block without a YAML library - do not assume the runner's Python has PyYAML.
492
510
  def strip_comment(s):
493
511
  q = None
494
512
  for i, c in enumerate(s):
@@ -504,7 +522,7 @@ jobs:
504
522
  v = v.strip()
505
523
  return v[1:-1] if len(v) >= 2 and v[0] == v[-1] and v[0] in "\"'" else v
506
524
 
507
- # GitHub 필터 문법: ** 는 / 포함 전부, * 와 ? 는 / 를 넘지 않는다. ! 는 뒤 패턴이 앞을 뒤집는다.
525
+ # GitHub filter syntax: ** matches everything including /, * and ? never cross /. A later ! pattern reverses an earlier match.
508
526
  def glob_re(p):
509
527
  out, i = "", 0
510
528
  while i < len(p):
@@ -576,7 +594,7 @@ jobs:
576
594
 
577
595
  root = ".github/workflows"
578
596
  for name in sorted(os.listdir(root)) if os.path.isdir(root) else []:
579
- # 공통 워크플로우(릴리스·README·안전망)는 배포가 아니고, 필요한 것은 각자 따로 깨운다
597
+ # Common workflows (release, README, safety net) are not deploys; each is woken separately where needed
580
598
  if name.startswith("PROJECT-COMMON-") or not re.search(r"\.ya?ml$", name):
581
599
  continue
582
600
  if is_target(os.path.join(root, name)):
@@ -585,18 +603,18 @@ jobs:
585
603
  TARGETS=$(python3 "$FIND_DEPLOY")
586
604
 
587
605
  if [ -z "$TARGETS" ]; then
588
- echo "{{MAIN_BRANCH}} push로 도는 배포 워크플로우가 없습니다 — 건너뜁니다"
606
+ echo "$(m wf_release.no_targets branch="$MAIN_BRANCH")"
589
607
  exit 0
590
608
  fi
591
609
 
592
610
  FAILED=0
593
611
  for WF in $TARGETS; do
594
- # 입력 없이 실행하면 workflow_dispatch 입력 기본값(설치 때 고른 배포 모드 등)을 쓴다
612
+ # Running without inputs uses the workflow_dispatch input defaults (e.g. the deploy mode chosen at install time)
595
613
  if gh workflow run "$WF" --ref {{MAIN_BRANCH}}; then
596
- echo "$WF workflow_dispatch 트리거 요청 완료"
614
+ echo "$(m wf_release.deploy_trigger_ok wf="$WF")"
597
615
  else
598
616
  FAILED=1
599
- echo "::warning::$WF 배포 워크플로우를 깨우지 못했습니다 — Actions 탭에서 Run workflow로 직접 실행하세요"
617
+ echo "::warning::$(m wf_release.deploy_trigger_warn wf="$WF")"
600
618
  fi
601
619
  done
602
620
  exit "$FAILED"