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
package/src/ui/prompts.js CHANGED
@@ -1,236 +1,211 @@
1
- // 대화형 프롬프트 래핑.
2
- // node:readline 기반 자체 엔진 사용 (@clack/prompts 는 Windows TTY에서 Enter가 멈추는 버그로 제거).
3
- // ESC는 각 함수가 CANCEL 심볼을 반환 → 호출부가 기본값/머무르기로 해석한다.
4
- // Ctrl+C·Ctrl+D는 엔진이 PromptAbortError로 reject → run()이 잡아 종료코드 130으로 끝낸다.
1
+ // Interactive prompt wrappers.
2
+ // Uses the in-house node:readline engine (@clack/prompts was removed because Enter hangs on Windows TTY).
3
+ // On ESC each function returns the CANCEL symbol -> the caller interprets it as default/stay.
4
+ // Ctrl+C / Ctrl+D make the engine reject with PromptAbortError -> run() catches it and exits with code 130.
5
5
  import * as engine from "./readline-engine.js";
6
+ import { t } from "../i18n/index.js";
6
7
  import { DEPLOY_STYLES, NO_DEPLOY_STYLE } from "../core/deploy-style.js";
7
8
  import { TYPE_IDS } from "../core/types.js";
8
9
  import { ENV_MODES, DEFAULT_ENV_MODE, STORE_PLATFORMS, DEPLOY_MODES, DEFAULT_DEPLOY_MODE, deployModeWarning } from "../core/flutter-options.js";
9
10
 
10
11
  export const CANCEL = engine.CANCEL;
11
12
 
12
- // 모드 선택 — 한국어 라벨, 내부 키 반환. 취소 시 CANCEL.
13
- // again=true는 읽기 전용 모드(status/doctor)를 실행하고 메뉴로 돌아온 재진입이다 —
14
- // 이때 "무엇을 설치할까요?"를 다시 묻는 건 어색하므로 문구를 바꾼다.
13
+ // Mode selection - localized labels, returns the internal key. CANCEL on cancel.
14
+ // again=true is a re-entry after running a read-only mode (status/doctor) and returning to the menu -
15
+ // asking "what should we install?" again would feel odd, so the wording changes.
15
16
  export async function selectMode({ again = false } = {}) {
16
17
  return engine.select({
17
- message: again ? "다음으로 무엇을 할까요?" : "무엇을 설치할까요?",
18
+ message: again ? t("ui.prompts.mode.messageAgain") : t("ui.prompts.mode.message"),
18
19
  options: [
19
- { value: "full", label: "설치 / 업데이트 — 버전관리 + 자동화 워크플로우 (처음이라면 추천)" },
20
- { value: "uninstall", label: "완전 삭제 — 마법사가 설치·수정한 모든 항목 제거(확인 후, README·gitignore·version.yml 포함)" },
21
- { value: "status", label: "설치 상태 확인 — 읽기 전용, 버전·타입·드리프트 확인" },
22
- { value: "doctor", label: "환경 진단 — 읽기 전용, gh CLI·권한·secret 설정 점검" },
20
+ { value: "full", label: t("ui.prompts.mode.full") },
21
+ { value: "uninstall", label: t("ui.prompts.mode.uninstall") },
22
+ { value: "status", label: t("ui.prompts.mode.status") },
23
+ { value: "doctor", label: t("ui.prompts.mode.doctor") },
23
24
  ],
24
25
  });
25
26
  }
26
27
 
27
- // 프로젝트 확인 화면 메뉴 (계속/수정/취소).
28
+ // Project confirmation menu (continue/edit/cancel).
28
29
  export async function confirmProjectMenu() {
29
30
  return engine.select({
30
- message: "이 정보로 진행할까요?",
31
+ message: t("ui.prompts.confirm.message"),
31
32
  options: [
32
- { value: "continue", label: "예, 계속 진행" },
33
- { value: "edit", label: "수정하기" },
34
- { value: "cancel", label: "아니오, 취소" },
33
+ { value: "continue", label: t("ui.prompts.confirm.continue") },
34
+ { value: "edit", label: t("ui.prompts.confirm.edit") },
35
+ { value: "cancel", label: t("ui.prompts.confirm.cancel") },
35
36
  ],
36
37
  });
37
38
  }
