project-auto-wizard 0.11.0 → 0.12.1
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.
- package/README.md +24 -21
- package/package.json +2 -2
- package/payload/config/breaking-changes.json +1 -1
- package/payload/scripts/__pycache__/changelog_manager.cpython-314.pyc +0 -0
- package/payload/scripts/__pycache__/issue_helper.cpython-314.pyc +0 -0
- package/payload/scripts/__pycache__/version_manager.cpython-314.pyc +0 -0
- package/payload/scripts/changelog_manager.py +84 -31
- package/payload/scripts/version_manager.py +2 -2
- package/payload/version.yml.template +4 -2
- package/payload/workflows/common/PROJECT-COMMON-AI-PR-SUMMARY.yaml +30 -10
- package/payload/workflows/common/PROJECT-COMMON-AUTO-CHANGELOG-CONTROL.yaml +37 -12
- package/payload/workflows/common/PROJECT-COMMON-ISSUE-HELPER.yaml +3 -6
- package/payload/workflows/common/PROJECT-COMMON-README-VERSION-UPDATE.yaml +0 -12
- package/payload/workflows/common/PROJECT-COMMON-RELEASE-PUBLISH.yaml +27 -6
- package/payload/workflows/common/PROJECT-COMMON-VERSION-CONTROL.yaml +3 -7
- package/payload/workflows/flutter/PROJECT-FLUTTER-ANDROID-FIREBASE-CICD.yaml +7 -34
- package/payload/workflows/flutter/PROJECT-FLUTTER-ANDROID-PLAYSTORE-CICD.yaml +2 -25
- package/payload/workflows/flutter/PROJECT-FLUTTER-ANDROID-SELFHOSTED-CICD.yaml +2 -20
- package/payload/workflows/flutter/PROJECT-FLUTTER-ANDROID-TEST-APK.yaml +5 -30
- package/payload/workflows/flutter/PROJECT-FLUTTER-APP-BUILD-TRIGGER.yaml +0 -4
- package/payload/workflows/flutter/PROJECT-FLUTTER-CI.yaml +7 -51
- package/payload/workflows/flutter/PROJECT-FLUTTER-IOS-TEST-TESTFLIGHT.yaml +5 -29
- package/payload/workflows/flutter/PROJECT-FLUTTER-IOS-TESTFLIGHT.yaml +9 -29
- package/payload/workflows/go/PROJECT-GO-CI.yaml +5 -28
- package/payload/workflows/go/PROJECT-GO-PR-PREVIEW.yaml +19 -99
- package/payload/workflows/go/PROJECT-GO-SIMPLE-CICD.yaml +18 -89
- package/payload/workflows/next/PROJECT-NEXT-CI.yaml +11 -41
- package/payload/workflows/next/PROJECT-NEXT-CICD.yaml +9 -45
- package/payload/workflows/python/PROJECT-PYTHON-CI.yaml +1 -27
- package/payload/workflows/python/PROJECT-PYTHON-PR-PREVIEW.yaml +14 -97
- package/payload/workflows/python/PROJECT-PYTHON-SIMPLE-CICD.yaml +14 -92
- package/payload/workflows/react/PROJECT-REACT-CI.yaml +11 -40
- package/payload/workflows/react/PROJECT-REACT-CICD.yaml +9 -43
- package/payload/workflows/spring/{nexus/PROJECT-SPRING-NEXUS-CI.yml → PROJECT-SPRING-CI.yml} +2 -25
- package/payload/workflows/spring/server-deploy/PROJECT-SPRING-NONSTOP-NGINX-CICD.yaml +19 -63
- package/payload/workflows/spring/server-deploy/PROJECT-SPRING-NONSTOP-TRAEFIK-CICD.yaml +19 -59
- package/payload/workflows/spring/server-deploy/PROJECT-SPRING-PR-PREVIEW.yaml +23 -86
- package/payload/workflows/spring/server-deploy/PROJECT-SPRING-SIMPLE-CICD.yaml +20 -80
- package/src/cli/args.js +8 -15
- package/src/cli/help.js +2 -3
- package/src/commands/doctor.js +9 -8
- package/src/commands/dry-run.js +1 -1
- package/src/commands/full.js +9 -10
- package/src/commands/interactive-flutter.js +1 -1
- package/src/commands/interactive.js +34 -46
- package/src/commands/purge.js +2 -2
- package/src/commands/status.js +5 -6
- package/src/commands/uninstall.js +3 -4
- package/src/context.js +5 -6
- package/src/core/assets.js +2 -2
- package/src/core/baseline.js +1 -1
- package/src/core/branches.js +3 -3
- package/src/core/breaking-check.js +1 -1
- package/src/core/breaking.js +1 -1
- package/src/core/copy/flutter-app.js +2 -2
- package/src/core/copy/gitignore.js +8 -8
- package/src/core/copy/workflows.js +24 -69
- package/src/core/deploy-style.js +2 -2
- package/src/core/detect-fs.js +11 -11
- package/src/core/detect.js +7 -7
- package/src/core/flutter-options.js +1 -1
- package/src/core/installed-stores.js +1 -1
- package/src/core/logger.js +1 -1
- package/src/core/paths-resolve.js +1 -1
- package/src/core/removal-plan.js +4 -4
- package/src/core/verify.js +4 -4
- package/src/core/version-yml.js +27 -34
- package/src/core/wizard-env.js +4 -4
- package/src/index.js +14 -14
- package/src/ui/banner.js +2 -2
- package/src/ui/env-plan.js +10 -16
- package/src/ui/prompts.js +9 -14
- package/src/ui/status-cards.js +10 -12
- package/src/ui/summary.js +13 -13
- package/payload/workflows/common/secret-backup/PROJECT-COMMON-SECRET-FILE-UPLOAD.yaml +0 -210
- package/payload/workflows/spring/nexus/PROJECT-SPRING-GITHUB-PACKAGES-PUBLISH.yml +0 -92
- package/payload/workflows/spring/nexus/PROJECT-SPRING-NEXUS-PUBLISH.yml +0 -70
- package/src/core/options-ask.js +0 -102
package/src/core/version-yml.js
CHANGED
|
@@ -4,12 +4,12 @@ import {
|
|
|
4
4
|
ENV_MODES, DEPLOY_MODES, DEFAULT_ENV_MODE, DEFAULT_DEPLOY_MODE, STORE_PLATFORMS, formatStoreList,
|
|
5
5
|
} from "./flutter-options.js";
|
|
6
6
|
|
|
7
|
-
// version.yml 파싱·생성 (.sh create_version_yml 등가, 전체 재생성 전략
|
|
7
|
+
// version.yml 파싱·생성 (.sh create_version_yml 등가, 전체 재생성 전략).
|
|
8
8
|
// ⚠️ YAML 재직렬화 금지 — 주석이 데이터.
|
|
9
9
|
// 레이아웃 단일 진실 = payload/version.yml.template (호출부가 templateText로 주입).
|
|
10
10
|
|
|
11
11
|
// version.yml.template이 아는 최상위 키 — 이 밖의 최상위 키는 사용자가 직접 추가한 것으로 간주한다.
|
|
12
|
-
// "project_type"(단수)은 더 이상 렌더하지 않는 레거시 키지만 이 집합에는
|
|
12
|
+
// "project_type"(단수)은 더 이상 렌더하지 않는 레거시 키지만 이 집합에는 남겨둔다:
|
|
13
13
|
// 빼면 기존 파일의 단수 줄이 "사용자가 추가한 필드"로 오인돼 재생성 때 되살아난다. 아는 키로
|
|
14
14
|
// 둬야 재통합 시 흡수되어 사라진다.
|
|
15
15
|
const KNOWN_TOP_LEVEL_KEYS = new Set([
|
|
@@ -17,14 +17,14 @@ const KNOWN_TOP_LEVEL_KEYS = new Set([
|
|
|
17
17
|
]);
|
|
18
18
|
|
|
19
19
|
// 최상위 레벨의 알려지지 않은 필드(사용자가 직접 추가한 임의 필드)를 원본 그대로 보존한다
|
|
20
|
-
|
|
20
|
+
//. 각 알려지지 않은 최상위 키부터 다음 최상위 키 직전까지를 통째로 한 블록으로
|
|
21
21
|
// 캡처한다(중첩 구조가 있어도 유효한 YAML로 남기기 위함). metadata/project_paths/deploy처럼
|
|
22
|
-
// 이 모듈이 이미 아는 블록 "내부"의 알려지지 않은 하위 키는 대상이 아니다(범위 밖
|
|
22
|
+
// 이 모듈이 이미 아는 블록 "내부"의 알려지지 않은 하위 키는 대상이 아니다(범위 밖).
|
|
23
23
|
export function parseExtraTopLevel(content) {
|
|
24
24
|
const blocks = [];
|
|
25
25
|
let current = null;
|
|
26
26
|
for (const line of String(content || "").split("\n")) {
|
|
27
|
-
// 최상위 키는 하이픈을 포함할 수 있다(YAML 관례)
|
|
27
|
+
// 최상위 키는 하이픈을 포함할 수 있다(YAML 관례).
|
|
28
28
|
const m = line.match(/^([a-zA-Z_][a-zA-Z0-9_-]*):/);
|
|
29
29
|
if (m) {
|
|
30
30
|
if (current) blocks.push(current.join("\n"));
|
|
@@ -37,20 +37,19 @@ export function parseExtraTopLevel(content) {
|
|
|
37
37
|
return blocks;
|
|
38
38
|
}
|
|
39
39
|
|
|
40
|
-
// Flutter 옵션 키
|
|
40
|
+
// Flutter 옵션 키 → 반환 필드. 값은 원문 문자열로 돌려주고, 유효성 판정은 resolveFlutterOptions 몫이다.
|
|
41
41
|
const FLUTTER_OPTION_KEYS = {
|
|
42
42
|
env_mode: "envMode", flutter_store: "flutterStore",
|
|
43
43
|
android_deploy_mode: "androidDeployMode", ios_deploy_mode: "iosDeployMode",
|
|
44
44
|
};
|
|
45
45
|
|
|
46
46
|
// metadata.template.options 상태머신 파싱 (.sh read_template_options L2361~2416 등가).
|
|
47
|
-
// 반환: {
|
|
47
|
+
// 반환: { semverAuto: bool|null, copilotAi: bool|null, deployStyle: string|null,
|
|
48
48
|
// envMode/flutterStore/androidDeployMode/iosDeployMode: string|null } — null=미기재.
|
|
49
49
|
// 구 synology·coderabbit 키 등 다른 키는 어느 분기에도 안 걸려 자연히 무시된다(파싱 에러 없음).
|
|
50
|
-
// (options-ask.js가 이 함수를 import한다 — 순환 방지 위해 여기(version-yml)에 정의.)
|
|
51
50
|
export function parseTemplateOptions(content) {
|
|
52
51
|
const out = {
|
|
53
|
-
|
|
52
|
+
semverAuto: null, copilotAi: null, deployStyle: null,
|
|
54
53
|
envMode: null, flutterStore: null, androidDeployMode: null, iosDeployMode: null,
|
|
55
54
|
};
|
|
56
55
|
// 값 정규화: 따옴표 제거 + 트림 (.sh tr -d '"' | tr -d "'" | xargs 등가)
|
|
@@ -63,21 +62,7 @@ export function parseTemplateOptions(content) {
|
|
|
63
62
|
if (/^\s*template:/.test(line)) { inTemplate = true; continue; }
|
|
64
63
|
if (inTemplate && /^\s+options:/.test(line)) { inOptions = true; continue; }
|
|
65
64
|
if (inTemplate && inOptions) {
|
|
66
|
-
let m = line.match(/^\s+
|
|
67
|
-
if (m) {
|
|
68
|
-
const v = strip(m[1]);
|
|
69
|
-
if (v === "true") out.nexus = true;
|
|
70
|
-
if (v === "false") out.nexus = false;
|
|
71
|
-
continue;
|
|
72
|
-
}
|
|
73
|
-
m = line.match(/^\s+secret_backup:\s*(.+)/);
|
|
74
|
-
if (m) {
|
|
75
|
-
const v = strip(m[1]);
|
|
76
|
-
if (v === "true") out.secretBackup = true;
|
|
77
|
-
if (v === "false") out.secretBackup = false;
|
|
78
|
-
continue;
|
|
79
|
-
}
|
|
80
|
-
m = line.match(/^\s+deploy_style:\s*(.+)/);
|
|
65
|
+
let m = line.match(/^\s+deploy_style:\s*(.+)/);
|
|
81
66
|
if (m) { const v = strip(m[1]); if (v) out.deployStyle = v; continue; }
|
|
82
67
|
m = line.match(/^\s+(env_mode|flutter_store|android_deploy_mode|ios_deploy_mode):\s*(.+)/);
|
|
83
68
|
if (m) { const v = strip(m[2]); if (v) out[FLUTTER_OPTION_KEYS[m[1]]] = v; continue; }
|
|
@@ -88,6 +73,13 @@ export function parseTemplateOptions(content) {
|
|
|
88
73
|
if (v === "false") out.semverAuto = false;
|
|
89
74
|
continue;
|
|
90
75
|
}
|
|
76
|
+
m = line.match(/^\s+copilot_ai:\s*(.+)/);
|
|
77
|
+
if (m) {
|
|
78
|
+
const v = strip(m[1]);
|
|
79
|
+
if (v === "true") out.copilotAi = true;
|
|
80
|
+
if (v === "false") out.copilotAi = false;
|
|
81
|
+
continue;
|
|
82
|
+
}
|
|
91
83
|
// 들여쓰기 0~4칸의 다른 키 → options 섹션 종료 (.sh L2404~2408)
|
|
92
84
|
if (/^\s{0,4}[a-z_]+:/.test(line)) { inOptions = false; inTemplate = false; }
|
|
93
85
|
}
|
|
@@ -139,7 +131,7 @@ export function parseExisting(content) {
|
|
|
139
131
|
if (/^\S/.test(l)) break;
|
|
140
132
|
}
|
|
141
133
|
}
|
|
142
|
-
// 선택 워크플로우 옵션 (metadata.template.options
|
|
134
|
+
// 선택 워크플로우 옵션 (metadata.template.options)
|
|
143
135
|
const options = parseTemplateOptions(text);
|
|
144
136
|
// metadata.template.branches — main/develop/mode (업데이트 모드 재질문 생략용)
|
|
145
137
|
const branches = parseTemplateBranches(text);
|
|
@@ -187,13 +179,13 @@ function buildFlutterOptionsBlock({ envMode, stores, androidDeployMode, iosDeplo
|
|
|
187
179
|
// version.yml 전체 생성 — payload/version.yml.template 렌더링.
|
|
188
180
|
// opts: { templateText, version, types:[], paths:Map, pathMarkers?:Map,
|
|
189
181
|
// branch, branches?, versionCode, now, today, templateOptions?, deployValues?,
|
|
190
|
-
// extraTopLevel?:string[], ← 기존 version.yml의 알려지지 않은 최상위 필드 보존
|
|
191
|
-
// flutterOptions?:{ envMode, stores, androidDeployMode, iosDeployMode } } ← Flutter 타입일 때만 렌더
|
|
182
|
+
// extraTopLevel?:string[], ← 기존 version.yml의 알려지지 않은 최상위 필드 보존
|
|
183
|
+
// flutterOptions?:{ envMode, stores, androidDeployMode, iosDeployMode } } ← Flutter 타입일 때만 렌더
|
|
192
184
|
// templateText = payload/version.yml.template 원문 (readVersionYmlTemplate — 필수)
|
|
193
185
|
// now = "YYYY-MM-DD HH:MM:SS" (UTC) — 결정성 위해 주입 / today = "YYYY-MM-DD"
|
|
194
186
|
// branches = { main, develop, mode } (resolveBranchConfig 결과. 없으면 branch 기반 기본값)
|
|
195
187
|
// pathMarkers = Map<type, markerFilename> (project_paths 주석용)
|
|
196
|
-
// templateOptions = { templateVersion,
|
|
188
|
+
// templateOptions = { templateVersion, optionsDate }
|
|
197
189
|
export function buildVersionYml({
|
|
198
190
|
templateText, version, types = [], paths = new Map(), pathMarkers = new Map(),
|
|
199
191
|
branch = "main", branches = null, versionCode = 1, now, today,
|
|
@@ -203,8 +195,8 @@ export function buildVersionYml({
|
|
|
203
195
|
const typesJson = types.length ? `[${types.map((t) => `"${t}"`).join(", ")}]` : `["basic"]`;
|
|
204
196
|
const b = branches || { main: branch || "main", develop: "develop", mode: "pr-flow" };
|
|
205
197
|
const {
|
|
206
|
-
templateVersion = "unknown",
|
|
207
|
-
includeSemverAuto = true, deployStyle = "", optionsDate = today,
|
|
198
|
+
templateVersion = "unknown",
|
|
199
|
+
includeSemverAuto = true, includeCopilotAi = false, deployStyle = "", optionsDate = today,
|
|
208
200
|
} = templateOptions || {};
|
|
209
201
|
|
|
210
202
|
// project_paths 블록 (full-line 토큰 {{PROJECT_PATHS}} — 없으면 라인 제거)
|
|
@@ -227,7 +219,7 @@ export function buildVersionYml({
|
|
|
227
219
|
for (const t of deployTypes) {
|
|
228
220
|
rows.push(` ${t}:`);
|
|
229
221
|
// 동일한 이스케이프를 재사용 — deploy 값도 @wizard ask 값과 같은 경로로 들어오므로
|
|
230
|
-
// 큰따옴표가 섞이면 setEnvLine과 동일하게 YAML이 깨진다 (
|
|
222
|
+
// 큰따옴표가 섞이면 setEnvLine과 동일하게 YAML이 깨진다 (두 번째 지점).
|
|
231
223
|
for (const [k, v] of deployValues.get(t)) rows.push(` ${k}: "${escapeYamlDoubleQuoted(v)}"`);
|
|
232
224
|
}
|
|
233
225
|
deployBlock = rows.join("\n");
|
|
@@ -242,8 +234,8 @@ export function buildVersionYml({
|
|
|
242
234
|
NOW: now, TODAY: today || optionsDate, DEFAULT_BRANCH: branch,
|
|
243
235
|
TEMPLATE_VERSION: templateVersion,
|
|
244
236
|
MAIN_BRANCH: b.main, DEVELOP_BRANCH: b.develop, BRANCH_MODE: b.mode,
|
|
245
|
-
OPT_NEXUS: String(includeNexus), OPT_SECRET_BACKUP: String(includeSecretBackup),
|
|
246
237
|
OPT_SEMVER_AUTO: String(includeSemverAuto),
|
|
238
|
+
OPT_COPILOT_AI: String(includeCopilotAi),
|
|
247
239
|
OPT_DEPLOY_STYLE: String(deployStyle || ""),
|
|
248
240
|
};
|
|
249
241
|
|
|
@@ -273,15 +265,16 @@ export function buildVersionYml({
|
|
|
273
265
|
export function renderVersionYml(context, templateText, { pathMarkers, deployValues = new Map(), extraTopLevel = [] }) {
|
|
274
266
|
const { version, types = [], paths = new Map(), branch = "main", versionCode = 1,
|
|
275
267
|
now, today, templateVersion = "unknown", branches = null,
|
|
276
|
-
|
|
268
|
+
includeSemverAuto, includeCopilotAi, deployStyle,
|
|
277
269
|
envMode, flutterStore, androidDeployMode, iosDeployMode } = context;
|
|
278
270
|
return buildVersionYml({
|
|
279
271
|
templateText, version, types, paths, pathMarkers, branch, branches, versionCode, now, today,
|
|
280
272
|
deployValues, extraTopLevel,
|
|
281
273
|
flutterOptions: { envMode, stores: flutterStore, androidDeployMode, iosDeployMode },
|
|
282
274
|
templateOptions: {
|
|
283
|
-
templateVersion,
|
|
275
|
+
templateVersion,
|
|
284
276
|
includeSemverAuto: includeSemverAuto !== false,
|
|
277
|
+
includeCopilotAi: includeCopilotAi === true,
|
|
285
278
|
deployStyle: deployStyle || DEFAULT_DEPLOY_STYLE,
|
|
286
279
|
optionsDate: today,
|
|
287
280
|
},
|
package/src/core/wizard-env.js
CHANGED
|
@@ -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
|
|
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) 양쪽에서 재사용한다 —
|
|
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 마커가 아무 경고 없이 무시된다(
|
|
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
|
-
// 재사용한다 — 두 곳이 서로 다른 로직으로 갈라지면
|
|
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/대화형 메뉴에 노출하지
|
|
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
|
-
//
|
|
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
|
-
// (
|
|
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
|
|
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)를 그대로
|
|
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
|
|
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,18 +283,18 @@ async function runInner(argv, {
|
|
|
283
283
|
mode: opts.mode, force: opts.force, types, version, versionCode, branch,
|
|
284
284
|
branches,
|
|
285
285
|
paths,
|
|
286
|
-
//
|
|
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(기존 설계) 유지.
|
|
292
290
|
includeSemverAuto: opts.includeSemverAuto ?? existing?.options?.semverAuto ?? (existing ? false : true),
|
|
291
|
+
// Copilot AI 요약은 AI Credits를 소비하는 opt-in — 신규·기존 설치 모두 명시하지 않으면 false다.
|
|
292
|
+
includeCopilotAi: opts.includeCopilotAi ?? existing?.options?.copilotAi ?? false,
|
|
293
293
|
repoName,
|
|
294
294
|
// 실 resolver 4종 (.sh resolve_token 등가)
|
|
295
295
|
resolvers: makeResolvers(cwd, repoName, paths, flutterOptions),
|
|
296
296
|
now, today,
|
|
297
|
-
// 설치
|
|
297
|
+
// 설치 로그용 부가 문맥 — 설치 동작 자체는 바꾸지 않는다.
|
|
298
298
|
markers: detectMarkers(cwd, types), detectWarnings,
|
|
299
299
|
deployStyle: opts.deployStyle
|
|
300
300
|
|| (isDeployStyle(existing?.options?.deployStyle) ? existing.options.deployStyle : DEFAULT_DEPLOY_STYLE),
|
|
@@ -307,7 +307,7 @@ async function runInner(argv, {
|
|
|
307
307
|
|
|
308
308
|
context.templateVersion = readTemplateVersion();
|
|
309
309
|
|
|
310
|
-
// 비대화형 축약 배너 (
|
|
310
|
+
// 비대화형 축약 배너 (1줄, 로그 오염 최소)
|
|
311
311
|
printBannerCompact({ version: context.templateVersion, mode: opts.mode });
|
|
312
312
|
|
|
313
313
|
// Breaking Changes 게이트 (.sh execute_integration L4415~4420 등가 — 비대화형은 경고 후 진행)
|
|
@@ -320,8 +320,8 @@ async function runInner(argv, {
|
|
|
320
320
|
}
|
|
321
321
|
|
|
322
322
|
// opts.mode는 parseArgs()에서 화이트리스트 검증을 통과했고, interactive/purge/uninstall/status/doctor는
|
|
323
|
-
// 전부 위에서 조기 반환했으므로 이 시점엔 full 하나로 보장된다 (
|
|
324
|
-
//
|
|
323
|
+
// 전부 위에서 조기 반환했으므로 이 시점엔 full 하나로 보장된다 (default 분기 제거,
|
|
324
|
+
// 부분 설치 모드 제거로 분기 자체가 사라졌다).
|
|
325
325
|
const result = runFull(context, payload, cwd);
|
|
326
326
|
|
|
327
327
|
// 완료 요약 (.sh print_summary — CLI 모드에서도 출력)
|
|
@@ -339,7 +339,7 @@ async function runInner(argv, {
|
|
|
339
339
|
});
|
|
340
340
|
// store_submit 배포 모드는 main push마다 심사를 자동 제출한다 — 비대화형에서도 같은 경고를 보여준다
|
|
341
341
|
// (대화형 경로는 ui/prompts.js#deployModeWarning을 선택 시점에 note로 보여준다).
|
|
342
|
-
// Flutter 타입이 아니거나 해당 스토어를 선택하지 않은 프로젝트에는 뜨면 안
|
|
342
|
+
// Flutter 타입이 아니거나 해당 스토어를 선택하지 않은 프로젝트에는 뜨면 안 된다.
|
|
343
343
|
const { stores } = flutterOptions;
|
|
344
344
|
const warnings = [];
|
|
345
345
|
if (types.includes("flutter") && (stores === null || stores.includes("android"))) {
|
package/src/ui/banner.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
// 첫 화면 배너
|
|
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
|
|
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");
|
package/src/ui/env-plan.js
CHANGED
|
@@ -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 = {},
|
|
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
|
-
// 동일 규칙
|
|
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
|
-
// 복사 엔진과 동일한 폴더 구성: 타입 직하위 + (
|
|
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 (
|
|
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__ 등이 박혀
|
|
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
|
-
// 최종 답변 목록
|
|
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 필드로
|
|
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/
|
|
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
|
-
|
|
190
|
+
deployStyle = "", flutterStore = null, targetRoot = ".", repoName = "", log = defaultLog,
|
|
197
191
|
} = {}) {
|
|
198
192
|
const prompts = loadWizardPrompts(targetRoot, payloadRoot);
|
|
199
|
-
const asks = collectAsks(payloadRoot, types, { resolvers,
|
|
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
|
-
// 수정 메뉴 항목 —
|
|
38
|
-
// showFlutter=Flutter 타입일 때만 환경변수 방식/스토어 배포 대상/배포 모드 노출 (이슈 #131).
|
|
37
|
+
// 수정 메뉴 항목 — showFlutter=Flutter 타입일 때만 환경변수 방식/스토어 배포 대상/배포 모드 노출.
|
|
39
38
|
// 라벨·순서를 테스트할 수 있도록 순수 함수로 분리했다.
|
|
40
|
-
export function editMenuOptions({
|
|
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({
|
|
61
|
-
return engine.select({ message: "어떤 항목을 수정할까요?", options: editMenuOptions({
|
|
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
|
-
// 감지 직후 타입
|
|
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
|
-
// 배포 방식
|
|
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 옵션
|
|
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
|
-
// 브랜치 전략
|
|
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
|
-
// ──
|
|
223
|
+
// ── 첫 화면 UI 5층 + 대화형 계층 실물 io ─────────────────
|
|
229
224
|
// runInteractive는 io.<method>?.() 옵셔널 호출 — 테스트 스텁은 이 메서드들을 생략해
|
|
230
225
|
// 시각 층·env 질문을 건너뛴다 (실행 계약은 그대로).
|
|
231
226
|
import { printBanner as _printBanner } from "./banner.js";
|
package/src/ui/status-cards.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
// 첫 화면 상태 표시 층
|
|
2
|
-
// (
|
|
3
|
-
// 원본의
|
|
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
|
-
//
|
|
12
|
-
// markers: Map<type, 실제 발견
|
|
11
|
+
// 감지 로그 (.ps1 감지 진행 표시 등가)
|
|
12
|
+
// markers: Map<type, 실제 발견 파일>.
|
|
13
13
|
// warnings: 감지 도중 나온 경고. 감지 함수를 먼저 호출한 뒤 박스를 그리는 구조라 경고가
|
|
14
|
-
// 박스 위로 새어나가 앞선 질문에 대한 경고처럼 보였다 — 박스 안에서
|
|
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
|
-
//
|
|
31
|
+
// 프로젝트 분석 개요 카드 (.ps1 Print-ProjectAnalysis 등가+)
|
|
32
32
|
export function printAnalysisCard({ mode = "", modeLabel = "", types = [], version = "", branch = "",
|
|
33
|
-
|
|
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
|
-
|
|
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
|
-
//
|
|
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`);
|