project-auto-wizard 0.1.18 → 0.1.20
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 +27 -1
- package/package.json +1 -1
- package/src/commands/doctor.js +176 -30
- package/src/commands/interactive.js +11 -6
- package/src/ui/ansi.js +1 -0
- package/src/ui/prompts.js +4 -2
package/README.md
CHANGED
|
@@ -16,7 +16,7 @@ npx project-auto-wizard
|
|
|
16
16
|
[](package.json)
|
|
17
17
|
|
|
18
18
|
<!-- AUTO-VERSION-SECTION: DO NOT EDIT MANUALLY -->
|
|
19
|
-
## 최신 버전 : v0.1.18 (2026-08-
|
|
19
|
+
## 최신 버전 : v0.1.18 (2026-08-06)
|
|
20
20
|
|
|
21
21
|
[전체 버전 기록 보기](CHANGELOG.md)
|
|
22
22
|
|
|
@@ -154,6 +154,30 @@ npx project-auto-wizard --mode doctor # 환경 진단 (읽기 전용, 규칙
|
|
|
154
154
|
| `--mode status` | 설치된 버전·타입·브랜치 모드·옵션값과, 설치 시점 대비 사용자가 직접 수정한 워크플로우 파일 목록을 보여줍니다. 네트워크 접근 없음(로컬 파일 비교만) |
|
|
155
155
|
| `--mode doctor` | `version.yml` 설치 여부, `gh` CLI 설치/인증 상태, GitHub Actions workflow permissions, `WORKFLOW_PAT` secret 등록 여부, merge commit 허용 설정을 점검합니다. `gh api` 호출을 사용하므로 네트워크 접근이 발생합니다(규칙 기반 점검 — AI 진단 아님) |
|
|
156
156
|
|
|
157
|
+
`doctor`는 항목마다 **그 설정이 무엇을 담당하는지**를 라벨에 함께 표시하고, 문제가 있는 항목만 `현상 → 그대로 두면 무엇이 안 되는지 → 어디를 눌러 고치는지 → 문서 링크` 순으로 펼쳐 보여줍니다. 정상 항목은 한 줄로 압축됩니다. GitHub 설정 화면에 실제로 표시되는 문자열(`Read and write permissions` 등)은 화면에서 찾을 수 있도록 원문 그대로 출력합니다.
|
|
158
|
+
|
|
159
|
+
```
|
|
160
|
+
◆ 환경 진단 — project-auto-wizard doctor
|
|
161
|
+
|
|
162
|
+
[✓] gh CLI — 레포 설정 조회용 gh version 2.96.0
|
|
163
|
+
[✓] GitHub 로그인 — 레포 설정 조회 권한 인증됨
|
|
164
|
+
[✓] merge commit 허용 — 릴리스 PR 자동 머지 조건 허용됨
|
|
165
|
+
|
|
166
|
+
[!] Workflow permissions — 버전 커밋 자동 push
|
|
167
|
+
✗ 현재 read 입니다.
|
|
168
|
+
워크플로가 버전 올림 커밋을 push하지 못해 릴리스가 중단됩니다.
|
|
169
|
+
→ 레포 Settings → Actions → General → Workflow permissions
|
|
170
|
+
→ "Read and write permissions" 선택 후 Save
|
|
171
|
+
→ 자세히: https://github.com/Twin-Fang/project-auto-wizard#post-install
|
|
172
|
+
|
|
173
|
+
[i] GitHub Models — AI 릴리스 노트 생성
|
|
174
|
+
조직 정책으로 차단됐는지는 자동으로 확인할 수 없습니다 (Settings → Models).
|
|
175
|
+
차단돼 있어도 규칙 기반 요약으로 자동 전환되므로 그대로 두셔도 됩니다.
|
|
176
|
+
|
|
177
|
+
! 1개 항목에서 문제를 찾았습니다.
|
|
178
|
+
설치 자체는 지금 진행할 수 있고, 위 1개는 나중에 설정해도 됩니다.
|
|
179
|
+
```
|
|
180
|
+
|
|
157
181
|
> **드리프트 판정 기준**: `--mode status`는 설치된 워크플로우 파일이 "설치 시점 기본값 템플릿"과 바이트 단위로 일치하는지만 비교합니다 — 파일을 직접 편집했는지는 추적하지 않습니다. 대화형 설치에서 `@wizard ask` 질문(예: 배포 포트)에 기본값이 아닌 값으로 응답했다면, 파일을 전혀 수정하지 않았더라도 설치 직후부터 항상 "사용자가 수정한 워크플로우 파일"로 표시됩니다. 정상 동작이며, 파일을 직접 편집했는지 구분하려면 해당 값이 예상한 응답과 일치하는지 직접 확인하세요.
|
|
158
182
|
|
|
159
183
|
`--dry-run`을 어떤 모드와도 함께 쓰면 실제로 파일을 바꾸지 않고 무엇이 바뀔지만 미리 보여줍니다(`full`/`version`/`workflows`/`revert` 전체 지원):
|
|
@@ -177,6 +201,8 @@ npx project-auto-wizard --no-semver-auto # 항상 patch+1 (레거시 동작)
|
|
|
177
201
|
|
|
178
202
|
기본 설치(pr-flow) 기준으로 일상적인 기능 PR은 `develop`을 대상으로 열리므로, 이 봇은 develop→main 릴리스 PR에서만 실제로 동작합니다 — 해당 PR에서는 `AUTO-CHANGELOG-CONTROL`이 이미 같은 엔진으로 체인지로그 요약을 생성하므로, 이 봇은 그 요약을 PR 코멘트 형태로도 남겨주는 보조 역할입니다. 릴리스 브랜치 = 개발 브랜치인 trunk-based 모드에서는 모든 PR이 곧 릴리스 대상 브랜치를 향하므로 매 PR마다 동작합니다.
|
|
179
203
|
|
|
204
|
+
<a id="post-install"></a>
|
|
205
|
+
|
|
180
206
|
## 설치 후 확인할 것
|
|
181
207
|
|
|
182
208
|
| 항목 | 내용 |
|
package/package.json
CHANGED
package/src/commands/doctor.js
CHANGED
|
@@ -1,73 +1,219 @@
|
|
|
1
1
|
// doctor 명령 — 로컬 환경 진단(읽기 전용, 규칙 기반). gh CLI에 위임해 원격 상태를 점검한다.
|
|
2
2
|
// AI 진단은 포함하지 않는다(스펙 §4에서 검토 후 기각 — 복잡도 대비 이득 낮음).
|
|
3
|
+
//
|
|
4
|
+
// 출력 설계(이슈 #29) — `flutter doctor` 패턴을 차용한다.
|
|
5
|
+
// ① 항목 라벨에 purpose("무엇을 위한 설정인지")를 병기한다. `WORKFLOW_PAT`만 보고는 그게
|
|
6
|
+
// 자기 릴리스 흐름의 무엇을 담당하는지 알 수 없다 — flutter의
|
|
7
|
+
// `Android toolchain - develop for Android devices`와 같은 이유다.
|
|
8
|
+
// ② 도구가 "설치해도 된다/안 된다"를 판정하지 않는다. 발견한 사실(문제 N개)만 진술하고
|
|
9
|
+
// 그게 자신에게 문제인지는 사용자가 판단한다.
|
|
10
|
+
// ③ 문제 항목만 `현상 → 영향 → 조치 → 문서` 4단으로 펼치고, 정상 항목은 한 줄로 압축한다.
|
|
11
|
+
// ④ GitHub 설정 화면에 실제로 표시되는 문자열("Read and write permissions" 등)은 번역하지
|
|
12
|
+
// 않는다. 번역하면 설명은 읽히지만 정작 화면에서 그 항목을 찾지 못한다.
|
|
3
13
|
import { spawnSync } from "node:child_process";
|
|
4
14
|
import { existsSync } from "node:fs";
|
|
5
15
|
import { join } from "node:path";
|
|
16
|
+
import { A, paint, colorEnabled, visualWidth } from "../ui/ansi.js";
|
|
6
17
|
|
|
7
18
|
const defaultExec = (cmd, args) => spawnSync(cmd, args, { encoding: "utf8" });
|
|
8
19
|
|
|
20
|
+
// 해결 가이드 링크 (스펙 2026-07-25-osscontest-scope-design.md §3.3② "각 실패 항목에 대한
|
|
21
|
+
// 해결 가이드 링크(README 앵커)"). README에 심은 영문 HTML 앵커를 가리킨다 — 한글 헤딩
|
|
22
|
+
// 자동 앵커는 URL 인코딩되어 터미널에서 알아볼 수 없게 깨진다.
|
|
23
|
+
const REPO_URL = "https://github.com/Twin-Fang/project-auto-wizard";
|
|
24
|
+
export const DOC = {
|
|
25
|
+
postInstall: `${REPO_URL}#post-install`,
|
|
26
|
+
};
|
|
27
|
+
|
|
9
28
|
export function runDoctor(cwd = process.cwd(), { exec = defaultExec } = {}) {
|
|
10
29
|
const results = [];
|
|
11
|
-
|
|
30
|
+
// name은 항목 식별자(테스트·프로그램 참조용), label은 화면 표기용. label 생략 시 name을 쓴다.
|
|
31
|
+
const add = (item) => { results.push({ actions: [], ...item }); return results; };
|
|
12
32
|
|
|
13
33
|
const installed = existsSync(join(cwd, "version.yml"));
|
|
14
|
-
|
|
15
|
-
|
|
34
|
+
// 미설치는 문제가 아니다 — doctor는 설치 "전에" 돌려보는 것이 정상 사용 경로다.
|
|
35
|
+
add(installed
|
|
36
|
+
? { name: "설치 여부", label: "설치 상태", purpose: "이 폴더의 마법사 설치 여부", status: "OK", value: "version.yml 있음" }
|
|
37
|
+
: {
|
|
38
|
+
name: "설치 여부", label: "설치 상태", purpose: "이 폴더의 마법사 설치 여부", status: "INFO",
|
|
39
|
+
note: ["이 폴더엔 아직 설치되지 않았습니다 — 지금 설치하면 됩니다."],
|
|
40
|
+
});
|
|
16
41
|
|
|
17
42
|
const ghVersion = exec("gh", ["--version"]);
|
|
18
43
|
if (ghVersion.error || ghVersion.status !== 0) {
|
|
19
|
-
add(
|
|
44
|
+
add({
|
|
45
|
+
name: "gh CLI", purpose: "레포 설정 조회용", status: "WARN",
|
|
46
|
+
value: "gh CLI를 찾을 수 없습니다.",
|
|
47
|
+
impact: ["레포 권한·secret 설정을 조회할 수 없어 아래 원격 점검을 모두 건너뜁니다."],
|
|
48
|
+
actions: ["https://cli.github.com/ 에서 gh CLI를 설치한 뒤 다시 실행하세요"],
|
|
49
|
+
});
|
|
20
50
|
return results;
|
|
21
51
|
}
|
|
22
|
-
add(
|
|
52
|
+
add({
|
|
53
|
+
name: "gh CLI", purpose: "레포 설정 조회용", status: "OK",
|
|
54
|
+
value: (ghVersion.stdout || "").split("\n")[0] || "설치됨",
|
|
55
|
+
});
|
|
23
56
|
|
|
24
57
|
const auth = exec("gh", ["auth", "status"]);
|
|
25
58
|
const authOk = !auth.error && auth.status === 0;
|
|
26
|
-
|
|
27
|
-
|
|
59
|
+
if (!authOk) {
|
|
60
|
+
add({
|
|
61
|
+
name: "gh 인증", label: "GitHub 로그인", purpose: "레포 설정 조회 권한", status: "FAIL",
|
|
62
|
+
value: "로그인되어 있지 않습니다.",
|
|
63
|
+
impact: ["레포 설정을 읽을 수 없어 아래 원격 점검을 모두 건너뜁니다."],
|
|
64
|
+
actions: ["터미널에서 `gh auth login` 실행"],
|
|
65
|
+
});
|
|
66
|
+
return results;
|
|
67
|
+
}
|
|
68
|
+
add({ name: "gh 인증", label: "GitHub 로그인", purpose: "레포 설정 조회 권한", status: "OK", value: "인증됨" });
|
|
28
69
|
|
|
29
70
|
const remote = exec("git", ["-C", cwd, "remote", "get-url", "origin"]);
|
|
30
71
|
const url = remote.status === 0 ? (remote.stdout || "").trim() : "";
|
|
31
72
|
const match = url.match(/github\.com[:/]([^/]+)\/([^/.]+?)(\.git)?$/);
|
|
32
73
|
if (!match) {
|
|
33
|
-
add(
|
|
74
|
+
add({
|
|
75
|
+
name: "GitHub 원격", purpose: "점검 대상 레포 식별", status: "WARN",
|
|
76
|
+
value: "origin 리모트에서 GitHub owner/repo를 찾지 못했습니다.",
|
|
77
|
+
impact: ["어느 레포를 점검해야 할지 알 수 없어 아래 원격 점검을 모두 건너뜁니다."],
|
|
78
|
+
actions: ["`git remote -v`로 origin이 GitHub 주소를 가리키는지 확인하세요"],
|
|
79
|
+
});
|
|
34
80
|
return results;
|
|
35
81
|
}
|
|
36
82
|
const [, owner, repo] = match;
|
|
37
83
|
|
|
38
84
|
const perm = exec("gh", ["api", `repos/${owner}/${repo}/actions/permissions/workflow`, "--jq", ".default_workflow_permissions"]);
|
|
39
85
|
const permValue = (perm.stdout || "").trim();
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
: "
|
|
86
|
+
if (perm.status !== 0) {
|
|
87
|
+
add({
|
|
88
|
+
name: "Workflow permissions", purpose: "버전 커밋 자동 push", status: "WARN",
|
|
89
|
+
value: "설정을 조회하지 못했습니다.",
|
|
90
|
+
impact: ["레포 관리자 권한이 없으면 이 설정은 조회되지 않습니다 — 값 자체는 정상일 수 있습니다."],
|
|
91
|
+
actions: ["레포 관리자에게 Settings → Actions → General → Workflow permissions 값을 확인하세요"],
|
|
92
|
+
doc: DOC.postInstall,
|
|
93
|
+
});
|
|
94
|
+
} else if (permValue === "write") {
|
|
95
|
+
add({ name: "Workflow permissions", purpose: "버전 커밋 자동 push", status: "OK", value: "Read and write" });
|
|
96
|
+
} else {
|
|
97
|
+
add({
|
|
98
|
+
name: "Workflow permissions", purpose: "버전 커밋 자동 push", status: "WARN",
|
|
99
|
+
value: `현재 ${permValue || "확인불가"} 입니다.`,
|
|
100
|
+
impact: ["워크플로가 버전 올림 커밋을 push하지 못해 릴리스가 중단됩니다."],
|
|
101
|
+
actions: [
|
|
102
|
+
"레포 Settings → Actions → General → Workflow permissions",
|
|
103
|
+
'"Read and write permissions" 선택 후 Save',
|
|
104
|
+
],
|
|
105
|
+
doc: DOC.postInstall,
|
|
106
|
+
});
|
|
107
|
+
}
|
|
44
108
|
|
|
45
109
|
const secrets = exec("gh", ["secret", "list", "--repo", `${owner}/${repo}`]);
|
|
46
110
|
const hasPat = secrets.status === 0 && (secrets.stdout || "").split("\n").some((l) => l.startsWith("WORKFLOW_PAT"));
|
|
47
|
-
add(
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
: "
|
|
111
|
+
add(hasPat
|
|
112
|
+
? { name: "WORKFLOW_PAT secret", label: "WORKFLOW_PAT", purpose: "자동 태그·Release 발행", status: "OK", value: "등록됨" }
|
|
113
|
+
: {
|
|
114
|
+
name: "WORKFLOW_PAT secret", label: "WORKFLOW_PAT", purpose: "자동 태그·Release 발행", status: "WARN",
|
|
115
|
+
value: "secret이 등록되어 있지 않습니다.",
|
|
116
|
+
impact: [
|
|
117
|
+
"PR 자동 머지 뒤 태그·Release 워크플로가 이어지지 않습니다.",
|
|
118
|
+
"(GitHub 정책상 기본 토큰으로 만든 커밋은 다음 워크플로를 깨우지 못합니다)",
|
|
119
|
+
],
|
|
120
|
+
actions: [
|
|
121
|
+
"개인 액세스 토큰 발급 (scopes: repo, workflow)",
|
|
122
|
+
"레포 Settings → Secrets and variables → Actions",
|
|
123
|
+
"New repository secret · 이름은 WORKFLOW_PAT",
|
|
124
|
+
],
|
|
125
|
+
doc: DOC.postInstall,
|
|
126
|
+
});
|
|
51
127
|
|
|
52
128
|
const mergeSettings = exec("gh", ["api", `repos/${owner}/${repo}`, "--jq", ".allow_merge_commit"]);
|
|
53
129
|
const automergeOk = mergeSettings.status === 0 && mergeSettings.stdout.trim() === "true";
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
: "
|
|
130
|
+
if (mergeSettings.status !== 0) {
|
|
131
|
+
add({
|
|
132
|
+
name: "automerge 호환성(merge commit 허용)", label: "merge commit 허용", purpose: "릴리스 PR 자동 머지 조건", status: "WARN",
|
|
133
|
+
value: "설정을 조회하지 못했습니다.",
|
|
134
|
+
impact: ["레포 관리자 권한이 없으면 이 설정은 조회되지 않습니다 — 값 자체는 정상일 수 있습니다."],
|
|
135
|
+
actions: ["레포 관리자에게 Settings → General → Pull Requests 설정을 확인하세요"],
|
|
136
|
+
doc: DOC.postInstall,
|
|
137
|
+
});
|
|
138
|
+
} else if (automergeOk) {
|
|
139
|
+
add({ name: "automerge 호환성(merge commit 허용)", label: "merge commit 허용", purpose: "릴리스 PR 자동 머지 조건", status: "OK", value: "허용됨" });
|
|
140
|
+
} else {
|
|
141
|
+
add({
|
|
142
|
+
name: "automerge 호환성(merge commit 허용)", label: "merge commit 허용", purpose: "릴리스 PR 자동 머지 조건", status: "WARN",
|
|
143
|
+
value: "이 레포는 merge commit이 꺼져 있습니다.",
|
|
144
|
+
impact: ["RELEASE-PUBLISH가 머지 커밋 제목으로 릴리스를 감지하므로 자동 머지 흐름이 어긋날 수 있습니다."],
|
|
145
|
+
actions: [
|
|
146
|
+
"레포 Settings → General → Pull Requests",
|
|
147
|
+
"Allow merge commits 체크",
|
|
148
|
+
],
|
|
149
|
+
doc: DOC.postInstall,
|
|
150
|
+
});
|
|
151
|
+
}
|
|
60
152
|
|
|
61
|
-
add(
|
|
62
|
-
"
|
|
153
|
+
add({
|
|
154
|
+
name: "GitHub Models 활성화", label: "GitHub Models", purpose: "AI 릴리스 노트 생성", status: "INFO",
|
|
155
|
+
note: [
|
|
156
|
+
"조직 정책으로 차단됐는지는 자동으로 확인할 수 없습니다 (Settings → Models).",
|
|
157
|
+
"차단돼 있어도 규칙 기반 요약으로 자동 전환되므로 그대로 두셔도 됩니다.",
|
|
158
|
+
],
|
|
159
|
+
});
|
|
63
160
|
|
|
64
161
|
return results;
|
|
65
162
|
}
|
|
66
163
|
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
164
|
+
const asLines = (v) => (Array.isArray(v) ? v : v ? [String(v)] : []);
|
|
165
|
+
const headOf = (r) => `${r.label || r.name}${r.purpose ? ` — ${r.purpose}` : ""}`;
|
|
166
|
+
|
|
167
|
+
export function printDoctorReport(results, { out = (s) => console.log(s), color = colorEnabled() } = {}) {
|
|
168
|
+
const p = (s, c) => paint(s, c, color);
|
|
169
|
+
const oks = results.filter((r) => r.status === "OK");
|
|
170
|
+
const problems = results.filter((r) => r.status === "WARN" || r.status === "FAIL");
|
|
171
|
+
const infos = results.filter((r) => r.status === "INFO");
|
|
172
|
+
|
|
173
|
+
const lines = ["", `${p("◆", A.cyan)} ${p("환경 진단", A.bold)} ${p("— project-auto-wizard doctor", A.dim)}`, ""];
|
|
174
|
+
|
|
175
|
+
// 정상 — 한 줄씩. 값은 라벨 폭에 맞춰 우측으로 정렬한다(CJK 2칸 계산은 visualWidth에 위임).
|
|
176
|
+
if (oks.length) {
|
|
177
|
+
const width = Math.max(...oks.map((r) => visualWidth(headOf(r))));
|
|
178
|
+
for (const r of oks) {
|
|
179
|
+
const head = headOf(r);
|
|
180
|
+
const pad = " ".repeat(width - visualWidth(head) + 4);
|
|
181
|
+
lines.push(` ${p("[✓]", A.green)} ${head}${r.value ? `${pad}${p(r.value, A.dim)}` : ""}`);
|
|
182
|
+
}
|
|
183
|
+
lines.push("");
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
// 문제 — 현상 → 영향 → 조치 → 문서 순으로 펼친다.
|
|
187
|
+
for (const r of problems) {
|
|
188
|
+
const fail = r.status === "FAIL";
|
|
189
|
+
lines.push(` ${p(fail ? "[✗]" : "[!]", fail ? A.red : A.yellow)} ${p(headOf(r), A.bold)}`);
|
|
190
|
+
if (r.value) lines.push(` ${p(fail ? "✗" : "✗", fail ? A.red : A.yellow)} ${r.value}`);
|
|
191
|
+
for (const l of asLines(r.impact)) lines.push(` ${p(l, A.dim)}`);
|
|
192
|
+
for (const a of asLines(r.actions)) lines.push(` ${p("→", A.cyan)} ${a}`);
|
|
193
|
+
if (r.doc) lines.push(` ${p("→", A.cyan)} 자세히: ${p(r.doc, A.dim)}`);
|
|
194
|
+
lines.push("");
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
// 참고 — 조치가 필요 없는 안내.
|
|
198
|
+
for (const r of infos) {
|
|
199
|
+
lines.push(` ${p("[i]", A.gray)} ${headOf(r)}`);
|
|
200
|
+
for (const l of asLines(r.note)) lines.push(` ${p(l, A.dim)}`);
|
|
201
|
+
}
|
|
202
|
+
if (infos.length) lines.push("");
|
|
203
|
+
|
|
204
|
+
lines.push(summaryLine(problems, p), "");
|
|
205
|
+
out(lines.join("\n"));
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
// 요약 — "설치해도 된다"는 판정 대신 발견한 사실만 말한다(flutter doctor의 마지막 줄과 같은 태도).
|
|
209
|
+
function summaryLine(problems, p) {
|
|
210
|
+
if (!problems.length) return ` ${p("✓", A.green)} 문제를 찾지 못했습니다.`;
|
|
211
|
+
const n = problems.length;
|
|
212
|
+
if (problems.some((r) => r.status === "FAIL")) {
|
|
213
|
+
return ` ${p("✗", A.red)} ${n}개 항목에서 문제를 찾았습니다 — 일부 점검은 실행하지 못했습니다.`;
|
|
214
|
+
}
|
|
215
|
+
return [
|
|
216
|
+
` ${p("!", A.yellow)} ${n}개 항목에서 문제를 찾았습니다.`,
|
|
217
|
+
` 설치 자체는 지금 진행할 수 있고, 위 ${n}개는 나중에 설정해도 됩니다.`,
|
|
218
|
+
].join("\n");
|
|
73
219
|
}
|
|
@@ -43,12 +43,17 @@ export async function runInteractive(baseCtx, { cwd = process.cwd(), payloadRoot
|
|
|
43
43
|
io.installKind?.({ currentTemplateVersion: existing?.templateVersion || "", templateVersion });
|
|
44
44
|
|
|
45
45
|
// 1) 모드 선택
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
46
|
+
// status/doctor — 읽기 전용이라 감지·breaking 게이트가 필요 없다. 결과를 보여준 뒤 메뉴로
|
|
47
|
+
// 돌아온다(이슈 #31): 진단의 목적이 설치 준비이므로 확인 → 설치를 한 세션에서 끝내야 한다.
|
|
48
|
+
// CLI 경로(index.js의 --mode status/doctor)는 단발 명령이므로 지금처럼 즉시 종료한다.
|
|
49
|
+
let mode;
|
|
50
|
+
for (let round = 0; ; round++) {
|
|
51
|
+
mode = await io.selectMode({ again: round > 0 });
|
|
52
|
+
if (mode === CANCEL || mode == null) { io.cancelMessage?.("설치를 취소했습니다."); return 0; }
|
|
53
|
+
if (mode === "status") { printStatus(runStatus(payload, cwd)); continue; }
|
|
54
|
+
if (mode === "doctor") { printDoctorReport(runDoctor(cwd)); continue; }
|
|
55
|
+
break;
|
|
56
|
+
}
|
|
52
57
|
|
|
53
58
|
// revert 모드 — 확인 질문(기본 아니오) 후 payload 유래 파일 제거. 감지·breaking 게이트 불필요.
|
|
54
59
|
if (mode === "revert") {
|
package/src/ui/ansi.js
CHANGED
package/src/ui/prompts.js
CHANGED
|
@@ -6,9 +6,11 @@ import * as engine from "./readline-engine.js";
|
|
|
6
6
|
export const CANCEL = engine.CANCEL;
|
|
7
7
|
|
|
8
8
|
// 모드 선택 — 한국어 라벨, 내부 키 반환. 취소 시 CANCEL.
|
|
9
|
-
|
|
9
|
+
// again=true는 읽기 전용 모드(status/doctor)를 실행하고 메뉴로 돌아온 재진입이다 —
|
|
10
|
+
// 이때 "무엇을 설치할까요?"를 다시 묻는 건 어색하므로 문구를 바꾼다.
|
|
11
|
+
export async function selectMode({ again = false } = {}) {
|
|
10
12
|
return engine.select({
|
|
11
|
-
message: "무엇을 설치할까요?",
|
|
13
|
+
message: again ? "다음으로 무엇을 할까요?" : "무엇을 설치할까요?",
|
|
12
14
|
options: [
|
|
13
15
|
{ value: "full", label: "전체 설치 — 버전관리 + 자동화 워크플로우 (처음이라면 추천)" },
|
|
14
16
|
{ value: "version", label: "버전 관리만 — 버전 자동 증가·동기화 시스템만 설치" },
|