38
39
 
39
- // 수정 메뉴 항목 — showFlutter=Flutter 타입일 때만 환경변수 방식/스토어 배포 대상/배포 모드 노출.
40
- // 라벨·순서를 테스트할 수 있도록 순수 함수로 분리했다.
41
- // showOptions=선택 워크플로우 토글(자동 버전 승격·Copilot) 노출 — 저장값이 있으면 처음 질문을 건너뛰므로 여기서 바꾼다.
40
+ // Edit menu items - showFlutter exposes env mode / store targets / deploy mode only for the Flutter type.
41
+ // Split into a pure function so labels and order can be tested.
42
+ // showOptions exposes the optional-workflow toggles (auto version bump, Copilot) - a saved value skips the first question, so it is changed here.
42
43
  export function editMenuOptions({ showFlutter = false, showOptions = false } = {}) {
43
44
  const options = [
44
- { value: "type", label: "프로젝트 타입" },
45
- { value: "version", label: "버전" },
46
- { value: "branch", label: "기본 브랜치" },
45
+ { value: "type", label: t("ui.prompts.edit.type") },
46
+ { value: "version", label: t("ui.prompts.edit.version") },
47
+ { value: "branch", label: t("ui.prompts.edit.branch") },
47
48
  ];
48
49
  if (showOptions) {
49
- options.push({ value: "semverAuto", label: "자동 버전 승격" });
50
- options.push({ value: "copilotAi", label: "Copilot AI 요약" });
50
+ options.push({ value: "semverAuto", label: t("ui.prompts.edit.semverAuto") });
51
+ options.push({ value: "copilotAi", label: t("ui.prompts.edit.copilotAi") });
51
52
  }
52
53
  if (showFlutter) {
53
- options.push({ value: "envMode", label: "환경변수 방식" });
54
- options.push({ value: "flutterStore", label: "스토어 배포 대상" });
55
- options.push({ value: "deployMode", label: "배포 모드" });
54
+ options.push({ value: "envMode", label: t("ui.prompts.edit.envMode") });
55
+ options.push({ value: "flutterStore", label: t("ui.prompts.edit.flutterStore") });
56
+ options.push({ value: "deployMode", label: t("ui.prompts.edit.deployMode") });
56
57
  }
57
- options.push({ value: "done", label: "모두 맞음, 계속" });
58
+ options.push({ value: "done", label: t("ui.prompts.edit.done") });
58
59
  return options;
59
60
  }
60
61
 
61
- // 수정 메뉴 — 어떤 항목을 고칠지.
62
+ // Edit menu - which item to fix.
62
63
  export async function editMenu({ showFlutter = false, showOptions = false } = {}) {
63
- return engine.select({ message: "어떤 항목을 수정할까요?", options: editMenuOptions({ showFlutter, showOptions }) });
64
+ return engine.select({ message: t("ui.prompts.edit.message"), options: editMenuOptions({ showFlutter, showOptions }) });
64
65
  }
65
66
 
66
- // 대화형 타입 선택지 — CLI 검증 목록(VALID_TYPES)과 같은 레지스트리에서 만든다.
67
+ // Interactive type choices - built from the same registry as the CLI validation list (VALID_TYPES).
67
68
  export const ALL_TYPES = TYPE_IDS;
68
69
 
69
- // 타입 멀티선택.
70
+ // Type multi-select.
70
71
  export async function selectTypes(current = []) {
71
72
  return engine.multiselect({
72
- message: "프로젝트 타입을 선택하세요 (Space 토글, Enter 확정)",
73
- options: ALL_TYPES.map((t) => ({ value: t, label: t })),
73
+ message: t("ui.prompts.types.select"),
74
+ options: ALL_TYPES.map((type) => ({ value: type, label: type })),
74
75
  initialValues: current.length ? current : ["basic"],
75
76
  required: true,
76
77
  });
77
78
  }
78
79
 
