project-auto-wizard 0.13.2 → 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
package/src/ui/summary.js CHANGED
@@ -1,89 +1,90 @@
1
- // 완료 요약 출력. 전부 stderr.
1
+ // Completion summary output. Everything goes to stderr.
2
2
  // ctx: { mode, types:[], version, copiedFiles:[], branches?, gitignoreUpdated?, readme?, scripts? }
3
3
  import { WORKFLOW_PREFIX, WORKFLOW_COMMON_PREFIX } from "../core/paths.js";
4
4
  import { paint, A, colorEnabled } from "./ansi.js";
5
5
  import { EITHER_SEP } from "../core/verify.js";
6
6
  import { BUILD_NUMBER_TYPES } from "../core/types.js";
7
7
  import { SCRIPT_NAMES } from "../core/copy/simple.js";
8
+ import { t } from "../i18n/index.js";
8
9
 
9
10
  const SEPARATOR = "────────────────────────────────────────";
10
11
 
11
12
  export function printSummary(ctx) {
12
13
  const { mode, types = [], version = "", versionCode = null, copiedFiles = [], autoUpdated = [], branches = null, gitignoreUpdated = false,
13
- // pr-flow인데 원격에 develop을 만들지 못한 경우 — 구성 줄만 보면 이미 준비된 것처럼 보이므로 따로 알린다.
14
+ // pr-flow but the develop branch could not be created on the remote - the layout line alone looks ready, so warn separately.
14
15
  developMissing = false,
15
- // 설치 후 검증·기록
16
+ // Post-install verification and record
16
17
  answers = [], unresolved = [], secrets = new Map(), optionalSecrets = new Map(), logPath = "", legacyMdLogs = false, cleanup = null,
17
- // Flutter 스토어 배포 — 앱 파일 생성/유지와 스토어 선택 해제 정리 결과
18
+ // Flutter store deployment - app files created/kept and cleanup of deselected stores
18
19
  flutterApp = null, storeCleanup = null,
19
- // 이번 실행의 실제 결과 — README 버전 섹션 처리 상태(addVersionSectionToReadme 반환값)와 스크립트별 결과.
20
- // 고정 문구로 찍으면 README.md가 없어 아무것도 하지 않은 실행도 "추가됨"으로 보고하게 된다.
20
+ // Actual outcome of this run - README version-section status (return value of addVersionSectionToReadme) and per-script results.
21
+ // Printing fixed text would report "added" even when README.md was missing and nothing was done.
21
22
  readme = null, scripts = null } = ctx || {};
22
23
  const err = (s = "") => process.stderr.write(`${s}\n`);
23
- // 색상은 ansi.js의 공용 가드로 통일 (NO_COLOR + stderr TTY 여부)
24
+ // Color is gated by the shared guard in ansi.js (NO_COLOR + stderr TTY)
24
25
  const enabled = colorEnabled(process.stderr);
25
26
 
26
27
  err("");
27
28
  err(SEPARATOR);
28
29
  err("");
29
- err("✨ project-auto-wizard Setup Complete!");
30
+ err(t("ui.summary.title"));
30
31
  err("");
31
32
  err(SEPARATOR);
32
33
  err("");
33
- err("통합된 기능:");
34
+ err(t("ui.summary.features.title"));
34
35
 
35
- // README 버전 섹션이 실제로 있는 경우(이번에 추가했거나 원래 있던 경우)에만 자동 업데이트를 안내한다.
36
+ // Mention the automatic update only when the README version section really exists (added now or already there).
36
37
  const readmeTracked = readme === "added" || readme === "skip-marker" || readme === "skip-version-line";
37
38
  if (mode === "full") {
38
- err(" ✅ 버전 관리 시스템 (version.yml)");
39
- if (readmeTracked) err(" ✅ README.md 자동 버전 업데이트");
40
- err(" ✅ GitHub Actions 워크플로우 (릴리스 자동화 포함)");
41
- if (gitignoreUpdated) err(" ✅ .gitignore 백업 파일 제외 항목 (*.bak/*.template.yaml)");
39
+ err(t("ui.summary.features.versionSystem"));
40
+ if (readmeTracked) err(t("ui.summary.features.readmeAuto"));
41
+ err(t("ui.summary.features.workflows"));
42
+ if (gitignoreUpdated) err(t("ui.summary.features.gitignore"));
42
43
  }
43
44
 
44
- // 브랜치 모드 + 릴리스 요약 엔진 안내
45
+ // Branch mode + release-summary engine notes
45
46
  if (branches) {
46
47
  err("");
47
- err("브랜치 구성:");
48
+ err(t("ui.summary.branches.title"));
48
49
  if (branches.mode === "trunk-based") {
49
- err(` 🌿 ${branches.main} 단일 브랜치 (trunk-based) — RELEASE-PUBLISH 하나가 버전확정→체인지로그→tag→Release를 순차 처리`);
50
+ err(t("ui.summary.branches.trunk", { main: branches.main }));
50
51
  } else {
51
- err(` 🌿 개발 ${branches.develop} → 릴리스 ${branches.main} (pr-flow) — 릴리스 PR에서 버전확정·체인지로그·automerge`);
52
+ err(t("ui.summary.branches.prFlow", { develop: branches.develop, main: branches.main }));
52
53
  if (developMissing) {
53
- err(` ⚠️ '${branches.develop}' 브랜치가 아직 원격에 없습니다 — 만들기 전에는 개발 브랜치 워크플로우가 동작하지 않습니다`);
54
- err(` → 설치 파일을 커밋해 ${branches.main}을 push한 뒤: git push origin ${branches.main}:${branches.develop}`);
54
+ err(t("ui.summary.branches.developMissing", { develop: branches.develop }));
55
+ err(t("ui.summary.branches.developHint", { main: branches.main, develop: branches.develop }));
55
56
  }
56
57
  }
57
58
  }
58
59
  if (mode === "full" || mode === "workflows") {
59
60
  err("");
60
- err("릴리스 노트 요약 엔진:");
61
- err(" 🤖 AI_API_KEY(선택) → Copilot(선택·AI Credits 소비, 기본 꺼짐) → 규칙 fallback — 릴리스는 절대 막히지 않음");
61
+ err(t("ui.summary.engine.title"));
62
+ err(t("ui.summary.engine.chain"));
62
63
  }
63
64
 
64
65
  err("");
65
- err("추가된 파일:");
66
- err(` 📄 version.yml (버전: ${version}, 타입: ${types.join(",")})`);
67
- if (versionCode != null && types.some((t) => BUILD_NUMBER_TYPES.has(t))) {
68
- err(` 빌드 번호: ${versionCode}`);
66
+ err(t("ui.summary.files.title"));
67
+ err(t("ui.summary.files.versionYml", { version, types: types.join(",") }));
68
+ if (versionCode != null && types.some((ty) => BUILD_NUMBER_TYPES.has(ty))) {
69
+ err(t("ui.summary.files.buildNumber", { code: versionCode }));
69
70
  }
70
- if (readme === "added") err(" 📝 README.md (버전 섹션 추가)");
71
+ if (readme === "added") err(t("ui.summary.files.readmeAdded"));
71
72
  if (readme === "skip-no-readme") {
72
- err(" ℹ️ README.md가 없어 버전 섹션을 추가하지 않았습니다");
73
- err(" → README.md를 만든 뒤 다시 실행하면 추가됩니다 (그 전까지 README 버전 갱신 워크플로우는 건너뜁니다)");
73
+ err(t("ui.summary.files.readmeMissing"));
74
+ err(t("ui.summary.files.readmeMissingHint"));
74
75
  }
75
76
  err("");
76
- err("추가된 워크플로우:");
77
+ err(t("ui.summary.workflows.title"));
77
78
 
78
- // 실제로 이번 실행에서 복사된 파일만 분류한다 (copyWorkflows()가 반환한 copiedFiles —
79
- // 디렉터리 재스캔은 재실행 시 skip된 파일까지 "새로 설치됨"으로 보여주는 결함이 있었다).
80
- // 자동 갱신분(사용자 미수정 파일을 최신으로 교체)도 copiedFiles에 들어 있다 — 새로 설치한 것과 나눠 보여준다.
79
+ // Classify only files actually copied in this run (copiedFiles returned by copyWorkflows() -
80
+ // rescanning the directory would show files skipped on a re-run as "newly installed").
81
+ // Auto-updated files (unmodified files replaced with the latest) are also in copiedFiles - show them apart from new installs.
81
82
  const updated = new Set(autoUpdated);
82
83
  const commonWorkflows = [];
83
84
  const typeWorkflows = [];
84
- const typePrefixes = types.map((t) => `${WORKFLOW_PREFIX}-${t.toUpperCase()}-`);
85
+ const typePrefixes = types.map((ty) => `${WORKFLOW_PREFIX}-${ty.toUpperCase()}-`);
85
86
  for (const filename of copiedFiles) {
86
- if (!filename.startsWith(`${WORKFLOW_PREFIX}-`)) continue; // PROJECT-*만
87
+ if (!filename.startsWith(`${WORKFLOW_PREFIX}-`)) continue; // PROJECT-* only
87
88
  if (updated.has(filename)) continue;
88
89
  if (filename.startsWith(`${WORKFLOW_COMMON_PREFIX}-`)) {
89
90
  commonWorkflows.push(filename);
@@ -93,142 +94,142 @@ export function printSummary(ctx) {
93
94
  }
94
95
 
95
96
  if (commonWorkflows.length > 0 || typeWorkflows.length > 0) {
96
- err(` 📦 새로 설치됨 (${commonWorkflows.length + typeWorkflows.length}개):`);
97
+ err(t("ui.summary.workflows.installed", { count: commonWorkflows.length + typeWorkflows.length }));
97
98
  for (const wf of commonWorkflows) err(` 📌 ${wf}`);
98
99
  for (const wf of typeWorkflows) err(` 🎯 ${wf}`);
99
100
  }
100
101
  if (updated.size > 0) {
101
- err(` 🔄 업데이트됨 (${updated.size}개, 수정하지 않은 파일을 최신으로 교체):`);
102
+ err(t("ui.summary.workflows.updated", { count: updated.size }));
102
103
  for (const wf of updated) err(` • ${wf}`);
103
104
  }
104
105
 
105
106
  err("");
106
107
  err(" 🔧 .github/scripts/");
107
108
  const scriptRows = scripts
108
- ? scripts.map(({ name, action }) => (action === "overwrite" ? `${name} ${paint("(기존 파일을 새 버전으로 덮어씀)", A.dim, enabled)}` : name))
109
+ ? scripts.map(({ name, action }) => (action === "overwrite" ? `${name} ${paint(t("ui.summary.scripts.overwritten"), A.dim, enabled)}` : name))
109
110
  : SCRIPT_NAMES;
110
111
  scriptRows.forEach((row, i) => err(` ${i === scriptRows.length - 1 ? "└─" : "├─"} ${row}`));
111
112
  err("");
112
113
 
113
- // 입력한 환경설정 값 — 마지막으로 눈으로 검산할 기회. 종전에는 답변이 워크플로우
114
- // YAML 안으로만 사라져, 오타를 내도 배포가 실패한 뒤에야 알 수 있었다.
114
+ // Environment values the user entered - a last chance to eyeball them. Answers used to vanish
115
+ // into the workflow YAML, so a typo only surfaced after a deploy failed.
115
116
  if (answers.length) {
116
- err(" ⚙️ 적용된 환경설정:");
117
+ err(t("ui.summary.answers.title"));
117
118
  for (const a of answers) {
118
- const mark = a.isDefault ? paint(" (기본값)", A.dim, enabled) : "";
119
+ const mark = a.isDefault ? paint(t("ui.summary.answers.default"), A.dim, enabled) : "";
119
120
  err(` • ${a.label}: ${paint(a.value, A.green, enabled)}${mark}`);
120
121
  }
121
122
  err("");
122
123
  }
123
- // 배포 방식을 바꿔 재설치한 경우, 이전 CD를 어떻게 처리했는지 알린다.
124
- printCleanup(err, enabled, "이전 배포 방식 정리", cleanup);
125
- // 스토어 배포 대상을 해제한 경우도 같은 규칙으로 정리한 결과를 알린다.
126
- printCleanup(err, enabled, "선택 해제한 스토어 배포 정리", storeCleanup);
127
- // Fastfile·ExportOptions.plist는 사용자 소유라 없을 때만 만든다 — 만든 것과 그대로 둔 것을 나눠 보여준다.
124
+ // When re-installed with a different deploy style, report how the previous CD was handled.
125
+ printCleanup(err, enabled, t("ui.summary.cleanup.previousDeploy"), cleanup);
126
+ // Same reporting when a store deploy target was deselected.
127
+ printCleanup(err, enabled, t("ui.summary.cleanup.deselectedStores"), storeCleanup);
128
+ // Fastfile and ExportOptions.plist are user-owned and only created when missing - show what was created vs. left alone.
128
129
  const { created: appCreated = [], kept: appKept = [] } = flutterApp || {};
129
130
  if (appCreated.length || appKept.length) {
130
- err(" 📱 Flutter 스토어 배포 파일:");
131
- for (const f of appCreated) err(` • ${f} ${paint("새로 생성", A.dim, enabled)}`);
132
- for (const f of appKept) err(` • ${f} ${paint("기존 파일 유지", A.dim, enabled)}`);
131
+ err(t("ui.summary.flutterApp.title"));
132
+ for (const f of appCreated) err(` • ${f} ${paint(t("ui.summary.flutterApp.created"), A.dim, enabled)}`);
133
+ for (const f of appKept) err(` • ${f} ${paint(t("ui.summary.flutterApp.kept"), A.dim, enabled)}`);
133
134
  if (appCreated.some((f) => f.endsWith("ExportOptions.plist"))) {
134
- err(" → ExportOptions.plist의 __TEAM_ID__ · __BUNDLE_ID__ · __PROVISIONING_PROFILE_NAME__ 을 실제 값으로 채워야 iOS 배포가 동작합니다");
135
+ err(t("ui.summary.flutterApp.plistHint"));
135
136
  }
136
137
  err("");
137
138
  }
138
139
  if (logPath) {
139
- err(` 📋 실행 로그: ${logPath}`);
140
- err(" → 무엇을 어떤 값으로 설치했는지 시간순으로 남아 있습니다 (git에 올라가지 않습니다)");
140
+ err(t("ui.summary.log.path", { path: logPath }));
141
+ err(t("ui.summary.log.hint"));
141
142
  err("");
142
143
  }
143
144
  if (legacyMdLogs) {
144
- err(" ℹ️ 이전 버전의 설치 기록(.md)이 git에 추적 중입니다:");
145
+ err(t("ui.summary.legacyLogs"));
145
146
  err(" git rm -r --cached .github/.wizard/logs");
146
147
  err("");
147
148
  }
148
149
 
149
- // 프로젝트 타입별 안내
150
+ // Per-type notes
150
151
  if (types.includes("spring")) {
151
- err(" 💡 Spring 프로젝트 추가 설정:");
152
- err(" • build.gradle / build.gradle.kts / pom.xml 의 버전 정보가 자동 동기화됩니다");
152
+ err(t("ui.summary.spring.title"));
153
+ err(t("ui.summary.spring.sync"));
153
154
  err("");
154
155
  }
155
156
 
156
157
  err(" 📖 REPO: https://github.com/Twin-Fang/project-auto-wizard");
157
158
  err("");
158
159
 
159
- // 필수 작업 안내
160
+ // Required follow-up actions
160
161
  err(SEPARATOR);
161
162
  err("");
162
- err(paint(paint("⚠️ 다음 작업을 확인해주세요:", A.yellow, enabled), A.bold, enabled));
163
+ err(paint(paint(t("ui.summary.todo.title"), A.yellow, enabled), A.bold, enabled));
163
164
  err("");
164
165
 
165
166
  let step = 0;
166
167
  const num = () => ["1️⃣ ", "2️⃣ ", "3️⃣ ", "4️⃣ ", "5️⃣ "][step++] || " •";
167
168
 
168
- // 미치환 플레이스홀더 — 이 상태로는 해당 워크플로우가 동작하지 않으므로 제일 먼저 알린다.
169
+ // Unresolved placeholders - the affected workflow will not work as is, so report them first.
169
170
  if (unresolved.length) {
170
- err(` ${num()} ${paint("값이 채워지지 않은 항목이 있습니다 — 직접 채워야 동작합니다", A.red, enabled)}`);
171
+ err(` ${num()} ${paint(t("ui.summary.todo.unresolved"), A.red, enabled)}`);
171
172
  for (const u of unresolved) {
172
173
  err(` → ${u.filename}:${u.line} ${paint(u.token, A.bold, enabled)}`);
173
174
  }
174
175
  err("");
175
176
  }
176
177
 
177
- // 설치된 워크플로우가 실제로 요구하는 Secret — 종전에는 하나도 안내되지 않아
178
- // "설치 성공"인데 배포는 돌지 않는 상태로 끝났다.
179
- // "A 또는 B"는 둘 중 하나만 등록하면 되는 폴백 쌍이라 한 항목으로 센다.
178
+ // Secrets the installed workflows actually require - none used to be listed, so an install
179
+ // "succeeded" yet deploys never ran.
180
+ // "A or B" is a fallback pair where registering either one is enough, so it counts as one item.
180
181
  if (secrets.size) {
181
- err(` ${num()} 아래 GitHub Secret을 등록해야 배포 워크플로우가 동작합니다 (${secrets.size}개)`);
182
- err(" → Settings > Secrets and variables > Actions");
182
+ err(` ${num()} ${t("ui.summary.todo.secrets", { count: secrets.size })}`);
183
+ err(t("ui.summary.todo.secretsPath"));
183
184
  for (const [name, users] of secrets) {
184
- const either = name.includes(EITHER_SEP) ? paint(" (둘 중 하나)", A.dim, enabled) : "";
185
+ const either = name.includes(EITHER_SEP) ? paint(t("ui.summary.todo.either"), A.dim, enabled) : "";
185
186
  err(` → ${paint(name, A.bold, enabled)}${either} ${paint(users.join(", "), A.dim, enabled)}`);
186
187
  }
187
188
  err("");
188
189
  }
189
- // 기본값이 있거나 없어도 동작하는 secret — 필수 개수에 섞지 않고 따로 알린다.
190
+ // Secrets that have a default or work without them - listed separately from the required count.
190
191
  if (optionalSecrets.size) {
191
- err(` ${paint("ℹ️", A.dim, enabled)} 선택 Secret (없어도 동작합니다 — 필요할 때 등록, ${optionalSecrets.size}개)`);
192
+ err(` ${paint("ℹ️", A.dim, enabled)} ${t("ui.summary.todo.optionalSecrets", { count: optionalSecrets.size })}`);
192
193
  for (const [name, users] of optionalSecrets) {
193
194
  err(` · ${name} ${paint(users.join(", "), A.dim, enabled)}`);
194
195
  }
195
196
  err("");
196
197
  }
197
198
 
198
- // 설치 파일은 아직 커밋 전이다. develop을 이번 실행에서 만들었다면 설치 전 커밋 기준이라 워크플로우가 없다 —
199
- // 한쪽 브랜치에만 커밋하면 다른 쪽에는 VERSION-CONTROL 등이 빠진 채로 남는다.
199
+ // Installed files are not committed yet. If develop was created in this run it is based on the pre-install commit and has no workflows -
200
+ // committing to only one branch leaves the other without VERSION-CONTROL etc.
200
201
  if (branches) {
201
- err(` ${num()} 설치된 파일을 커밋해 ${branches.main}에 push하세요`);
202
+ err(` ${num()} ${t("ui.summary.todo.commit", { main: branches.main })}`);
202
203
  if (branches.mode !== "trunk-based") {
203
- err(` → ${branches.develop}에도 같은 파일이 있어야 합니다 — 한쪽에 커밋한 뒤 다른 쪽에 병합하세요`);
204
- err(` 예) git checkout ${branches.develop} && git merge ${branches.main} && git push`);
204
+ err(t("ui.summary.todo.commitDevelop", { develop: branches.develop }));
205
+ err(t("ui.summary.todo.commitExample", { develop: branches.develop, main: branches.main }));
205
206
  }
206
- err(" → README의 '전체 버전 기록 보기'(CHANGELOG.md) 링크는 첫 릴리스에서 CHANGELOG가 생성된 뒤부터 열립니다");
207
+ err(t("ui.summary.todo.changelogLink"));
207
208
  err("");
208
209
  }
209
210
 
210
- err(` ${num()} 릴리스 automerge용 PAT (선택 — 없으면 GITHUB_TOKEN 폴백으로 자동 진행)`);
211
- err(" → Repository Settings > Secrets > Actions");
212
- err(" → Secret Name: WORKFLOW_PAT (Scopes: repo, workflow)");
213
- err(" → 등록 시 개인 계정이 아닌 조직 bot/machine 계정으로 발급하세요");
214
- err(" → 없어도 태그·Release 발행과 배포 워크플로우 실행까지 자동으로 이어지며, 있으면 조금 더 빠릅니다");
211
+ err(` ${num()} ${t("ui.summary.todo.pat")}`);
212
+ err(t("ui.summary.todo.patPath"));
213
+ err(t("ui.summary.todo.patName"));
214
+ err(t("ui.summary.todo.patAccount"));
215
+ err(t("ui.summary.todo.patOptional"));
215
216
  err("");
216
- // 설치 워크플로우는 필요한 권한을 각자 선언한다 — doctor 안내와 같은 기준으로 알린다.
217
- err(` ${num()} GitHub Actions 권한 (변경 불필요)`);
218
- err(" → Workflow permissions가 기본값 Read여도 설치된 워크플로우는 그대로 동작합니다");
219
- err(" → 직접 추가한 워크플로우가 permissions 선언 없이 쓰기 작업을 할 때만 Read and write로 올리세요");
217
+ // Installed workflows declare the permissions they need themselves - report it by the same rule as the doctor guidance.
218
+ err(` ${num()} ${t("ui.summary.todo.permissions")}`);
219
+ err(t("ui.summary.todo.permissionsRead"));
220
+ err(t("ui.summary.todo.permissionsWrite"));
220
221
  err("");
221
222
  err(SEPARATOR);
222
223
  err("");
223
- err(paint("📖 워크플로우 구성과 릴리스 흐름은 README를 참고하세요.", A.cyan, enabled));
224
+ err(paint(t("ui.summary.footer"), A.cyan, enabled));
224
225
  err("");
225
226
  }
226
227
 
227
- // 삭제·백업 정리 결과 한 블록 — 이전 배포 방식 정리와 스토어 선택 해제 정리가 같은 형식을 쓴다.
228
+ // One block of removal/backup cleanup results - previous-deploy cleanup and deselected-store cleanup share the format.
228
229
  function printCleanup(err, enabled, title, cleanup) {
229
230
  if (!cleanup?.removed?.length && !cleanup?.backedUp?.length) return;
230
231
  err(` 🧹 ${title}:`);
231
- for (const f of cleanup.removed || []) err(` • ${f} ${paint("삭제 (손대지 않은 파일)", A.dim, enabled)}`);
232
- for (const f of cleanup.backedUp || []) err(` • ${f} → ${f}.bak ${paint("수정하신 내용이 있어 백업", A.dim, enabled)}`);
232
+ for (const f of cleanup.removed || []) err(` • ${f} ${paint(t("ui.summary.cleanup.removed"), A.dim, enabled)}`);
233
+ for (const f of cleanup.backedUp || []) err(` • ${f} → ${f}.bak ${paint(t("ui.summary.cleanup.backedUp"), A.dim, enabled)}`);
233
234
  err("");
234
235
  }