project-auto-wizard 0.12.0 → 0.12.2

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 (78) hide show
  1. package/README.md +4 -8
  2. package/package.json +1 -1
  3. package/payload/config/breaking-changes.json +1 -1
  4. package/payload/scripts/__pycache__/changelog_manager.cpython-314.pyc +0 -0
  5. package/payload/scripts/__pycache__/issue_helper.cpython-314.pyc +0 -0
  6. package/payload/scripts/__pycache__/version_manager.cpython-314.pyc +0 -0
  7. package/payload/scripts/changelog_manager.py +4 -4
  8. package/payload/scripts/version_manager.py +2 -2
  9. package/payload/version.yml.template +0 -2
  10. package/payload/workflows/common/PROJECT-COMMON-AI-PR-SUMMARY.yaml +0 -4
  11. package/payload/workflows/common/PROJECT-COMMON-AUTO-CHANGELOG-CONTROL.yaml +2 -7
  12. package/payload/workflows/common/PROJECT-COMMON-ISSUE-HELPER.yaml +3 -6
  13. package/payload/workflows/common/PROJECT-COMMON-README-VERSION-UPDATE.yaml +0 -12
  14. package/payload/workflows/common/PROJECT-COMMON-RELEASE-PUBLISH.yaml +2 -6
  15. package/payload/workflows/common/PROJECT-COMMON-VERSION-CONTROL.yaml +3 -7
  16. package/payload/workflows/flutter/PROJECT-FLUTTER-ANDROID-FIREBASE-CICD.yaml +7 -34
  17. package/payload/workflows/flutter/PROJECT-FLUTTER-ANDROID-PLAYSTORE-CICD.yaml +2 -25
  18. package/payload/workflows/flutter/PROJECT-FLUTTER-ANDROID-SELFHOSTED-CICD.yaml +2 -20
  19. package/payload/workflows/flutter/PROJECT-FLUTTER-ANDROID-TEST-APK.yaml +5 -30
  20. package/payload/workflows/flutter/PROJECT-FLUTTER-APP-BUILD-TRIGGER.yaml +0 -4
  21. package/payload/workflows/flutter/PROJECT-FLUTTER-CI.yaml +7 -51
  22. package/payload/workflows/flutter/PROJECT-FLUTTER-IOS-TEST-TESTFLIGHT.yaml +5 -29
  23. package/payload/workflows/flutter/PROJECT-FLUTTER-IOS-TESTFLIGHT.yaml +9 -29
  24. package/payload/workflows/go/PROJECT-GO-CI.yaml +5 -28
  25. package/payload/workflows/go/PROJECT-GO-PR-PREVIEW.yaml +19 -99
  26. package/payload/workflows/go/PROJECT-GO-SIMPLE-CICD.yaml +18 -89
  27. package/payload/workflows/next/PROJECT-NEXT-CI.yaml +11 -41
  28. package/payload/workflows/next/PROJECT-NEXT-CICD.yaml +9 -45
  29. package/payload/workflows/python/PROJECT-PYTHON-CI.yaml +1 -27
  30. package/payload/workflows/python/PROJECT-PYTHON-PR-PREVIEW.yaml +14 -97
  31. package/payload/workflows/python/PROJECT-PYTHON-SIMPLE-CICD.yaml +14 -92
  32. package/payload/workflows/react/PROJECT-REACT-CI.yaml +11 -40
  33. package/payload/workflows/react/PROJECT-REACT-CICD.yaml +9 -43
  34. package/payload/workflows/spring/{nexus/PROJECT-SPRING-NEXUS-CI.yml → PROJECT-SPRING-CI.yml} +2 -25
  35. package/payload/workflows/spring/server-deploy/PROJECT-SPRING-NONSTOP-NGINX-CICD.yaml +19 -63
  36. package/payload/workflows/spring/server-deploy/PROJECT-SPRING-NONSTOP-TRAEFIK-CICD.yaml +19 -59
  37. package/payload/workflows/spring/server-deploy/PROJECT-SPRING-PR-PREVIEW.yaml +23 -86
  38. package/payload/workflows/spring/server-deploy/PROJECT-SPRING-SIMPLE-CICD.yaml +20 -80
  39. package/src/cli/args.js +1 -15
  40. package/src/cli/help.js +0 -2
  41. package/src/commands/doctor.js +5 -5
  42. package/src/commands/dry-run.js +1 -1
  43. package/src/commands/full.js +9 -10
  44. package/src/commands/interactive-flutter.js +1 -1
  45. package/src/commands/interactive.js +25 -46
  46. package/src/commands/purge.js +2 -2
  47. package/src/commands/status.js +4 -6
  48. package/src/commands/uninstall.js +3 -4
  49. package/src/context.js +4 -6
  50. package/src/core/assets.js +2 -2
  51. package/src/core/baseline.js +1 -1
  52. package/src/core/branches.js +3 -3
  53. package/src/core/breaking-check.js +1 -1
  54. package/src/core/breaking.js +1 -1
  55. package/src/core/copy/flutter-app.js +2 -2
  56. package/src/core/copy/gitignore.js +8 -8
  57. package/src/core/copy/workflows.js +24 -69
  58. package/src/core/deploy-style.js +2 -2
  59. package/src/core/detect-fs.js +11 -11
  60. package/src/core/detect.js +7 -7
  61. package/src/core/flutter-options.js +1 -1
  62. package/src/core/installed-stores.js +1 -1
  63. package/src/core/logger.js +1 -1
  64. package/src/core/paths-resolve.js +1 -1
  65. package/src/core/removal-plan.js +4 -4
  66. package/src/core/verify.js +3 -3
  67. package/src/core/version-yml.js +17 -33
  68. package/src/core/wizard-env.js +4 -4
  69. package/src/index.js +12 -14
  70. package/src/ui/banner.js +2 -2
  71. package/src/ui/env-plan.js +10 -16
  72. package/src/ui/prompts.js +9 -14
  73. package/src/ui/status-cards.js +10 -12
  74. package/src/ui/summary.js +9 -9
  75. package/payload/workflows/common/secret-backup/PROJECT-COMMON-SECRET-FILE-UPLOAD.yaml +0 -210
  76. package/payload/workflows/spring/nexus/PROJECT-SPRING-GITHUB-PACKAGES-PUBLISH.yml +0 -92
  77. package/payload/workflows/spring/nexus/PROJECT-SPRING-NEXUS-PUBLISH.yml +0 -70
  78. package/src/core/options-ask.js +0 -102