79
- // 감지 직후 타입 확정. selectTypes와 달리 감지 근거 파일을 라벨에 붙여
80
- // "왜 이렇게 판단했는지"를 보여준다 — 근거가 보여야 맞는지 틀린지 판단할 수 있다.
81
- // 감지 결과가 맞으면 Enter 한 번으로 끝난다.
80
+ // Confirm types right after detection. Unlike selectTypes it appends the evidence file to the label
81
+ // to show "why it was decided this way" - the user can only judge right or wrong when the evidence is visible.
82
+ // If detection is right, a single Enter finishes it.
82
83
  export async function confirmTypes({ types = [], markers = null } = {}) {
83
84
  const detected = new Set(types);
84
85
  engine.note(
85
- "선택한 타입에 따라 설치되는 CI/CD 워크플로우와 버전 동기화 대상 파일이 달라집니다.\n" +
86
- "감지 결과가 맞으면 그대로 Enter를 누르세요.",
87
- "프로젝트 타입 확정",
86
+ t("ui.prompts.types.confirmNote"),
87
+ t("ui.prompts.types.confirmTitle"),
88
88
  );
89
89
  return engine.multiselect({
90
- message: "이 프로젝트의 타입입니다 (Space 토글, Enter 확정)",
91
- options: ALL_TYPES.map((t) => {
92
- const marker = markers?.get?.(t);
93
- // 감지된 타입만 근거를 붙인다 — 나머지는 후보로만 나열한다.
94
- return { value: t, label: detected.has(t) && marker ? `${t} — ${marker} 발견` : t };
90
+ message: t("ui.prompts.types.confirmMessage"),
91
+ options: ALL_TYPES.map((type) => {
92
+ const marker = markers?.get?.(type);
93
+ // Only detected types get evidence - the rest are just listed as candidates.
94
+ return { value: type, label: detected.has(type) && marker ? t("ui.prompts.types.withMarker", { type, marker }) : type };
95
95
  }),
96
96
  initialValues: types.length ? types : ["basic"],
97
97
  required: true,
98
98
  });
99
99
  }
100
100
 
101
- // 배포 방식 선택. 서버 배포 CD 워크플로우는 서로 대체재라 하나만 쓴다.
102
- // 고른 것만 설치하고 push 트리거까지 켜준다 — 종전에는 넷을 다 깔고 SIMPLE만 켜져 있어,
103
- // 무중단을 원한 사람은 설치 후 YAML을 직접 고쳐야 했다.
104
- // "서버 배포 안 함"은 모든 타입에서 서버 배포 워크플로우(CD·PR 프리뷰)를 제외한다 — 서버 배포를
105
- // 하지 않는 프로젝트(프론트엔드 전용, 라이브러리 등)를 위한 선택지다.
106
- // nonstop=false면(선택한 타입 어디에도 무중단 워크플로우가 없음) 고를 수 없는 무중단 선택지를 빼고 묻는다.
101
+ // Deploy style selection. Server-deploy CD workflows are alternatives to each other, so only one is used.
102
+ // Only the chosen one is installed and its push trigger is enabled - previously all four were installed with only SIMPLE on,
103
+ // so anyone wanting zero-downtime had to edit the YAML by hand after install.
104
+ // "No server deploy" excludes server-deploy workflows (CD / PR preview) for every type - a choice for projects
105
+ // that do not deploy to a server (frontend-only, libraries, ...).
106
+ // With nonstop=false (no selected type has a zero-downtime workflow) the unusable zero-downtime choices are left out.
107
107
  export async function selectDeployStyle({ nonstop = true } = {}) {
108
108
  engine.note(
109
- "서버 배포 워크플로우는 서로 대체재입니다 (Nginx와 Traefik을 동시에 쓰지 않습니다).\n" +
110
- "고른 방식만 설치하고 자동 실행(push 트리거)까지 켭니다. PR 프리뷰는 배포 방식과 함께 설치되고,\n" +
111
- "\"서버 배포 안 함\"을 고르면 모든 타입에서 CD와 PR 프리뷰를 함께 제외합니다.\n" +
112
- "무중단 배포는 spring에만 있습니다 — 다른 타입(python·go·react·next)은 단일 서버 배포로 설치됩니다.",
113
- "배포 방식",
109
+ t("ui.prompts.deployStyle.note"),
110
+ t("ui.prompts.deployStyle.title"),
114
111
  );
115
112
  return engine.select({
116
- message: "서버 배포는 어떤 방식으로 할까요?",
113
+ message: t("ui.prompts.deployStyle.message"),
117
114
  options: [
118
- ...DEPLOY_STYLES.filter((s) => nonstop || s.value === "simple").map((s) => ({ value: s.value, label: s.label })),
119
- { value: NO_DEPLOY_STYLE, label: "서버 배포 안 함 — CD·PR 프리뷰 워크플로우와 배포 설정을 생성하지 않음" },
115
+ // label may be a plain string or a lazy function depending on the registry
116
+ ...DEPLOY_STYLES.filter((s) => nonstop || s.value === "simple").map((s) => ({ value: s.value, label: typeof s.label === "function" ? s.label() : s.label })),
117
+ { value: NO_DEPLOY_STYLE, label: t("ui.prompts.deployStyle.none") },
120
118
  ],
121
119
  });
122
120
  }
123
121
 
124
- // ── Flutter 옵션 ─────────────────────────────────────────
125
- // 프로젝트 타입에 flutter가 포함된 경우에만 interactive.js가 묻는다. 세 함수 모두 취소(ESC) 시 CANCEL을
126
- // 그대로 돌려주고, "ESC = 기본값" 처리는 호출부가 한다 (selectDeployStyle과 같은 규약).
127
- const ENV_MODE_LABELS = {
128
- "dart-define": "dart-define (신규 설치 기본) — 시크릿을 --dart-define-from-file로 빌드에 전달, 프로젝트에 .env를 만들지 않음",
129
- dotenv: "dotenv — Flutter 루트에 .env를 만들어 flutter_dotenv·envied가 읽음 (기존 설치 유지값)",
130
- };
131
-
132
- const STORE_LABELS = {
133
- android: "Android — Google Play Store (fastlane)",
134
- ios: "iOS — TestFlight / App Store Connect (fastlane)",
135
- };
136
-
137
- const DEPLOY_MODE_LABELS = {
138
- android: {
139
- store_only: "store_only — internal 트랙에 업로드 (기본)",
140
- store_prepare: "store_prepare — production 트랙에 draft로 업로드 (Play Console에서 직접 출시)",
141
- store_submit: "store_submit — production 심사 제출까지",
142
- },
143
- ios: {
144
- store_only: "store_only — TestFlight 업로드 (기본)",
145
- store_prepare: "store_prepare — 앱 버전·메타데이터 준비까지 (심사 제출 안 함)",
146
- store_submit: "store_submit — 심사 제출까지",
147
- },
148
- };
149
-
150
- const PLATFORM_TITLES = { android: "Android (Play Store)", ios: "iOS (TestFlight)" };
122
+ // ── Flutter options ──────────────────────────────────────
123
+ // interactive.js asks these only when the project types include flutter. All three functions return CANCEL
124
+ // as is on cancel (ESC); the "ESC = default" handling is up to the caller (same convention as selectDeployStyle).
125
+ // Labels are resolved lazily via t() so they follow the language chosen at run time.
126
+ const envModeLabel = (value) => t(`ui.prompts.envMode.label.${value}`);
127
+ const storeLabel = (value) => t(`ui.prompts.stores.label.${value}`);
128
+ const deployModeLabel = (platform, value) => t(`ui.prompts.deployMode.label.${platform}.${value}`);
129
+ const platformTitle = (platform) => t(`ui.prompts.deployMode.platform.${platform}`);
151
130
 
152
- // 환경변수 방식 — 시크릿 ENV_FILE(.env 형식)을 Flutter 빌드에 넘기는 방법.
131
+ // Env mode - how the secret ENV_FILE (.env format) is passed to the Flutter build.
153
132
  export async function selectEnvMode({ initialValue = DEFAULT_ENV_MODE } = {}) {
154
133
  engine.note(
155
- "시크릿 ENV_FILE(.env 형식)을 Flutter 빌드에 넘기는 방식입니다.\n" +
156
- "dart-define은 String.fromEnvironment로 읽고, dotenv는 flutter_dotenv·envied가 .env 파일을 읽습니다.\n" +
157
- "나중에 확인 화면의 '수정하기 > 환경변수 방식'에서 바꿀 수 있습니다.",
158
- "환경변수 방식",
134
+ t("ui.prompts.envMode.note"),
135
+ t("ui.prompts.envMode.title"),
159
136
  );
160
137
  return engine.select({
161
- message: "Flutter 환경변수는 어떤 방식으로 넘길까요?",
162
- options: ENV_MODES.map((value) => ({ value, label: ENV_MODE_LABELS[value] })),
138
+ message: t("ui.prompts.envMode.message"),
139
+ options: ENV_MODES.map((value) => ({ value, label: envModeLabel(value) })),
163
140
  initialIndex: Math.max(0, ENV_MODES.indexOf(initialValue)),
164
141
  });
165
142
  }
166
143
 
167
- // 스토어 배포 대상 — 고른 플랫폼의 워크플로우와 fastlane 템플릿만 설치한다. 아무것도 안 고르면 스토어 배포 없이 설치.
144
+ // Store deploy targets - installs only the workflows and fastlane templates of the chosen platforms. Choosing none installs without store deployment.
168
145
  export async function selectFlutterStores({ initialValues = [] } = {}) {
169
146
  engine.note(
170
- "고른 플랫폼의 스토어 배포 워크플로우와 fastlane 파일(Fastfile 등)만 설치합니다.\n" +
171
- "아무것도 고르지 않으면 스토어 배포 없이 설치합니다 (Firebase·Selfhosted·Test APK·CI는 항상 설치).",
172
- "스토어 배포 대상",
147
+ t("ui.prompts.stores.note"),
148
+ t("ui.prompts.stores.title"),
173
149
  );
174
150
  return engine.multiselect({
175
- message: "스토어에 배포할 플랫폼을 선택하세요 (Space 토글, Enter 확정)",
176
- options: STORE_PLATFORMS.map((value) => ({ value, label: STORE_LABELS[value] })),
151
+ message: t("ui.prompts.stores.message"),
152
+ options: STORE_PLATFORMS.map((value) => ({ value, label: storeLabel(value) })),
177
153
  initialValues,
178
154
  required: false,
179
155
  });
180
156
  }
181
157
 
182
- // 배포 모드 — 플랫폼별로 한 번씩 묻는다. 런타임의 저장소 변수와 workflow_dispatch 입력이 항상 이 값보다 우선한다.
158
+ // Deploy mode - asked once per platform. The runtime repository variable and workflow_dispatch input always take precedence over this value.
183
159
  export async function selectDeployMode({ platform, initialValue = DEFAULT_DEPLOY_MODE }) {
184
160
  return engine.select({
185
- message: `${PLATFORM_TITLES[platform]} 배포 모드를 선택하세요`,
186
- options: DEPLOY_MODES.map((value) => ({ value, label: DEPLOY_MODE_LABELS[platform][value] })),
161
+ message: t("ui.prompts.deployMode.message", { platform: platformTitle(platform) }),
162
+ options: DEPLOY_MODES.map((value) => ({ value, label: deployModeLabel(platform, value) })),
187
163
  initialIndex: Math.max(0, DEPLOY_MODES.indexOf(initialValue)),
188
164
  });
189
165
  }
190
166
 
191
- // store_submit 경고 문구 — 정의는 core에 있고, 호출부(interactive)가 note로 출력한다.
167
+ // store_submit warning text - defined in core; the caller (interactive) prints it via note.
192
168
  export { deployModeWarning };
193
169
 
194
- // 브랜치 전략 선택. 종전에는 "릴리스 브랜치"/"개발 브랜치" 두 질문에
195
- // 같은 이름을 입력해야만 trunk-based가 됐는데, 그 규칙이 사전에 안내되지 않아
196
- // 의도치 않게 pr-flow로 흘러갔다. 전략을 먼저 명시적으로 고르게 해 이를 없앤다.
197
- // 옵션 순서(pr-flow 먼저)는 비-TTY 환경의 기본값과 직결되므로 바꾸지 않는다.
170
+ // Branch strategy selection. Previously trunk-based only happened when the same name was entered for both
171
+ // the "release branch" and "development branch" questions; that rule was never announced, so users
172
+ // drifted into pr-flow unintentionally. Choosing the strategy explicitly first removes that.
173
+ // Do not reorder the options (pr-flow first) - the order determines the default in non-TTY environments.
198
174
  export async function selectBranchStrategy() {
199
175
  engine.note(
200
- "pr-flow는 develop에서 작업해 main으로 PR을 올리는 팀 협업 흐름입니다.\n" +
201
- "trunk-based는 브랜치를 하나만 두고 그 브랜치에서 바로 작업하는 단순한 흐름입니다.",
202
- "브랜치 전략",
176
+ t("ui.prompts.branch.note"),
177
+ t("ui.prompts.branch.title"),
203
178
  );
204
179
  return engine.select({
205
- message: "브랜치 전략을 선택하세요",
180
+ message: t("ui.prompts.branch.message"),
206
181
  options: [
207
- { value: "pr-flow", label: "develop → main PR 흐름 (pr-flow) — 팀 협업/리뷰 프로세스가 필요할 때" },
208
- { value: "trunk-based", label: "main 단일 브랜치 (trunk-based) — 개인/소규모 프로젝트로 브랜치 없이 단순하게 쓸 때" },
182
+ { value: "pr-flow", label: t("ui.prompts.branch.prFlow") },
183
+ { value: "trunk-based", label: t("ui.prompts.branch.trunk") },
209
184
  ],
210
185
  });
211
186
  }
212
187
 
213
- // 텍스트 입력 (빈 입력=기본값 유지).
188
+ // Text input (empty input keeps the default).
214
189
  export async function askText(message, defaultValue = "") {
215
190
  const v = await engine.text({ message, defaultValue });
216
191
  if (v === CANCEL) return CANCEL;
217
192
  return v === "" || v == null ? defaultValue : v;
218
193
  }
219
194
 
220
- // 예/아니오.
195
+ // Yes/no.
221
196
  export async function askYesNo(message, initial = true) {
222
197
  return engine.confirm({ message, initialValue: initial });
223
198
  }
224
199
 
225
- // 배너·안내 출력.
200
+ // Banner / notice output.
226
201
  export function intro(text) { engine.intro(text); }
227
202
  export function outro(text) { engine.outro(text); }
228
203
  export function note(text, title) { engine.note(text, title); }
229
- export function cancelMessage(text = "취소했습니다.") { engine.cancelMessage(text); }
204
+ export function cancelMessage(text = t("ui.readline-engine.cancelled")) { engine.cancelMessage(text); }
230
205
 
231
- // ── 첫 화면 UI 5층 + 대화형 계층 실물 io ─────────────────
232
- // runInteractive는 io.<method>?.() 옵셔널 호출 — 테스트 스텁은 이 메서드들을 생략해
233
- // 시각 층·env 질문을 건너뛴다 (실행 계약은 그대로).
206
+ // ── First-screen UI layers + real io for the interactive layer ─────
207
+ // runInteractive calls io.<method>?.() optionally - test stubs omit these methods
208
+ // to skip the visual layers and env questions (the execution contract is unchanged).
234
209
  import { printBanner as _printBanner } from "./banner.js";
235
210
  import {
236
211
  printDetectionLog as _detLog, printAnalysisCard as _card,
@@ -244,7 +219,7 @@ export function analysisCard(info) { _card(info); }
244
219
  export function installKind(info) { _installKind(info); }
245
220
  export function summary(ctx) { _summary(ctx); }
246
221
 
247
- // env 계획·경로 해석·충돌 메뉴가 쓰는 저수준 엔진 io (env-plan/paths-resolve의 io 계약)
222
+ // Low-level engine io used by the env plan, path resolution and conflict menu (io contract of env-plan/paths-resolve)
248
223
  export const engineIo = {
249
224
  select: engine.select,
250
225
  multiselect: engine.multiselect,