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
@@ -3,7 +3,7 @@
3
3
  // 실측 기준: template_integrator.sh 3282~3360, 3003~3012.
4
4
 
5
5
  // KEY 정규식: .sh는 [A-Z_]+ (대문자+언더스코어만). ask/auto/fallback 마커가 있는 라인만 대상.
6
- // fallback(이슈 #131)은 `KEY: ${{ 런타임값 || 'literal' }}` 표현식 안의 기본 리터럴을 교체하는 마커다.
6
+ // fallback은 `KEY: ${{ 런타임값 || 'literal' }}` 표현식 안의 기본 리터럴을 교체하는 마커다.
7
7
  const MARKER_RE = /#\s*@wizard\s+(ask|auto|fallback):(.*)$/;
8
8
  const KEY_RE = /^(\s*)([A-Z_]+):/;
9
9
  const PATHS_ANCHOR_RE = /#\s*@wizard\s+paths-anchor/;
@@ -19,7 +19,7 @@ export function parseWizardLine(line) {
19
19
 
20
20
  // YAML 큰따옴표 문자열 안에 안전하게 넣기 위한 이스케이프(백슬래시 우선 — 그래야 그 다음에 붙이는
21
21
  // 큰따옴표 이스케이프가 깨지지 않는다). @wizard 치환(setEnvLine)과 version.yml의 deploy 블록
22
- // (buildVersionYml, src/core/version-yml.js) 양쪽에서 재사용한다 — issue #20 L9는 두 지점 모두에서
22
+ // (buildVersionYml, src/core/version-yml.js) 양쪽에서 재사용한다 — 두 지점 모두에서
23
23
  // 발생하는 동일한 버그다.
24
24
  export function escapeYamlDoubleQuoted(value) {
25
25
  return String(value).replace(/\\/g, "\\\\").replace(/"/g, '\\"');
@@ -34,7 +34,7 @@ export function setEnvLine(line, key, value) {
34
34
  const body = cr ? line.slice(0, -1) : line;
35
35
  // 값 치환: KEY: "기존값" → KEY: "value"
36
36
  // 홑따옴표(KEY: '기존값')도 받는다 — 템플릿에 두 표기가 섞여 있는데 겹따옴표만 보면
37
- // 홑따옴표 줄의 @wizard 마커가 아무 경고 없이 무시된다(이슈 #81과 같은 실패 형태).
37
+ // 홑따옴표 줄의 @wizard 마커가 아무 경고 없이 무시된다(같은 실패 형태).
38
38
  // 치환 결과는 겹따옴표로 통일하고, 값은 그에 맞게 이스케이프한다.
39
39
  const escaped = escapeYamlDoubleQuoted(value);
40
40
  let out = body.replace(
@@ -71,7 +71,7 @@ export function resolveToken(name, type, resolvers = {}) {
71
71
 
72
72
  // __PROJECT_NAME__/__APP_ARTIFACT_NAME__ 전역 토큰을 repoName으로 치환.
73
73
  // substituteEnv()(설치 파일 본문)와 collectAsks()(마법사 화면 표시용 기본값) 양쪽에서
74
- // 재사용한다 — 두 곳이 서로 다른 로직으로 갈라지면 issue #110과 같은 표시 불일치가 재발한다.
74
+ // 재사용한다 — 두 곳이 서로 다른 로직으로 갈라지면 표시 불일치가 재발한다.
75
75
  export function replaceProjectTokens(text, repoName) {
76
76
  if (!text.includes("__PROJECT_NAME__") && !text.includes("__APP_ARTIFACT_NAME__")) return text;
77
77
  return text.replaceAll("__PROJECT_NAME__", repoName).replaceAll("__APP_ARTIFACT_NAME__", repoName);
package/src/index.js CHANGED
@@ -105,7 +105,7 @@ async function runInner(argv, {
105
105
  }
106
106
 
107
107
  // purge 모드 — 마법사가 만든 모든 산출물을 지워 설치 이전 상태로 완전히 되돌린다.
108
- // 개발·테스트 전용 숨김 모드 — --help/대화형 메뉴에 노출하지 않는다 (issue #6).
108
+ // 개발·테스트 전용 숨김 모드 — --help/대화형 메뉴에 노출하지 않는다.
109
109
  if (opts.mode === "purge") {
110
110
  if (!existsSync(join(cwd, ".git"))) {
111
111
  console.error("git 레포가 아닙니다(.git 없음) — purge는 git 레포 안에서만 실행할 수 있습니다.");
@@ -122,7 +122,7 @@ async function runInner(argv, {
122
122
  const existing = existsSync(vyPath) ? parseExisting(readFileSync(vyPath, "utf8")) : null;
123
123
  if (opts.dryRun) {
124
124
  printPurgePlan(planPurge(payload, cwd, keepFlags), { dryRun: true });
125
- // M4 (Fable 검토): develop 브랜치 삭제는 plan에 포함되지 않으므로(§6 — git 상태는 실행 시점에만
125
+ // develop 브랜치 삭제는 plan에 포함되지 않으므로(§6 — git 상태는 실행 시점에만
126
126
  // 판단 가능) 별도로 예고하지 않으면 dry-run 미리보기가 유일한 파괴적 동작을 사용자에게 숨기게 된다.
127
127
  if (opts.deleteDevelopBranch) {
128
128
  const developBranch = existing?.branches?.develop || "develop";
@@ -218,25 +218,25 @@ async function runInner(argv, {
218
218
  return 0;
219
219
  }
220
220
  // 명시 모드(full/version/workflows)인데 --force 없으면 TTY 여부와 무관하게 즉시 거부한다
221
- // (issue #19 — TTY에서 확인 없이 즉시 설치되던 결함 수정).
221
+ // (TTY에서 확인 없이 즉시 설치되던 결함 수정).
222
222
  // --dry-run은 파일을 쓰지 않으므로 --force 게이트를 우회한다 (status/doctor와 동일한 안전성).
223
223
  if (!opts.force && !opts.dryRun) {
224
224
  console.error("--force 없이는 이 모드를 실행할 수 없습니다 (확인 절차가 없습니다).");
225
225
  return 1;
226
226
  }
227
227
 
228
- // 기존 version.yml 로드 — version/version_code/project_paths 보존의 단일 진실 (.sh L2208~2239 SSoT)
228
+ // 기존 version.yml 로드 — version/version_code/project_paths 보존의 단일 진실 (.sh SSoT)
229
229
  const vyPath = join(cwd, "version.yml");
230
230
  const existing = existsSync(vyPath) ? parseExisting(readFileSync(vyPath, "utf8")) : null;
231
231
 
232
232
  // 감지 (CLI 인자 우선, 없으면 자동 감지 — version.yml 우선 규칙은 detectTypes/detectVersion 내부)
233
233
  const types = opts.types.length ? opts.types : detectTypes(cwd);
234
234
  // version: 기존 version.yml 최우선(SSoT — 재실행 시 덮어쓰기 방지) → CLI 지정 → 파일 감지
235
- // 비대화형이므로 폴백 안내는 CLI 문구(--project-version)를 그대로 쓴다 (이슈 #80).
235
+ // 비대화형이므로 폴백 안내는 CLI 문구(--project-version)를 그대로 쓴다.
236
236
  const detectWarnings = [];
237
237
  const version = (existing?.version) || opts.version
238
238
  || detectVersion(cwd, { warn: (m) => { detectWarnings.push(m); console.error(m); } });
239
- const versionCode = existing?.versionCode ?? detectBuildNumber(cwd, { types }) ?? 1; // 기존 빌드번호 보존, 신규 통합 시 프로젝트 파일에서 감지 (.sh L2208~2221, 이슈 #41)
239
+ const versionCode = existing?.versionCode ?? detectBuildNumber(cwd, { types }) ?? 1; // 기존 빌드번호 보존, 신규 통합 시 프로젝트 파일에서 감지 (.sh L2208~2221)
240
240
  const branch = detectDefaultBranch(cwd);
241
241
  const repoName = detectRepoName(cwd);
242
242
  // 경로 확정 (.sh resolve_project_paths 비대화형 경로 — --paths 우선 → 저장값 → 후보 1개 자동 → 에러)
@@ -283,9 +283,7 @@ async function runInner(argv, {
283
283
  mode: opts.mode, force: opts.force, types, version, versionCode, branch,
284
284
  branches,
285
285
  paths,
286
- // 옵션 워크플로우: CLI 플래그 최우선 → version.yml 저장 옵션(.sh read_template_options 등가) → false
287
- includeNexus: opts.includeNexus ?? existing?.options?.nexus ?? false,
288
- includeSecretBackup: opts.includeSecretBackup ?? existing?.options?.secretBackup ?? false,
286
+ // 옵션: CLI 플래그 최우선 → version.yml 저장 옵션 → 기본값
289
287
  // 기존 version.yml이 있는데 semver_auto 키가 아예 없었던 경우(신규 기능 추가 이전 설치·
290
288
  // workflows-only 재실행) 조용히 true로 켜지면 애매한 커밋 하나로 major가 승격될 위험이 있다 —
291
289
  // 기존 설치는 false로 안전하게 폴백, 완전 신규 설치만 true(기존 설계) 유지.
@@ -296,7 +294,7 @@ async function runInner(argv, {
296
294
  // 실 resolver 4종 (.sh resolve_token 등가)
297
295
  resolvers: makeResolvers(cwd, repoName, paths, flutterOptions),
298
296
  now, today,
299
- // 설치 로그(#79)용 부가 문맥 — 설치 동작 자체는 바꾸지 않는다.
297
+ // 설치 로그용 부가 문맥 — 설치 동작 자체는 바꾸지 않는다.
300
298
  markers: detectMarkers(cwd, types), detectWarnings,
301
299
  deployStyle: opts.deployStyle
302
300
  || (isDeployStyle(existing?.options?.deployStyle) ? existing.options.deployStyle : DEFAULT_DEPLOY_STYLE),
@@ -309,7 +307,7 @@ async function runInner(argv, {
309
307
 
310
308
  context.templateVersion = readTemplateVersion();
311
309
 
312
- // 비대화형 축약 배너 (#446 확정 — 1줄, 로그 오염 최소)
310
+ // 비대화형 축약 배너 (1줄, 로그 오염 최소)
313
311
  printBannerCompact({ version: context.templateVersion, mode: opts.mode });
314
312
 
315
313
  // Breaking Changes 게이트 (.sh execute_integration L4415~4420 등가 — 비대화형은 경고 후 진행)
@@ -322,8 +320,8 @@ async function runInner(argv, {
322
320
  }
323
321
 
324
322
  // opts.mode는 parseArgs()에서 화이트리스트 검증을 통과했고, interactive/purge/uninstall/status/doctor는
325
- // 전부 위에서 조기 반환했으므로 이 시점엔 full 하나로 보장된다 (issue #19 — default 분기 제거,
326
- // issue #70 — 부분 설치 모드 제거로 분기 자체가 사라졌다).
323
+ // 전부 위에서 조기 반환했으므로 이 시점엔 full 하나로 보장된다 (default 분기 제거,
324
+ // 부분 설치 모드 제거로 분기 자체가 사라졌다).
327
325
  const result = runFull(context, payload, cwd);
328
326
 
329
327
  // 완료 요약 (.sh print_summary — CLI 모드에서도 출력)
@@ -341,7 +339,7 @@ async function runInner(argv, {
341
339
  });
342
340
  // store_submit 배포 모드는 main push마다 심사를 자동 제출한다 — 비대화형에서도 같은 경고를 보여준다
343
341
  // (대화형 경로는 ui/prompts.js#deployModeWarning을 선택 시점에 note로 보여준다).
344
- // Flutter 타입이 아니거나 해당 스토어를 선택하지 않은 프로젝트에는 뜨면 안 된다 (fable5.1 Important #2).
342
+ // Flutter 타입이 아니거나 해당 스토어를 선택하지 않은 프로젝트에는 뜨면 안 된다.
345
343
  const { stores } = flutterOptions;
346
344
  const warnings = [];
347
345
  if (types.includes("flutter") && (stores === null || stores.includes("android"))) {
package/src/ui/banner.js CHANGED
@@ -1,4 +1,4 @@
1
- // 첫 화면 배너 (#446 층1) — 클래식 박스형 (사용자 확정 시안 A)
1
+ // 첫 화면 배너 — 클래식 박스형
2
2
  // .ps1 Print-Banner 계승 + 브랜딩을 project-auto-wizard로 갱신.
3
3
  import { A, paint, visualWidth } from "./ansi.js";
4
4
 
@@ -9,7 +9,7 @@ function boxLine(out, content = "") {
9
9
  out(paint("║", A.cyan) + content + " ".repeat(pad) + paint("║", A.cyan) + "\n");
10
10
  }
11
11
 
12
- // 대화형 첫 화면 배너 — 박스 타이틀 + 메타 4줄 (.ps1 Print-Banner 등가, #446 확정 시안 A)
12
+ // 대화형 첫 화면 배너 — 박스 타이틀 + 메타 4줄 (.ps1 Print-Banner 등가)
13
13
  export function printBanner({ version, modeLabel }, out = (s) => process.stderr.write(s)) {
14
14
  out("\n");
15
15
  out(paint(`╔${"═".repeat(INNER)}╗`, A.cyan) + "\n");
@@ -35,13 +35,12 @@ export function scopeString(usages = []) {
35
35
  // payloadRoot: 패키지 payload/ 루트. types: 설치 대상 타입 목록.
36
36
  // opts:
37
37
  // resolvers - @접두 기본값(@repo 등) 해석용 (.sh는 수집 시점에 resolve_token — 동일)
38
- // includeNexus - true면 server-deploy 제외 + nexus/ 포함 (복사 엔진과 스캔 범위 일치)
39
38
  // flutterStore - Flutter 스토어 대상(string[]|null). 배열이면 선택 해제된 스토어 워크플로우는 스캔에서 제외 (null=현행)
40
39
  // prompts - wizard-labels 파싱 객체 (워크플로우 표시명용, null이면 확장자 제거 폴백)
41
40
  // 반환: { keys:[], defaults:Map<key,default>, typeDefaults:Map<"type|key",default>,
42
41
  // usages:Map<key,[{type,workflowName}]> }
43
42
  export function collectAsks(payloadRoot, types = [], opts = {}) {
44
- const { resolvers = {}, includeNexus = false, includeSecretBackup = false, deployStyle = "", flutterStore = null, prompts = null } = opts;
43
+ const { resolvers = {}, deployStyle = "", flutterStore = null, prompts = null } = opts;
45
44
  // 설치하지 않을 배포 워크플로우의 질문까지 묻지 않는다 — 질문 수는 설치 범위를 따라간다.
46
45
  const keepDeploy = deployFilter(deployStyle);
47
46
  const baseDir = join(payloadRoot, PAYLOAD.workflowsDir);
@@ -51,16 +50,14 @@ export function collectAsks(payloadRoot, types = [], opts = {}) {
51
50
  const usages = new Map();
52
51
 
53
52
  // 스캔 단위: [타입, 폴더]. common/ 최상위는 타입 선택과 무관하게 항상 설치되므로(복사 엔진과
54
- // 동일 규칙 — issue #94) 무조건 스캔한다. secret-backup은 common 최상위가 아니라 그 하위
55
- // 폴더고, 파일 전체가 조건부로 설치되므로 포함하기로 한 경우에만 별도로 스캔한다 (이슈 #82) —
56
- // 종전에는 스캔 대상이 아니어서 my-project 같은 예시값이 질문 없이 그대로 설치됐다.
53
+ // 동일 규칙) 무조건 스캔한다.
57
54
  const units = [];
58
55
  const commonDir = join(baseDir, "common");
59
56
  if (exists(commonDir)) units.push(["common", commonDir, null]);
60
57
  for (const type of types) {
61
58
  const typeDir = join(baseDir, type);
62
59
  if (!exists(typeDir)) continue;
63
- // 복사 엔진과 동일한 폴더 구성: 타입 직하위 + (nexus면) nexus + (그 외엔, "배포 안 함"이
60
+ // 복사 엔진과 동일한 폴더 구성: 타입 직하위 + ("배포 안 함"이
64
61
  // 아닐 때만) server-deploy. server-deploy가 있는 타입은 "none"일 때 그 폴더(PR 프리뷰
65
62
  // 포함) 자체를 스캔에서 뺀다.
66
63
  // go/python처럼 CD가 server-deploy 없이 타입 루트에 바로 있는 타입은, "배포 안 함"일 때
@@ -69,13 +66,10 @@ export function collectAsks(payloadRoot, types = [], opts = {}) {
69
66
  // 배포 방식과 무관하게 항상 설치되는 별도 축이라 의도된 잔여 범위다.
70
67
  // Flutter는 선택 해제된 스토어 워크플로우(PLAYSTORE·TESTFLIGHT)도 같은 필터로 걸러 질문 범위가 설치 범위와 같다.
71
68
  units.push([type, typeDir, buildTypeRootFilter(type, deployStyle, flutterStore)]);
72
- if (includeNexus) {
73
- units.push([type, join(typeDir, "nexus"), null]);
74
- } else if (deployStyle !== NO_DEPLOY_STYLE) {
69
+ if (deployStyle !== NO_DEPLOY_STYLE) {
75
70
  units.push([type, join(typeDir, "server-deploy"), keepDeploy]);
76
71
  }
77
72
  }
78
- if (includeSecretBackup) units.push(["common", join(baseDir, "common", "secret-backup"), null]);
79
73
 
80
74
  for (const [type, dir, fileFilter] of units) {
81
75
  if (!exists(dir)) continue;
@@ -91,7 +85,7 @@ export function collectAsks(payloadRoot, types = [], opts = {}) {
91
85
  const rawDefault = p.arg.startsWith("@")
92
86
  ? resolveToken(p.arg.slice(1), type, resolvers)
93
87
  : p.arg;
94
- // 리터럴 기본값 안에 __PROJECT_NAME__ 등이 박혀 있으면(issue #110) 실제 repoName으로
88
+ // 리터럴 기본값 안에 __PROJECT_NAME__ 등이 박혀 있으면실제 repoName으로
95
89
  // 풀어준다 — substituteEnv()가 설치 파일에 적용하는 것과 동일한 치환이라야 마법사
96
90
  // 화면 표시와 실제 설치 결과가 어긋나지 않는다.
97
91
  const typeDefault = replaceProjectTokens(rawDefault, resolveToken("repo", type, resolvers));
@@ -111,7 +105,7 @@ function firstTypeFor(usages, key) {
111
105
  return usages.get(key)?.[0]?.type ?? "";
112
106
  }
113
107
 
114
- // 최종 답변 목록 (이슈 #79, #80) — 완료 요약과 설치 로그가 같은 데이터를 쓰도록 여기서 만든다.
108
+ // 최종 답변 목록 — 완료 요약과 설치 로그가 같은 데이터를 쓰도록 여기서 만든다.
115
109
  // isDefault는 "기본값 그대로인가"다. 나중에 배포가 안 될 때 제일 먼저 확인하게 되는 정보라
116
110
  // 값만 남기면 부족하다.
117
111
  function buildAnswers(prompts, asks, values, useDefaults) {
@@ -146,7 +140,7 @@ export function printFieldCard(prompts, key, info, idx = null, tot = null, log =
146
140
  log("");
147
141
  }
148
142
 
149
- // ask 필드의 기본값이 정확히 "true"/"false"면 boolean 필드로 간주한다 (이슈 #94).
143
+ // ask 필드의 기본값이 정확히 "true"/"false"면 boolean 필드로 간주한다.
150
144
  // 마커 문법(@wizard ask:...)을 바꾸지 않고 리터럴 값 형태만으로 판단 — 별도 타입 표기가 필요 없다.
151
145
  function isBooleanDefault(value) {
152
146
  return value === "true" || value === "false";
@@ -186,17 +180,17 @@ async function promptEach(io, prompts, asks, todoKeys, values, log) {
186
180
  // - useDefaults=false → values에 담긴 키만 사용자 확정값으로 치환, 나머지는 기본값
187
181
  // (⚠️ substituteEnv는 useDefaults=false일 때만 values를 참조하므로 이 플래그를 반드시 함께 전달)
188
182
  // 인자:
189
- // payloadRoot/types/resolvers/includeNexus/flutterStore — collectAsks와 동일 의미
183
+ // payloadRoot/types/resolvers/flutterStore — collectAsks와 동일 의미
190
184
  // targetRoot — wizard-prompts.yml 1차 탐색 위치(기본 ".")
191
185
  // force — true면 질문 없이 전부 기본값 (.sh FORCE_MODE 등가)
192
186
  // io — {select, multiselect, text} 주입 (기본 readline-engine). 테스트 스텁 지점.
193
187
  // log — 카드·안내 출력 함수 주입 (기본 stderr)
194
188
  export async function promptEnvPlan({
195
189
  payloadRoot, types = [], io = null, force = false, resolvers = {},
196
- includeNexus = false, includeSecretBackup = false, deployStyle = "", flutterStore = null, targetRoot = ".", repoName = "", log = defaultLog,
190
+ deployStyle = "", flutterStore = null, targetRoot = ".", repoName = "", log = defaultLog,
197
191
  } = {}) {
198
192
  const prompts = loadWizardPrompts(targetRoot, payloadRoot);
199
- const asks = collectAsks(payloadRoot, types, { resolvers, includeNexus, includeSecretBackup, deployStyle, flutterStore, prompts });
193
+ const asks = collectAsks(payloadRoot, types, { resolvers, deployStyle, flutterStore, prompts });
200
194
  const defaults = asks.defaults;
201
195
 
202
196
  // 수집 키 0개 → 질문 자체가 없음 (.sh `[ ${#WF_ASK_KEYS[@]} -eq 0 ]` 등가)
package/src/ui/prompts.js CHANGED
@@ -34,19 +34,14 @@ export async function confirmProjectMenu() {
34
34
  });
35
35
  }
36
36
 
37
- // 수정 메뉴 항목 — showOptional=full/workflows에서만 nexus/secret 노출,
38
- // showFlutter=Flutter 타입일 때만 환경변수 방식/스토어 배포 대상/배포 모드 노출 (이슈 #131).
37
+ // 수정 메뉴 항목 — showFlutter=Flutter 타입일 때만 환경변수 방식/스토어 배포 대상/배포 모드 노출.
39
38
  // 라벨·순서를 테스트할 수 있도록 순수 함수로 분리했다.
40
- export function editMenuOptions({ showOptional = false, showFlutter = false } = {}) {
39
+ export function editMenuOptions({ showFlutter = false } = {}) {
41
40
  const options = [
42
41
  { value: "type", label: "프로젝트 타입" },
43
42
  { value: "version", label: "버전" },
44
43
  { value: "branch", label: "기본 브랜치" },
45
44
  ];
46
- if (showOptional) {
47
- options.push({ value: "nexus", label: "Nexus publish 포함 여부" });
48
- options.push({ value: "secret", label: "Secret 백업 포함 여부" });
49
- }
50
45
  if (showFlutter) {
51
46
  options.push({ value: "envMode", label: "환경변수 방식" });
52
47
  options.push({ value: "flutterStore", label: "스토어 배포 대상" });
@@ -57,8 +52,8 @@ export function editMenuOptions({ showOptional = false, showFlutter = false } =
57
52
  }
58
53
 
59
54
  // 수정 메뉴 — 어떤 항목을 고칠지.
60
- export async function editMenu({ showOptional = false, showFlutter = false } = {}) {
61
- return engine.select({ message: "어떤 항목을 수정할까요?", options: editMenuOptions({ showOptional, showFlutter }) });
55
+ export async function editMenu({ showFlutter = false } = {}) {
56
+ return engine.select({ message: "어떤 항목을 수정할까요?", options: editMenuOptions({ showFlutter }) });
62
57
  }
63
58
 
64
59
  const ALL_TYPES = ["spring", "flutter", "next", "react", "react-native", "react-native-expo", "node", "python", "basic", "go"];
@@ -73,7 +68,7 @@ export async function selectTypes(current = []) {
73
68
  });
74
69
  }
75
70
 
76
- // 감지 직후 타입 확정 (이슈 #78). selectTypes와 달리 감지 근거 파일을 라벨에 붙여
71
+ // 감지 직후 타입 확정. selectTypes와 달리 감지 근거 파일을 라벨에 붙여
77
72
  // "왜 이렇게 판단했는지"를 보여준다 — 근거가 보여야 맞는지 틀린지 판단할 수 있다.
78
73
  // 감지 결과가 맞으면 Enter 한 번으로 끝난다.
79
74
  export async function confirmTypes({ types = [], markers = null } = {}) {
@@ -95,7 +90,7 @@ export async function confirmTypes({ types = [], markers = null } = {}) {
95
90
  });
96
91
  }
97
92
 
98
- // 배포 방식 선택 (이슈 #80, #126). 서버 배포 CD 워크플로우는 서로 대체재라 하나만 쓴다.
93
+ // 배포 방식 선택. 서버 배포 CD 워크플로우는 서로 대체재라 하나만 쓴다.
99
94
  // 고른 것만 설치하고 push 트리거까지 켜준다 — 종전에는 넷을 다 깔고 SIMPLE만 켜져 있어,
100
95
  // 무중단을 원한 사람은 설치 후 YAML을 직접 고쳐야 했다.
101
96
  // "서버 배포 안 함"은 server-deploy 폴더 자체(PR 프리뷰 포함)를 제외한다 — 서버 배포를
@@ -116,7 +111,7 @@ export async function selectDeployStyle() {
116
111
  });
117
112
  }
118
113
 
119
- // ── Flutter 옵션 (이슈 #131) ─────────────────────────────────────────
114
+ // ── Flutter 옵션 ─────────────────────────────────────────
120
115
  // 프로젝트 타입에 flutter가 포함된 경우에만 interactive.js가 묻는다. 세 함수 모두 취소(ESC) 시 CANCEL을
121
116
  // 그대로 돌려주고, "ESC = 기본값" 처리는 호출부가 한다 (selectDeployStyle과 같은 규약).
122
117
  const ENV_MODE_LABELS = {
@@ -188,7 +183,7 @@ export function deployModeWarning(mode) {
188
183
  return mode === "store_submit" ? "store_submit을 고르면 main push마다 심사가 자동 제출됩니다." : "";
189
184
  }
190
185
 
191
- // 브랜치 전략 선택 (이슈 #93). 종전에는 "릴리스 브랜치"/"개발 브랜치" 두 질문에
186
+ // 브랜치 전략 선택. 종전에는 "릴리스 브랜치"/"개발 브랜치" 두 질문에
192
187
  // 같은 이름을 입력해야만 trunk-based가 됐는데, 그 규칙이 사전에 안내되지 않아
193
188
  // 의도치 않게 pr-flow로 흘러갔다. 전략을 먼저 명시적으로 고르게 해 이를 없앤다.
194
189
  // 옵션 순서(pr-flow 먼저)는 비-TTY 환경의 기본값과 직결되므로 바꾸지 않는다.
@@ -225,7 +220,7 @@ export function outro(text) { engine.outro(text); }
225
220
  export function note(text, title) { engine.note(text, title); }
226
221
  export function cancelMessage(text = "취소했습니다.") { engine.cancelMessage(text); }
227
222
 
228
- // ── #446 첫 화면 UI 5층 + SP2-C 대화형 계층 실물 io ─────────────────
223
+ // ── 첫 화면 UI 5층 + 대화형 계층 실물 io ─────────────────
229
224
  // runInteractive는 io.<method>?.() 옵셔널 호출 — 테스트 스텁은 이 메서드들을 생략해
230
225
  // 시각 층·env 질문을 건너뛴다 (실행 계약은 그대로).
231
226
  import { printBanner as _printBanner } from "./banner.js";
@@ -1,6 +1,6 @@
1
- // 첫 화면 상태 표시 층 (#446 층2·3·5) — 감지 로그 · 분석 카드 · 신규/업데이트 판별
2
- // (층5의 Breaking Changes 박스는 core/breaking-check.js가 담당.
3
- // 원본의 층4 IDE Skills 상태는 project-auto-wizard 스코프 제외 — Agent Skills 미포함)
1
+ // 첫 화면 상태 표시 층 — 감지 로그 · 분석 카드 · 신규/업데이트 판별
2
+ // (Breaking Changes 박스는 core/breaking-check.js가 담당.
3
+ // 원본의 IDE Skills 상태는 project-auto-wizard 스코프 제외 — Agent Skills 미포함)
4
4
  import { A, paint } from "./ansi.js";
5
5
  import { DEFAULT_DEPLOY_MODE } from "../core/flutter-options.js";
6
6
 
@@ -8,10 +8,10 @@ const GUT = paint("│", A.gray);
8
8
  const HEAD = paint("◆", A.cyan);
9
9
  const OK = paint("✓", A.green);
10
10
 
11
- // 층2 — 감지 로그 (.ps1 감지 진행 표시 등가)
12
- // markers: Map<type, 실제 발견 파일> (이슈 #77).
11
+ // 감지 로그 (.ps1 감지 진행 표시 등가)
12
+ // markers: Map<type, 실제 발견 파일>.
13
13
  // warnings: 감지 도중 나온 경고. 감지 함수를 먼저 호출한 뒤 박스를 그리는 구조라 경고가
14
- // 박스 위로 새어나가 앞선 질문에 대한 경고처럼 보였다 — 박스 안에서 출력한다 (이슈 #80).
14
+ // 박스 위로 새어나가 앞선 질문에 대한 경고처럼 보였다 — 박스 안에서 출력한다.
15
15
  export function printDetectionLog({ types = [], version = "", branch = "", markers = new Map(), warnings = [] },
16
16
  out = (s) => process.stderr.write(s)) {
17
17
  out(`${paint("┌", A.gray)} 🔍 프로젝트를 살펴보는 중...\n`);
@@ -28,9 +28,9 @@ export function printDetectionLog({ types = [], version = "", branch = "", marke
28
28
  out(`${GUT}\n`);
29
29
  }
30
30
 
31
- // 층3 — 프로젝트 분석 개요 카드 (.ps1 Print-ProjectAnalysis 등가+)
31
+ // 프로젝트 분석 개요 카드 (.ps1 Print-ProjectAnalysis 등가+)
32
32
  export function printAnalysisCard({ mode = "", modeLabel = "", types = [], version = "", branch = "",
33
- includeNexus = null, includeSecretBackup = null, paths = new Map(), showOptional = false,
33
+ paths = new Map(), showOptional = false,
34
34
  flutter = null, envModeDefault = "" },
35
35
  out = (s) => process.stderr.write(s)) {
36
36
  out(`${HEAD} ${paint("프로젝트 분석 결과", A.bold)}\n`);
@@ -40,9 +40,7 @@ export function printAnalysisCard({ mode = "", modeLabel = "", types = [], versi
40
40
  row("🌿", "브랜치", branch);
41
41
  if (modeLabel || mode) row("💫", "통합 모드", modeLabel || mode);
42
42
  if (showOptional) {
43
- row("📦", "Nexus", includeNexus === true ? paint("포함", A.green) : paint("제외", A.dim));
44
- row("🔐", "Secret백업", includeSecretBackup === true ? paint("포함", A.green) : paint("제외", A.dim));
45
- // Flutter 옵션 (이슈 #131 fable5.1 리뷰 Important #1) — 확정 직전 화면에서도 선택값을 보여준다.
43
+ // Flutter 옵션 — 확정 직전 화면에서도 선택값을 보여준다.
46
44
  if (flutter && types.includes("flutter")) {
47
45
  const stores = flutter.stores ?? [];
48
46
  const modeParts = stores.map(
@@ -61,7 +59,7 @@ export function printAnalysisCard({ mode = "", modeLabel = "", types = [], versi
61
59
  out(`${GUT}\n`);
62
60
  }
63
61
 
64
- // 층5 — 신규 통합 vs 업데이트 판별 라인 (Breaking 박스는 breaking-check.js)
62
+ // 신규 통합 vs 업데이트 판별 라인 (Breaking 박스는 breaking-check.js)
65
63
  export function printInstallKind({ currentTemplateVersion = "", templateVersion = "" }, out = (s) => process.stderr.write(s)) {
66
64
  if (currentTemplateVersion) {
67
65
  out(`${GUT} ♻️ ${paint("업데이트", A.bold)} — 템플릿 ${paint(`v${currentTemplateVersion}`, A.dim)} → ${paint(`v${templateVersion}`, A.green)}\n`);
package/src/ui/summary.js CHANGED
@@ -7,9 +7,9 @@ const SEPARATOR = "────────────────────
7
7
 
8
8
  export function printSummary(ctx) {
9
9
  const { mode, types = [], version = "", versionCode = null, copiedFiles = [], branches = null, gitignoreUpdated = false,
10
- // 설치 후 검증·기록 (#79, #80, #81)
10
+ // 설치 후 검증·기록
11
11
  answers = [], unresolved = [], secrets = new Map(), logPath = "", legacyMdLogs = false, cleanup = null,
12
- // Flutter 스토어 배포 (이슈 #131) — 앱 파일 생성/유지와 스토어 선택 해제 정리 결과
12
+ // Flutter 스토어 배포 — 앱 파일 생성/유지와 스토어 선택 해제 정리 결과
13
13
  flutterApp = null, storeCleanup = null } = ctx || {};
14
14
  const err = (s = "") => process.stderr.write(`${s}\n`);
15
15
  // 색상은 ansi.js의 공용 가드로 통일 (NO_COLOR + stderr TTY 여부)
@@ -69,7 +69,7 @@ export function printSummary(ctx) {
69
69
  err("추가된 워크플로우:");
70
70
 
71
71
  // 실제로 이번 실행에서 복사된 파일만 분류한다 (copyWorkflows()가 반환한 copiedFiles —
72
- // 디렉터리 재스캔은 재실행 시 skip된 파일까지 "새로 설치됨"으로 보여주는 결함이 있었다, issue #19).
72
+ // 디렉터리 재스캔은 재실행 시 skip된 파일까지 "새로 설치됨"으로 보여주는 결함이 있었다).
73
73
  const commonWorkflows = [];
74
74
  const typeWorkflows = [];
75
75
  const typePrefixes = types.map((t) => `${WORKFLOW_PREFIX}-${t.toUpperCase()}-`);
@@ -96,7 +96,7 @@ export function printSummary(ctx) {
96
96
  err(" └─ issue_helper.py");
97
97
  err("");
98
98
 
99
- // 입력한 환경설정 값 (#80) — 마지막으로 눈으로 검산할 기회. 종전에는 답변이 워크플로우
99
+ // 입력한 환경설정 값 — 마지막으로 눈으로 검산할 기회. 종전에는 답변이 워크플로우
100
100
  // YAML 안으로만 사라져, 오타를 내도 배포가 실패한 뒤에야 알 수 있었다.
101
101
  if (answers.length) {
102
102
  err(" ⚙️ 적용된 환경설정:");
@@ -106,11 +106,11 @@ export function printSummary(ctx) {
106
106
  }
107
107
  err("");
108
108
  }
109
- // 배포 방식을 바꿔 재설치한 경우, 이전 CD를 어떻게 처리했는지 알린다 (#80).
109
+ // 배포 방식을 바꿔 재설치한 경우, 이전 CD를 어떻게 처리했는지 알린다.
110
110
  printCleanup(err, enabled, "이전 배포 방식 정리", cleanup);
111
- // 스토어 배포 대상을 해제한 경우도 같은 규칙으로 정리한 결과를 알린다 (#131).
111
+ // 스토어 배포 대상을 해제한 경우도 같은 규칙으로 정리한 결과를 알린다.
112
112
  printCleanup(err, enabled, "선택 해제한 스토어 배포 정리", storeCleanup);
113
- // Fastfile·ExportOptions.plist는 사용자 소유라 없을 때만 만든다 — 만든 것과 그대로 둔 것을 나눠 보여준다 (#131).
113
+ // Fastfile·ExportOptions.plist는 사용자 소유라 없을 때만 만든다 — 만든 것과 그대로 둔 것을 나눠 보여준다.
114
114
  const { created: appCreated = [], kept: appKept = [] } = flutterApp || {};
115
115
  if (appCreated.length || appKept.length) {
116
116
  err(" 📱 Flutter 스토어 배포 파일:");
@@ -151,7 +151,7 @@ export function printSummary(ctx) {
151
151
  let step = 0;
152
152
  const num = () => ["1️⃣ ", "2️⃣ ", "3️⃣ ", "4️⃣ ", "5️⃣ "][step++] || " •";
153
153
 
154
- // 미치환 플레이스홀더 (#81) — 이 상태로는 해당 워크플로우가 동작하지 않으므로 제일 먼저 알린다.
154
+ // 미치환 플레이스홀더 — 이 상태로는 해당 워크플로우가 동작하지 않으므로 제일 먼저 알린다.
155
155
  if (unresolved.length) {
156
156
  err(` ${num()} ${paint("값이 채워지지 않은 항목이 있습니다 — 직접 채워야 동작합니다", A.red, enabled)}`);
157
157
  for (const u of unresolved) {
@@ -160,7 +160,7 @@ export function printSummary(ctx) {
160
160
  err("");
161
161
  }
162
162
 
163
- // 설치된 워크플로우가 실제로 요구하는 Secret (#80) — 종전에는 하나도 안내되지 않아
163
+ // 설치된 워크플로우가 실제로 요구하는 Secret — 종전에는 하나도 안내되지 않아
164
164
  // "설치 성공"인데 배포는 돌지 않는 상태로 끝났다.
165
165
  if (secrets.size) {
166
166
  err(` ${num()} 아래 GitHub Secret을 등록해야 배포 워크플로우가 동작합니다 (${secrets.size}개)`);