@@ -10,7 +10,6 @@ import { parseExisting } from "../core/version-yml.js";
10
10
  import { runBreakingCheck } from "../core/breaking-check.js";
11
11
  import { resolveProjectPaths } from "../core/paths-resolve.js";
12
12
  import { resolveBranchConfig, detectRemoteBranches, ensureDevelopBranch, sortBranchesForSelection } from "../core/branches.js";
13
- import { askAllOptionalWorkflows } from "../core/options-ask.js";
14
13
  import { promptEnvPlan } from "../ui/env-plan.js";
15
14
  import { surveyWorkflows } from "../core/copy/workflows.js";
16
15
  import { createContext, VALID_TYPES } from "../context.js";
@@ -34,7 +33,7 @@ export async function runInteractive(baseCtx, { cwd = process.cwd(), payloadRoot
34
33
  const payload = assertPayload(payloadRoot ?? resolvePayloadRoot());
35
34
  const templateVersion = readTemplateVersion();
36
35
 
37
- // 층1 — 시작 배너 (#446 확정 시안 A). 스텁엔 banner 없음 → intro 폴백.
36
+ // 시작 배너. 스텁엔 banner 없음 → intro 폴백.
38
37
  if (io.banner) io.banner({ version: templateVersion, modeLabel: "대화형 통합 마법사" });
39
38
  else io.intro?.("project-auto-wizard — 대화형 통합 마법사");
40
39
 
@@ -42,12 +41,12 @@ export async function runInteractive(baseCtx, { cwd = process.cwd(), payloadRoot
42
41
  const vyPath = join(cwd, "version.yml");
43
42
  const existing = existsSync(vyPath) ? parseExisting(readFileSync(vyPath, "utf8")) : null;
44
43
 
45
- // 층5 — 신규/업데이트 판별 (#446)
44
+ // 신규/업데이트 판별
46
45
  io.installKind?.({ currentTemplateVersion: existing?.templateVersion || "", templateVersion });
47
46
 
48
47
  // 1) 모드 선택
49
48
  // status/doctor — 읽기 전용이라 감지·breaking 게이트가 필요 없다. 결과를 보여준 뒤 메뉴로
50
- // 돌아온다(이슈 #31): 진단의 목적이 설치 준비이므로 확인 → 설치를 한 세션에서 끝내야 한다.
49
+ // 돌아온다: 진단의 목적이 설치 준비이므로 확인 → 설치를 한 세션에서 끝내야 한다.
51
50
  // CLI 경로(index.js의 --mode status/doctor)는 단발 명령이므로 지금처럼 즉시 종료한다.
52
51
  let mode;
53
52
  for (let round = 0; ; round++) {
@@ -75,7 +74,7 @@ export async function runInteractive(baseCtx, { cwd = process.cwd(), payloadRoot
75
74
 
76
75
  // full/version/workflows — 감지 (version은 기존 version.yml 최우선)
77
76
  // 감지 경고는 즉시 찍지 않고 모았다가 감지 박스 안에서 출력한다 — 종전에는 경고가 박스보다
78
- // 먼저 나와 앞선 질문에 대한 경고처럼 보였다 (이슈 #80). 안내 문구도 대화형용으로 바꾼다.
77
+ // 먼저 나와 앞선 질문에 대한 경고처럼 보였다. 안내 문구도 대화형용으로 바꾼다.
79
78
  const detectWarnings = [];
80
79
  let types = detectTypes(cwd);
81
80
  let version = (existing?.version) || detectVersion(cwd, {
@@ -85,16 +84,14 @@ export async function runInteractive(baseCtx, { cwd = process.cwd(), payloadRoot
85
84
  let branch = detectDefaultBranch(cwd);
86
85
  const repoName = detectRepoName(cwd);
87
86
  // 선택 워크플로우 초기값: version.yml 저장 옵션 (.sh read_template_options L2361 등가)
88
- let includeNexus = existing?.options?.nexus ?? false;
89
- let includeSecretBackup = existing?.options?.secretBackup ?? false;
90
87
  let includeSemverAuto = existing?.options?.semverAuto ?? null;
91
88
  let includeCopilotAi = existing?.options?.copilotAi ?? null;
92
- // 서버 배포 방식 — 저장값(version.yml)이 있으면 재질문하지 않는다 (nexus/secret_backup과 같은 규약).
89
+ // 서버 배포 방식 — 저장값(version.yml)이 있으면 재질문하지 않는다 (semver_auto와 같은 규약).
93
90
  let deployStyle = isDeployStyle(existing?.options?.deployStyle) ? existing.options.deployStyle : "";
94
91
  const showOptional = mode === "full";
95
92
  const realTty = process.stdout.isTTY === true;
96
93
 
97
- // Flutter 옵션 (이슈 #131) — 저장값이 있으면 재질문하지 않는다 (deploy_style과 같은 규약).
94
+ // Flutter 옵션 — 저장값이 있으면 재질문하지 않는다 (deploy_style과 같은 규약).
98
95
  // 저장값 없는 기존 설치는 동작 보존을 위해 dotenv를 초기 선택으로, 스토어는 설치된 워크플로우로 추론한다.
99
96
  // 환경변수 기본값 규칙(신규=dart-define, 기존 설치·저장값 없음=dotenv)은 resolveFlutterOptions가 단일 진실이다.
100
97
  let flutter = savedFlutterState(existing);
@@ -109,11 +106,11 @@ export async function runInteractive(baseCtx, { cwd = process.cwd(), payloadRoot
109
106
  if (types.includes("flutter")) flutter = await askUnsetFlutterOptions(io, flutter, flutterAsk);
110
107
  };
111
108
 
112
- // 층2 — 감지 로그 (#446). markers = 실제로 존재를 확인한 파일 (이슈 #77).
109
+ // 감지 로그. markers = 실제로 존재를 확인한 파일.
113
110
  let markers = detectMarkers(cwd, types);
114
111
  io.detectionLog?.({ types, version, branch, markers, warnings: detectWarnings });
115
112
 
116
- // 타입 확정 (이슈 #78) — 감지는 추정이므로 다른 질문보다 먼저 확인받는다. 종전에는 확정 UI가
113
+ // 타입 확정 — 감지는 추정이므로 다른 질문보다 먼저 확인받는다. 종전에는 확정 UI가
117
114
  // '수정하기 > 프로젝트 타입' 두 단계 뒤에 숨어 있어, 타입이 틀린 채로 설치가 끝나는 일이 많았다.
118
115
  // 타입이 뒤에 나올 질문(선택 워크플로우·경로·env)의 범위를 정하므로 순서상 여기가 맞다.
119
116
  // 저장값이 있는 업데이트 설치와 비대화형에서는 묻지 않는다 — 기존 동작 그대로.
@@ -128,24 +125,15 @@ export async function runInteractive(baseCtx, { cwd = process.cwd(), payloadRoot
128
125
  }
129
126
  }
130
127
 
131
- // 선택 워크플로우(Nexus/Secret) 질문 (.sh ask_all_optional_workflows L2707 — full/workflows만)
128
+ // 배포 방식·Flutter 옵션·semver/Copilot 질문 — 선택 항목을 묻는 경우(showOptional)만
132
129
  if (showOptional) {
133
- const r = await askAllOptionalWorkflows({
134
- payloadRoot: payload, types, targetRoot: cwd,
135
- current: { nexus: existing?.options?.nexus ?? null, secretBackup: existing?.options?.secretBackup ?? null },
136
- force: false, tty: realTty,
137
- io: { confirm: ({ message, initialValue }) => io.askYesNo(message, initialValue) },
138
- });
139
- includeNexus = r.nexus;
140
- includeSecretBackup = r.secretBackup;
141
-
142
- // 서버 배포 방식 (이슈 #80). Nexus(라이브러리)를 고르면 server-deploy가 통째로 빠지므로 묻지 않는다.
143
- if (!includeNexus && !isDeployStyle(deployStyle)) {
130
+ // 서버 배포 방식.
131
+ if (!isDeployStyle(deployStyle)) {
144
132
  const picked = await io.selectDeployStyle();
145
133
  deployStyle = isDeployStyle(picked) ? picked : DEFAULT_DEPLOY_STYLE; // ESC = 기본값
146
134
  }
147
135
 
148
- // Flutter 옵션 (이슈 #131) — 환경변수 방식 → 스토어 배포 대상 → 플랫폼별 배포 모드.
136
+ // Flutter 옵션 — 환경변수 방식 → 스토어 배포 대상 → 플랫폼별 배포 모드.
149
137
  await askFlutterOptions();
150
138
 
151
139
  // 신규 질문 — 자동 semver 승격 (기본 ON). 저장값 있으면 재질문 생략.
@@ -171,11 +159,11 @@ export async function runInteractive(baseCtx, { cwd = process.cwd(), payloadRoot
171
159
  let paths = new Map();
172
160
  let confirmed = false;
173
161
  while (!confirmed) {
174
- // 층3 — 프로젝트 분석 개요 카드 (#446). 스텁엔 없음 → note 폴백.
162
+ // 프로젝트 분석 개요 카드. 스텁엔 없음 → note 폴백.
175
163
  if (io.analysisCard) {
176
- io.analysisCard({ mode, modeLabel: modeLabel(mode), types, version, branch, includeNexus, includeSecretBackup, showOptional, paths, flutter, envModeDefault: flutterAsk.envModeDefault });
164
+ io.analysisCard({ mode, modeLabel: modeLabel(mode), types, version, branch, showOptional, paths, flutter, envModeDefault: flutterAsk.envModeDefault });
177
165
  } else {
178
- io.note?.(summarize({ mode, types, version, branch, includeNexus, includeSecretBackup, showOptional, flutter, envModeDefault: flutterAsk.envModeDefault }), "프로젝트 분석 결과");
166
+ io.note?.(summarize({ mode, types, version, branch, showOptional, flutter, envModeDefault: flutterAsk.envModeDefault }), "프로젝트 분석 결과");
179
167
  }
180
168
  const choice = await io.confirmProjectMenu();
181
169
  if (choice === "cancel") { io.cancelMessage?.("설치를 취소했습니다."); return 0; }
@@ -184,7 +172,7 @@ export async function runInteractive(baseCtx, { cwd = process.cwd(), payloadRoot
184
172
  // edit 루프
185
173
  let editing = true;
186
174
  while (editing) {
187
- const what = await io.editMenu({ showOptional, showFlutter: showOptional && types.includes("flutter") });
175
+ const what = await io.editMenu({ showFlutter: showOptional && types.includes("flutter") });
188
176
  if (isCancel(what) || what === "done") { editing = false; break; }
189
177
  if (what === "type") {
190
178
  const t = await io.selectTypes(types);
@@ -204,12 +192,6 @@ export async function runInteractive(baseCtx, { cwd = process.cwd(), payloadRoot
204
192
  } else if (what === "branch") {
205
193
  const b = await io.askText("기본 브랜치", branch);
206
194
  if (!isCancel(b) && b) branch = b;
207
- } else if (what === "nexus") {
208
- const y = await io.askYesNo("Nexus publish 워크플로우를 포함할까요?", includeNexus);
209
- if (!isCancel(y)) includeNexus = y === true;
210
- } else if (what === "secret") {
211
- const y = await io.askYesNo("Secret 백업 워크플로우를 포함할까요?", includeSecretBackup);
212
- if (!isCancel(y)) includeSecretBackup = y === true;
213
195
  } else if (FLUTTER_EDIT_ITEMS.has(what)) {
214
196
  flutter = await editFlutterOption(io, what, flutter, flutterAsk.envModeDefault);
215
197
  }
@@ -226,7 +208,7 @@ export async function runInteractive(baseCtx, { cwd = process.cwd(), payloadRoot
226
208
  iosDeployMode: flutter.iosDeployMode || DEFAULT_DEPLOY_MODE,
227
209
  };
228
210
 
229
- const versionCode = existing?.versionCode ?? detectBuildNumber(cwd, { types }) ?? 1; // 기존 빌드번호 보존, 신규 통합 시 프로젝트 파일에서 감지 (이슈 #41)
211
+ const versionCode = existing?.versionCode ?? detectBuildNumber(cwd, { types }) ?? 1; // 기존 빌드번호 보존, 신규 통합 시 프로젝트 파일에서 감지
230
212
 
231
213
  // 신규 질문 ① — 브랜치 설정 (DESIGN-SPEC §4). full/workflows만 질문, version은 기본값 기록.
232
214
  // 저장값(version.yml metadata.template.branches)이 있으면 재질문 없이 재사용 (업데이트 모드).
@@ -235,7 +217,7 @@ export async function runInteractive(baseCtx, { cwd = process.cwd(), payloadRoot
235
217
  : resolveBranchConfig({ defaultBranch: branch });
236
218
  if (showOptional && !existing?.branches) {
237
219
  const remoteBranches = await detectRemoteBranches(cwd);
238
- // 이슈 #93 — 두 질문에 같은 이름을 입력해야만 trunk-based가 되는 암묵적 규칙 대신,
220
+ // 두 질문에 같은 이름을 입력해야만 trunk-based가 되는 암묵적 규칙 대신,
239
221
  // 전략을 먼저 명시적으로 고르게 한다. 취소/그 외 값은 기존 기본 동작과 같은 pr-flow로 폴백
240
222
  // (selectDeployStyle의 "ESC = 기본값" 패턴과 동일).
241
223
  const strategyPick = await io.selectBranchStrategy();
@@ -273,22 +255,21 @@ export async function runInteractive(baseCtx, { cwd = process.cwd(), payloadRoot
273
255
  if (showOptional) {
274
256
  const plan = await promptEnvPlan({
275
257
  payloadRoot: payload, types, io: io.engineIo ?? null, force: false,
276
- resolvers, includeNexus, includeSecretBackup, deployStyle, targetRoot: cwd, repoName,
277
- flutterStore: flutterOptions.stores, // 선택 해제된 스토어 워크플로우의 ask 질문은 묻지 않는다 (D2 env-plan)
258
+ resolvers, deployStyle, targetRoot: cwd, repoName,
259
+ flutterStore: flutterOptions.stores, // 선택 해제된 스토어 워크플로우의 ask 질문은 묻지 않는다
278
260
  });
279
261
  envValues = plan.values;
280
262
  envUseDefaults = plan.useDefaults;
281
- envAnswers = plan.answers || []; // 완료 요약·설치 로그가 같은 답변 데이터를 쓴다 (#79, #80)
263
+ envAnswers = plan.answers || []; // 완료 요약·설치 로그가 같은 답변 데이터를 쓴다
282
264
  }
283
265
 
284
266
  const { now, today } = clock || utcNow();
285
267
  const ctx = createContext({
286
268
  mode, force: true, types, version, versionCode, branch, branches, paths,
287
- includeNexus, includeSecretBackup,
288
269
  includeSemverAuto,
289
270
  includeCopilotAi,
290
271
  repoName, templateVersion, resolvers, envValues, envUseDefaults, now, today,
291
- // 설치 로그(#79)·완료 요약(#80)이 쓰는 부가 문맥 — 설치 동작 자체는 바꾸지 않는다.
272
+ // 설치 로그·완료 요약이 쓰는 부가 문맥 — 설치 동작 자체는 바꾸지 않는다.
292
273
  markers, envAnswers, detectWarnings,
293
274
  deployStyle: deployStyle || DEFAULT_DEPLOY_STYLE,
294
275
  envMode: flutterOptions.envMode, flutterStore: flutterOptions.stores,
@@ -297,7 +278,7 @@ export async function runInteractive(baseCtx, { cwd = process.cwd(), payloadRoot
297
278
  });
298
279
  ctx.templateVersion = templateVersion;
299
280
 
300
- // 사용자가 답해야 하는 것만 묻는다 (issue #69). baseline 3-way가 자동으로 안전한 경우를
281
+ // 사용자가 답해야 하는 것만 묻는다. baseline 3-way가 자동으로 안전한 경우를
301
282
  // 걸러내므로, 여기 오는 것은 (a) 양쪽이 다 바뀐 진짜 충돌과 (b) 사용자가 지운 파일뿐이다.
302
283
  let hooks = {};
303
284
  if (showOptional && io.engineIo?.select) {
@@ -363,7 +344,7 @@ export async function runInteractive(baseCtx, { cwd = process.cwd(), payloadRoot
363
344
  }
364
345
 
365
346
  // 브랜치 선택 — 원격 목록이 있으면 select(+직접 입력), 없으면 텍스트 입력. ESC/빈값 = 기본값.
366
- // 이슈 #85: def를 최우선으로, main/develop을 그다음으로 정렬하고 커서를 def에 고정한다.
347
+ // def를 최우선으로, main/develop을 그다음으로 정렬하고 커서를 def에 고정한다.
367
348
  export async function pickBranch(io, message, def, remoteBranches, isCancel) {
368
349
  if (io.engineIo?.select && remoteBranches.length) {
369
350
  const sorted = sortBranchesForSelection(remoteBranches, def);
@@ -383,7 +364,7 @@ export async function pickBranch(io, message, def, remoteBranches, isCancel) {
383
364
  return isCancel(v) || !v ? def : v;
384
365
  }
385
366
 
386
- function summarize({ mode, types, version, branch, includeNexus, includeSecretBackup, showOptional, flutter, envModeDefault }) {
367
+ function summarize({ mode, types, version, branch, showOptional, flutter, envModeDefault }) {
387
368
  const lines = [
388
369
  `통합 모드 : ${modeLabel(mode)}`,
389
370
  `프로젝트 타입 : ${types.join(", ")}${types.length > 1 ? " (멀티)" : ""}`,
@@ -391,8 +372,6 @@ function summarize({ mode, types, version, branch, includeNexus, includeSecretBa
391
372
  `기본 브랜치 : ${branch}`,
392
373
  ];
393
374
  if (showOptional) {
394
- lines.push(`Nexus publish : ${includeNexus ? "포함" : "제외"}`);
395
- lines.push(`Secret 백업 : ${includeSecretBackup ? "포함" : "제외"}`);
396
375
  if (types.includes("flutter")) {
397
376
  const stores = flutter.stores ?? [];
398
377
  const modeParts = stores.map((p) => `${p}=${(p === "android" ? flutter.androidDeployMode : flutter.iosDeployMode) || DEFAULT_DEPLOY_MODE}`);
@@ -48,7 +48,7 @@ export function printPurgePlan(plan, { dryRun = false } = {}) {
48
48
  // 실제 삭제 수행 — planPurge()와 동일 shape을 반환하되 실제로 제거된 항목을 반영한다.
49
49
  // planRemoval 결과를 그대로 지우지 않는 이유: 그 목록은 항상 전체라서
50
50
  // --keep-* 로 선택적 카테고리만 보존하는 요구사항과 맞지 않는다.
51
- // H3 (Fable 검토): readmeSection은 plan의 판정을 그대로 되돌려주지 않고
51
+ // readmeSection은 plan의 판정을 그대로 되돌려주지 않고
52
52
  // removeVersionSectionFromReadme()의 실제 반환값("removed"인지)을 반영한다 — 스펙 §6이
53
53
  // "반환값은 실제 삭제 결과를 반영"하라고 명시하기 때문에, plan과 실제 제거 조건이
54
54
  // 이론상 어긋나는 경우에도 printPurgeResult가 거짓으로 "제거됨"을 보고하지 않는다.
@@ -65,7 +65,7 @@ export function executePurge(payloadRoot, targetRoot = ".", keepFlags = {}) {
65
65
  }
66
66
 
67
67
  // 삭제 후 실제 제거된 목록 출력 — printPurgePlan과 완전히 동일한 형태(파일명 나열)로
68
- // 맞춘다 (M3, Fable 검토: 개수만 출력하면 스펙 §5-6의 "제거된 목록 재출력" 요구를 충족하지 못함).
68
+ // 맞춘다 (개수만 출력하면 스펙 §5-6의 "제거된 목록 재출력" 요구를 충족하지 못함).
69
69
  export function printPurgeResult(result) {
70
70
  const lines = ["", "제거됨:", ""];
71
71
  lines.push(`워크플로우 (${result.workflows.length}개):`);
@@ -26,8 +26,6 @@ export function runStatus(payloadRoot, targetRoot = ".") {
26
26
  const branchesForCompare = existing.branches || { main: detectDefaultBranch(targetRoot) || "main", develop: "develop", mode: "pr-flow" };
27
27
  const context = {
28
28
  types: existing.types, paths: existing.paths,
29
- includeNexus: existing.options.nexus === true,
30
- includeSecretBackup: existing.options.secretBackup === true,
31
29
  repoName, resolvers, branches: branchesForCompare,
32
30
  flutterStore: flutterOptions.stores,
33
31
  };
@@ -41,7 +39,7 @@ export function runStatus(payloadRoot, targetRoot = ".") {
41
39
  branches: existing.branches,
42
40
  options: existing.options,
43
41
  // 사용자가 손댄 파일 = 진짜 충돌(changed) + 업스트림은 그대로인데 내가 고친 것(localOnly).
44
- // baseline이 없으면 localOnly는 항상 비어 있어 종전과 동일하게 동작한다 (issue #69).
42
+ // baseline이 없으면 localOnly는 항상 비어 있어 종전과 동일하게 동작한다.
45
43
  modifiedFiles: [...plan.changed, ...plan.localOnly].map((f) => f.filename),
46
44
  // 업데이트 시 무슨 일이 일어날지 미리 보여주는 버킷들 — 판단 재료가 없어 사용자가
47
45
  // 직접 git diff를 떠야 했던 문제를 없앤다.
@@ -71,7 +69,7 @@ export function printStatus(status) {
71
69
  const semverAutoLabel = status.options.semverAuto === null ? "미설정(기본 false)" : status.options.semverAuto;
72
70
  const copilotAiLabel = boolLabel(status.options.copilotAi ?? null);
73
71
  const flutterLabels = status.types.includes("flutter") ? flutterOptionLabels(status.options) : "";
74
- lines.push(`옵션 : nexus=${boolLabel(status.options.nexus)} secret_backup=${boolLabel(status.options.secretBackup)} semver_auto=${semverAutoLabel} copilot_ai=${copilotAiLabel}${flutterLabels}`);
72
+ lines.push(`옵션 : semver_auto=${semverAutoLabel} copilot_ai=${copilotAiLabel}${flutterLabels}`);
75
73
  if (status.modifiedFiles.length) {
76
74
  lines.push("", `사용자가 수정한 워크플로우 파일 (${status.modifiedFiles.length}개):`);
77
75
  for (const f of status.modifiedFiles) lines.push(` - ${f}`);
@@ -79,7 +77,7 @@ export function printStatus(status) {
79
77
  lines.push("", "모든 워크플로우 파일이 설치 시점 기본값과 동일합니다 (수정 없음).");
80
78
  }
81
79
 
82
- // 업데이트하면 무슨 일이 일어나는지 (issue #69). baseline이 없는 설치는 전부 0이라 출력하지 않는다.
80
+ // 업데이트하면 무슨 일이 일어나는지. baseline이 없는 설치는 전부 0이라 출력하지 않는다.
83
81
  const b = status.buckets || { autoUpdatable: [], localKept: [], conflicts: [], removed: [] };
84
82
  if (b.autoUpdatable.length || b.localKept.length || b.conflicts.length || b.removed.length) {
85
83
  lines.push("", "지금 업데이트하면:");
@@ -92,7 +90,7 @@ export function printStatus(status) {
92
90
  console.log(lines.join("\n"));
93
91
  }
94
92
 
95
- // Flutter 타입일 때만 붙는 옵션 (이슈 #131). 저장값이 없으면 그 상태에서 실제로 적용되는 동작을 함께 알린다.
93
+ // Flutter 타입일 때만 붙는 옵션. 저장값이 없으면 그 상태에서 실제로 적용되는 동작을 함께 알린다.
96
94
  function flutterOptionLabels(options) {
97
95
  return [
98
96
  ` env_mode=${options.envMode ?? "미설정(dotenv 유지)"}`,
@@ -17,7 +17,7 @@ export function planUninstall(payloadRoot, targetRoot, selection) {
17
17
  const removalPlan = planRemoval(payloadRoot, targetRoot);
18
18
  return {
19
19
  workflows: selection.workflows ? removalPlan.workflows : [],
20
- // baseline은 워크플로우 해시 기록 — 워크플로우를 지우면 함께 사라져야 한다 (issue #69)
20
+ // baseline은 워크플로우 해시 기록 — 워크플로우를 지우면 함께 사라져야 한다
21
21
  baseline: selection.workflows ? removalPlan.baseline : [],
22
22
  scripts: selection.scripts ? removalPlan.scripts : [],
23
23
  readme: selection.readme ? hasVersionSection(targetRoot) : false,
@@ -46,7 +46,7 @@ export function runUninstall(context, payloadRoot, targetRoot, selection) {
46
46
  // ── 대화형 체크리스트 흐름 ────────────────────────────────────────────
47
47
  const ITEM_DEFS = [
48
48
  // .github/.wizard 에는 baseline.json과 설치 로그(.wizard/logs)가 함께 들어 있다 —
49
- // 워크플로우를 지우면 함께 사라지므로 라벨에 명시한다 (이슈 #79).
49
+ // 워크플로우를 지우면 함께 사라지므로 라벨에 명시한다.
50
50
  { key: "workflows", label: "워크플로우 (.github/workflows/PROJECT-*.yaml) + 설치 기록 (.github/.wizard)" },
51
51
  { key: "scripts", label: "스크립트 (.github/scripts/*.py)" },
52
52
  { key: "readme", label: "README.md 버전 섹션 (AUTO-VERSION-SECTION)" },
@@ -54,8 +54,7 @@ const ITEM_DEFS = [
54
54
  { key: "versionYml", label: "version.yml (버전/브랜치 설정 전체)" },
55
55
  ];
56
56
 
57
- // 기본 체크 상태 — 설치 시 옵션(nexus/secret-backup)이 opt-in인 것과 대칭으로,
58
- // 여기서는 "안전 삭제" 2종만 기본 체크하고 나머지(readme/gitignore/versionYml)는 opt-in.
57
+ // 기본 체크 상태 — "안전 삭제" 2종만 기본 체크하고 나머지(readme/gitignore/versionYml)는 opt-in.
59
58
  export const SAFE_ITEMS = ["workflows", "scripts"];
60
59
 
61
60
  function detectAvailableItems(payloadRoot, targetRoot) {
package/src/context.js CHANGED
@@ -4,9 +4,9 @@ export const VALID_TYPES = [
4
4
  "react-native", "react-native-expo", "node", "python", "basic", "go",
5
5
  ];
6
6
 
7
- // --mode 화이트리스트 (issue #19) — 알 수 없는 값은 부수효과(브랜치 조회 등) 이전에 즉시 거부해야 한다.
8
- // purge는 --help/대화형 메뉴에 노출하지 않는 숨김 모드(issue #6)이지만 검증 대상에는 포함한다.
9
- // version/workflows(부분 설치)와 revert는 제거됐다 (issue #70) — 부분 설치는 설치 시점 baseline을
7
+ // --mode 화이트리스트 — 알 수 없는 값은 부수효과(브랜치 조회 등) 이전에 즉시 거부해야 한다.
8
+ // purge는 --help/대화형 메뉴에 노출하지 않는 숨김 모드이지만 검증 대상에는 포함한다.
9
+ // version/workflows(부분 설치)와 revert는 제거됐다 — 부분 설치는 설치 시점 baseline을
10
10
  // 반쪽만 갱신해 업데이트 판정을 흐리고, revert는 uninstall의 부분집합이었다.
11
11
  export const VALID_MODES = [
12
12
  "interactive", "full",
@@ -24,11 +24,9 @@ export function createContext(overrides = {}) {
24
24
  branch: "",
25
25
  branches: null, // { main, develop, mode: "pr-flow"|"trunk-based" } — resolveBranchConfig 결과
26
26
  paths: new Map(), // type -> path
27
- includeNexus: null, // null=미설정, true/false=명시
28
- includeSecretBackup: null,
29
27
  includeSemverAuto: null, // null=미설정(다운스트림에서 true로 해석), true/false=명시
30
28
  includeCopilotAi: null, // null=미설정(다운스트림에서 false로 해석), true/false=명시 — Copilot AI 요약 opt-in
31
- // Flutter 옵션 (이슈 #131) — Flutter 타입이 없는 프로젝트에서는 전부 무시된다.
29
+ // Flutter 옵션 — Flutter 타입이 없는 프로젝트에서는 전부 무시된다.
32
30
  // 결정은 src/core/flutter-options.js의 resolveFlutterOptions가 한다.
33
31
  envMode: "", // "dart-define" | "dotenv". ""=미결정 → 템플릿 기본값(dart-define)
34
32
  flutterStore: null, // 스토어 배포 대상 string[] (예: ["android","ios"]). null=미결정 → 둘 다(현행 동작)
@@ -24,7 +24,7 @@ export function readTemplateVersion() {
24
24
  }
25
25
  }
26
26
 
27
- // payload/workflows/common 직하위 yaml 목록 (secret-backup 하위 폴더 제외 — 복사 엔진과 동일 규약).
27
+ // payload/workflows/common 직하위 yaml 목록.
28
28
  export function listCommonWorkflows(payloadRoot = resolvePayloadRoot()) {
29
29
  return listYamlFiles(join(payloadRoot, "workflows", "common"));
30
30
  }
@@ -40,7 +40,7 @@ export function assertPayload(payloadRoot = resolvePayloadRoot()) {
40
40
  return payloadRoot;
41
41
  }
42
42
 
43
- // payload/version.yml.template 원문 (Task 14 치환 파이프라인 소비).
43
+ // payload/version.yml.template 원문.
44
44
  export function readVersionYmlTemplate(payloadRoot = resolvePayloadRoot()) {
45
45
  const p = join(payloadRoot, "version.yml.template");
46
46
  return exists(p) ? readText(p) : null;
@@ -1,4 +1,4 @@
1
- // 설치 시점 baseline (issue #69) — 업데이트에서 "누가 바꿨는지"를 가르는 기준점.
1
+ // 설치 시점 baseline — 업데이트에서 "누가 바꿨는지"를 가르는 기준점.
2
2
  //
3
3
  // 왜 필요한가: isUnchanged()는 payload(theirs)와 설치본(ours)을 2-way로 비교한다.
4
4
  // base가 없으니 업스트림이 한 글자만 고쳐도 사용자가 손대지 않은 파일이 changed로 떨어지고,
@@ -1,7 +1,7 @@
1
1
  // 브랜치 구성 (DESIGN-SPEC §4 신규 질문 ①).
2
2
  // on: push: branches: 는 YAML 정적 값 — 마법사가 릴리스/개발 브랜치를 물어(또는 플래그로 받아)
3
- // {{MAIN_BRANCH}}/{{DEVELOP_BRANCH}} 플레이스홀더를 치환한다 (치환 자체는 branding.js — Task 14).
4
- // main === develop 이면 trunk-based 모드 → RELEASE-PUBLISH 단독 설치 (설치 매트릭스는 Task 16).
3
+ // {{MAIN_BRANCH}}/{{DEVELOP_BRANCH}} 플레이스홀더를 치환한다 (치환 자체는 branding.js).
4
+ // main === develop 이면 trunk-based 모드 → RELEASE-PUBLISH 단독 설치.
5
5
  import { execFile } from "node:child_process";
6
6
 
7
7
  // 기본 exec — git 명령 실행. 반환 {code, stdout, stderr}. 테스트는 mock 주입.
@@ -34,7 +34,7 @@ export function resolveBranchConfig({ mainBranch = "", developBranch = "", defau
34
34
  return { main, develop, mode: main === develop ? "trunk-based" : "pr-flow" };
35
35
  }
36
36
 
37
- // 브랜치 선택 프롬프트용 정렬 (이슈 #85). def를 최우선으로, 그다음 priority(main/develop)
37
+ // 브랜치 선택 프롬프트용 정렬. def를 최우선으로, 그다음 priority(main/develop)
38
38
  // 순서로 목록 앞에 배치한다. 나머지는 remoteBranches의 원래 순서(git이 준 알파벳순)를 유지한다.
39
39
  // 순수 함수 — remoteBranches 원본은 변경하지 않는다.
40
40
  export function sortBranchesForSelection(remoteBranches, def, priority = ["main", "develop"]) {
@@ -24,7 +24,7 @@ export async function loadBreakingJson(payloadRoot) {
24
24
  // opts:
25
25
  // cwd - 통합 대상 루트 (기존 version.yml에서 현재 템플릿 버전 읽음)
26
26
  // payloadRoot - 패키지 payload/ 루트 (번들 폴백용)
27
- // templateVersion - 설치하려는 템플릿 버전 (.sh의 DEFAULT_VERSION 고정 버그를 실버전으로 교정 — 설계 D2)
27
+ // templateVersion - 설치하려는 템플릿 버전 (.sh의 DEFAULT_VERSION 고정 버그를 실버전으로 교정)
28
28
  // askYesNo - async(message, defaultYes)→bool. null이면 비대화형: 경고만 출력 후 진행
29
29
  // loader - 테스트 주입용 (기본 loadBreakingJson)
30
30
  export async function runBreakingCheck({ cwd, payloadRoot, templateVersion, askYesNo = null, loader = loadBreakingJson }) {
@@ -11,7 +11,7 @@ export function compareVersions(a, b) {
11
11
  }
12
12
 
13
13
  // breaking-changes.json에서 current < ver <= target 범위 항목 수집.
14
- // ⚠️ .sh 버그 수정: target은 하드코딩 1.3.14가 아니라 실제 templateVersion을 넘긴다 (D2).
14
+ // ⚠️ .sh 버그 수정: target은 하드코딩 1.3.14가 아니라 실제 templateVersion을 넘긴다.
15
15
  // _ 로 시작하는 키(메타) 제외. severity critical / 그 외(warning).
16
16
  // 버전 키의 값은 항목 객체 또는 항목 객체의 배열(같은 릴리스에 고지가 여러 건일 때).
17
17
  export function collectBreaking(json, current, target) {
@@ -1,6 +1,6 @@
1
- // Flutter 앱 소유 파일(fastlane·ExportOptions.plist) 설치 (이슈 #131).
1
+ // Flutter 앱 소유 파일(fastlane·ExportOptions.plist) 설치.
2
2
  // 워크플로우와 달리 사용자가 값을 채워 넣는 파일이라 "없을 때만 생성"한다 — 덮어쓰지 않고 baseline
3
- // 3-way도 적용하지 않는다(copyWorkflows의 secret-backup 경로와 같은 선례). 원본 갱신을 기존 사용자에게
3
+ // 3-way도 적용하지 않는다(기존 사용자 파일을 덮어쓰지 않는 규약). 원본 갱신을 기존 사용자에게
4
4
  // 전파하는 것은 범위 밖이다.
5
5
  import { join, posix } from "node:path";
6
6
  import { existsSync } from "node:fs";
@@ -1,15 +1,15 @@
1
1
  // .gitignore 보장 — 마법사 자신이 만드는 충돌 백업 부산물(*.bak, *.template.yaml)만 대상으로 한다.
2
- // 마법사가 설치하는 것과 무관한 개인 개발환경 설정(IDE 등)은 마법사 책임 범위 밖 — issue #7.
2
+ // 마법사가 설치하는 것과 무관한 개인 개발환경 설정(IDE 등)은 마법사 책임 범위 밖.
3
3
  // 배너 블록은 종료 마커(BANNER_END)로 범위가 명확히 구분되어 있어, 그 안에 사용자가 다른 줄을
4
- // 끼워넣어도 REQUIRED_ENTRIES만 정확히 개별 제거하고 나머지는 보존한다 (issue #20 M7).
4
+ // 끼워넣어도 REQUIRED_ENTRIES만 정확히 개별 제거하고 나머지는 보존한다.
5
5
  // 주의(의도된 트레이드오프): 이 수정 이전 버전으로 설치되어 종료 마커가 없는 배너가 이미 있는
6
6
  // 레포에서는, 배너 직후 REQUIRED_ENTRIES가 연속으로 이어지는 동안만 제거하는 구 방식으로
7
- // 폴백한다 — issue #7이 "이미 설치된 레포의 .gitignore는 소급 처리하지 않고 사용자 판단에
7
+ // 폴백한다 — 이전 결정이 "이미 설치된 레포의 .gitignore는 소급 처리하지 않고 사용자 판단에
8
8
  // 맡긴다"고 명시하므로 별도 마이그레이션 로직을 추가하지 않는다.
9
9
  import { join } from "node:path";
10
10
  import { existsSync, readFileSync, writeFileSync, rmSync } from "node:fs";
11
11
 
12
- // issue #7: 마법사가 설치하는 것과 무관한 개인 개발환경 항목(/.idea 등)은 마법사 책임 밖이므로 제거.
12
+ // 마법사가 설치하는 것과 무관한 개인 개발환경 항목(/.idea 등)은 마법사 책임 밖이므로 제거.
13
13
  // 대신 마법사 자신의 충돌 처리(workflows.js backup/template 결정)가
14
14
  // 실제로 만들어내는 부산물만 gitignore 대상으로 삼는다.
15
15
  const REQUIRED_ENTRIES = ["*.bak", "*.template.yaml"];
@@ -66,7 +66,7 @@ export function ensureGitignore(targetRoot = ".") {
66
66
  // ensureGitignore가 기존 파일에 배너 블록을 추가할 때 항상 이 정확한 시퀀스로 시작한다
67
67
  // (빈 줄 하나 + 3줄 배너), 그리고 REQUIRED_ENTRIES 뒤에 BANNER_END로 끝난다. 배너~BANNER_END
68
68
  // 범위 안에서 REQUIRED_ENTRIES와 일치하는 줄만 개별 제거하고, 사용자가 그 사이/뒤에 추가한
69
- // 줄은 절대 건드리지 않는다 (issue #20 M7).
69
+ // 줄은 절대 건드리지 않는다.
70
70
  const BANNER =
71
71
  "\n" +
72
72
  "# ====================================================================\n" +
@@ -75,7 +75,7 @@ const BANNER =
75
75
 
76
76
  // REQUIRED_ENTRIES 뒤에 오는 종료 마커 — 배너 블록의 "끝"을 명확히 구분해, 그 사이에 사용자가
77
77
  // 다른 줄을 끼워넣어도 removeAutoAddedEntriesFromGitignore가 정확한 범위 안에서 개별 제거할 수
78
- // 있게 한다 (issue #20 M7). ensureGitignore가 기존 파일에 배너를 추가할 때만 함께 쓴다
78
+ // 있게 한다. ensureGitignore가 기존 파일에 배너를 추가할 때만 함께 쓴다
79
79
  // (신규 파일 생성 케이스는 NEW_FILE_CONTENT를 통째로 쓰고 종료 마커는 쓰지 않는다 —
80
80
  // startsWith 전체 prefix 매칭이라 별도 마커가 필요 없다).
81
81
  const BANNER_END = "# ==== project-auto-wizard: end of auto-added entries ====\n";
@@ -114,7 +114,7 @@ export function removeAutoAddedEntriesFromGitignore(targetRoot = ".") {
114
114
 
115
115
  // 종료 마커가 있으면(이 수정 이후 설치분) 그 범위 안에서 REQUIRED_ENTRIES만 개별적으로
116
116
  // 제거하고, 사용자가 그 사이에 끼워넣은 줄은 순서·연속 여부와 무관하게 그대로 보존한다
117
- // (issue #20 M7).
117
+ //.
118
118
  const endIdx = content.indexOf(BANNER_END, afterBanner);
119
119
  if (endIdx !== -1) {
120
120
  const region = content.slice(afterBanner, endIdx).split("\n");
@@ -126,7 +126,7 @@ export function removeAutoAddedEntriesFromGitignore(targetRoot = ".") {
126
126
  }
127
127
 
128
128
  // 종료 마커가 없는 구버전 설치(이 수정 이전) — 배너 직후 REQUIRED_ENTRIES가 연속으로
129
- // 이어지는 동안만 제거하는 기존 방식으로 폴백한다 (issue #7과 동일한 소급 미처리 원칙).
129
+ // 이어지는 동안만 제거하는 기존 방식으로 폴백한다 (동일한 소급 미처리 원칙).
130
130
  const lines = content.slice(afterBanner).split("\n");
131
131
  let consumed = 0;
132
132
  for (const line of lines) {