projectops 4.2.21 → 4.2.22
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 +1 -1
- package/package.json +1 -1
- package/src/commands/full.js +1 -1
- package/src/commands/interactive.js +64 -10
- package/src/commands/skills.js +8 -2
- package/src/commands/version.js +4 -2
- package/src/core/version-yml.js +10 -5
- package/src/core/wizard-env.js +13 -0
- package/src/index.js +2 -0
- package/src/ui/prompts.js +16 -11
- package/src/ui/status-cards.js +1 -1
package/README.md
CHANGED
package/package.json
CHANGED
package/src/commands/full.js
CHANGED
|
@@ -41,7 +41,7 @@ export function runFull(context, tempDir, targetRoot = ".", hooks = {}) {
|
|
|
41
41
|
version, types, paths, pathMarkers, branch, deployBranch, versionCode, now, today,
|
|
42
42
|
deployValues,
|
|
43
43
|
templateOptions: { templateVersion, deployTarget, publishTargets, includeSecretBackup, optionsDate: today,
|
|
44
|
-
changelogProvider, changelogBaseUrl, codeReviewCoderabbit, intent },
|
|
44
|
+
changelogProvider, changelogBaseUrl, codeReviewCoderabbit, intent, mode: "full" },
|
|
45
45
|
}));
|
|
46
46
|
|
|
47
47
|
// 2. README 버전 섹션
|
|
@@ -18,6 +18,7 @@ import { createRunTrace } from "../core/run-trace.js";
|
|
|
18
18
|
import { appendGuideEntry } from "../core/migration-guide.js";
|
|
19
19
|
import { promptEnvPlan } from "../ui/env-plan.js";
|
|
20
20
|
import { listWorkflowConflicts } from "../core/copy/workflows.js";
|
|
21
|
+
import { extractEnvValues } from "../core/wizard-env.js";
|
|
21
22
|
import { createContext, VALID_TYPES } from "../context.js";
|
|
22
23
|
import { runFull } from "./full.js";
|
|
23
24
|
import { runVersion } from "./version.js";
|
|
@@ -53,9 +54,14 @@ export async function runInteractive(baseCtx, { cwd = process.cwd(), source = {
|
|
|
53
54
|
io.ideStatus?.();
|
|
54
55
|
io.installKind?.({ currentTemplateVersion: existing?.templateVersion || "", templateVersion });
|
|
55
56
|
|
|
56
|
-
// 1) 모드 선택
|
|
57
|
-
const
|
|
58
|
-
|
|
57
|
+
// 1) 모드 선택 — 기존 통합 레포면 업데이트 항목을 맨 위에 노출 (#502)
|
|
58
|
+
const updateInfo = existing?.templateVersion ? { from: existing.templateVersion, to: templateVersion } : null;
|
|
59
|
+
const picked = await io.selectMode(updateInfo ? { update: updateInfo } : {});
|
|
60
|
+
if (picked === CANCEL || picked == null) { io.cancelMessage?.("설치를 취소했습니다."); return 0; }
|
|
61
|
+
// 업데이트 모드(#502): 저장된 통합 범위(templateMode, 없으면 full)를 재실행하고
|
|
62
|
+
// 이하 updateRun 분기가 질문을 최소화한다 ("저장된 설정 그대로 반영"이 계약).
|
|
63
|
+
const updateRun = picked === "update";
|
|
64
|
+
const mode = updateRun ? (existing?.templateMode || "full") : picked;
|
|
59
65
|
|
|
60
66
|
// Breaking Changes 게이트 (.sh execute_integration L4415~4420 — 모든 모드 공통, 대화형은 확인 질문)
|
|
61
67
|
let breakingReport = null; // #493 — 통과 구간 항목을 가이드에 조치 방법 전문으로 임베드
|
|
@@ -84,6 +90,11 @@ export async function runInteractive(baseCtx, { cwd = process.cwd(), source = {
|
|
|
84
90
|
|
|
85
91
|
// full/version/workflows — 감지 (version은 기존 version.yml 최우선)
|
|
86
92
|
let types = detectTypes(cwd);
|
|
93
|
+
// 업데이트 모드 — 저장된 타입 우선 (재감지로 타입 집합이 흔들리지 않게, #502)
|
|
94
|
+
if (updateRun && existing?.types?.length) {
|
|
95
|
+
const saved = existing.types.filter((t) => VALID_TYPES.includes(t));
|
|
96
|
+
if (saved.length) types = saved;
|
|
97
|
+
}
|
|
87
98
|
let version = (existing?.version) || detectVersion(cwd);
|
|
88
99
|
let branch = detectDefaultBranch(cwd);
|
|
89
100
|
const repoName = detectRepoName(cwd);
|
|
@@ -140,6 +151,15 @@ export async function runInteractive(baseCtx, { cwd = process.cwd(), source = {
|
|
|
140
151
|
// 확인/수정 루프 — ESC는 '머무르기' (.sh L1877~1881: 명시적 '아니오'만 종료)
|
|
141
152
|
let paths = new Map();
|
|
142
153
|
let confirmed = false;
|
|
154
|
+
// 업데이트 모드 — 카드는 보여주되 확인 루프 생략 (#502: 설정 변경은 기존 모드의 몫)
|
|
155
|
+
if (updateRun) {
|
|
156
|
+
if (io.analysisCard) {
|
|
157
|
+
io.analysisCard({ mode, modeLabel: `업데이트 (${modeLabel(mode)} 범위)`, types, version, branch, deployTarget, publishTargets, includeSecretBackup, showOptional, paths });
|
|
158
|
+
} else {
|
|
159
|
+
io.note?.(summarize({ mode, types, version, branch, deployTarget, publishTargets, includeSecretBackup, showOptional, changelogProvider, codeReviewCoderabbit }), "업데이트 — 저장된 설정으로 진행");
|
|
160
|
+
}
|
|
161
|
+
confirmed = true;
|
|
162
|
+
}
|
|
143
163
|
while (!confirmed) {
|
|
144
164
|
// 층3 — 프로젝트 분석 개요 카드 (#446). 스텁엔 없음 → note 폴백.
|
|
145
165
|
if (io.analysisCard) {
|
|
@@ -217,10 +237,11 @@ export async function runInteractive(baseCtx, { cwd = process.cwd(), source = {
|
|
|
217
237
|
for (const t of types) if (t !== "basic" && !paths.has(t)) paths.set(t, existing?.paths.get(t) || ".");
|
|
218
238
|
}
|
|
219
239
|
|
|
220
|
-
// @wizard env 계획 질문 (.sh wf_prompt_env_plan L3220 — full/workflows만)
|
|
240
|
+
// @wizard env 계획 질문 (.sh wf_prompt_env_plan L3220 — full/workflows만).
|
|
241
|
+
// 업데이트 모드는 생략 — 신규 파일은 기본값, 교체 파일은 아래 carryover가 기존 실값을 이월한다.
|
|
221
242
|
const resolvers = makeResolvers(cwd, repoName, paths);
|
|
222
243
|
let envValues = new Map(), envUseDefaults = true;
|
|
223
|
-
if (showOptional) {
|
|
244
|
+
if (showOptional && !updateRun) {
|
|
224
245
|
const plan = await promptEnvPlan({
|
|
225
246
|
tempDir, types, io: io.engineIo ?? null, force: false,
|
|
226
247
|
resolvers, deployTarget, publishTargets, targetRoot: cwd, repoName,
|
|
@@ -229,17 +250,45 @@ export async function runInteractive(baseCtx, { cwd = process.cwd(), source = {
|
|
|
229
250
|
envUseDefaults = plan.useDefaults;
|
|
230
251
|
}
|
|
231
252
|
|
|
253
|
+
// 업데이트 모드 충돌 처리 (#502) — 타입별 3지선다 대신 일괄 확인 1회(기본 Y: .bak 백업 후 교체).
|
|
254
|
+
// 교체를 선택하면 기존 설치본의 env 실값을 추출해 새 파일에 이월(carryover)한다 —
|
|
255
|
+
// 기본값 재치환으로 사용자가 채운 값(FIREBASE_APP_ID 등)이 초기화되는 것을 막는 핵심.
|
|
256
|
+
let updateDecisions = null;
|
|
257
|
+
if (showOptional && updateRun) {
|
|
258
|
+
const liteCtx = { types, paths, deployTarget, publishTargets, repoName, resolvers, branch, deployBranch };
|
|
259
|
+
const conflicts = listWorkflowConflicts(liteCtx, tempDir, cwd);
|
|
260
|
+
if (conflicts.length) {
|
|
261
|
+
io.note?.(conflicts.map((c) => `• ${c.filename}`).join("\n"), `♻️ 템플릿이 갱신된 워크플로우 ${conflicts.length}개`);
|
|
262
|
+
const yes = await io.askYesNo(`위 ${conflicts.length}개를 .bak 백업 후 새 버전으로 교체할까요? (기존 설정값은 유지됩니다)`, true);
|
|
263
|
+
const decision = yes === true ? "backup" : "skip";
|
|
264
|
+
updateDecisions = new Map();
|
|
265
|
+
for (const { filename } of conflicts) updateDecisions.set(filename, decision);
|
|
266
|
+
if (decision === "backup") {
|
|
267
|
+
const carried = new Map();
|
|
268
|
+
for (const { filename } of conflicts) {
|
|
269
|
+
const p = join(cwd, PATHS.workflowsDir, filename);
|
|
270
|
+
if (!existsSync(p)) continue;
|
|
271
|
+
for (const [k, v] of extractEnvValues(readFileSync(p, "utf8"))) carried.set(k, v);
|
|
272
|
+
}
|
|
273
|
+
if (carried.size) { envValues = carried; envUseDefaults = false; }
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
|
|
232
278
|
const { now, today } = clock || utcNow();
|
|
233
279
|
const ctx = createContext({
|
|
234
280
|
mode, force: true, types, version, versionCode, branch, paths, deployTarget, publishTargets, includeSecretBackup,
|
|
235
281
|
codeReviewCoderabbit, changelogProvider, changelogBaseUrl, deployBranch, intent,
|
|
236
282
|
repoName, templateVersion, resolvers, envValues, envUseDefaults, now, today,
|
|
283
|
+
// #502 — version 모드가 기존 full 기록을 강등하지 않도록 (full이 우세)
|
|
284
|
+
recordMode: existing?.templateMode === "full" ? "full" : "version",
|
|
237
285
|
});
|
|
238
286
|
ctx.templateVersion = templateVersion;
|
|
239
287
|
|
|
240
288
|
// 기존 워크플로우 충돌 3지선 — 타입당 1회 결정을 파일에 캐시 적용 (.sh L3440~3508 UX 등가)
|
|
241
|
-
|
|
242
|
-
|
|
289
|
+
// 업데이트 모드는 위에서 일괄 결정(updateDecisions)했으므로 3지선다를 건너뛴다 (#502).
|
|
290
|
+
let hooks = updateDecisions ? { decisions: updateDecisions } : {};
|
|
291
|
+
if (showOptional && io.engineIo?.select && !updateRun) {
|
|
243
292
|
const conflicts = listWorkflowConflicts(ctx, tempDir, cwd);
|
|
244
293
|
if (conflicts.length) {
|
|
245
294
|
const perType = new Map();
|
|
@@ -302,8 +351,13 @@ export async function runInteractive(baseCtx, { cwd = process.cwd(), source = {
|
|
|
302
351
|
else if (mode === "workflows") result = runWorkflows(ctx, tempDir, cwd, { ...hooks, trace });
|
|
303
352
|
|
|
304
353
|
// 통합 후 IDE 스킬 제안 (.sh L4557 offer_ide_tools_install — 사전 질문 게이트, 기본 N)
|
|
305
|
-
|
|
306
|
-
if (
|
|
354
|
+
// 업데이트 모드(#502): 이미 설치된 IDE 스킬만 질문 없이 최신화 (미설치 IDE는 건드리지 않음)
|
|
355
|
+
if (updateRun) {
|
|
356
|
+
await skills({ templateVersion, tempDir, interactive: false, installedOnly: true });
|
|
357
|
+
} else {
|
|
358
|
+
const wantSkills = await io.askYesNo("AI 에이전트 스킬(Claude·Cursor·Gemini·Codex·PI)도 설치/업데이트할까요?", false);
|
|
359
|
+
if (wantSkills === true) await skills({ templateVersion, tempDir, interactive: true });
|
|
360
|
+
}
|
|
307
361
|
|
|
308
362
|
// 마이그레이션 기록 (#493/#494) — Layer 2/3 트레이스 파일 + Layer 1 가이드 엔트리 (full/workflows만)
|
|
309
363
|
let migrationGuidePath = null;
|
|
@@ -351,7 +405,7 @@ function summarize({ mode, types, version, branch, deployTarget, publishTargets,
|
|
|
351
405
|
}
|
|
352
406
|
|
|
353
407
|
function modeLabel(m) {
|
|
354
|
-
return { full: "전체 설치", version: "버전 관리만", workflows: "워크플로우만", issues: "이슈·PR 템플릿만", skills: "AI 스킬만" }[m] || m;
|
|
408
|
+
return { full: "전체 설치", version: "버전 관리만", workflows: "워크플로우만", issues: "이슈·PR 템플릿만", skills: "AI 스킬만", update: "업데이트" }[m] || m;
|
|
355
409
|
}
|
|
356
410
|
|
|
357
411
|
function utcNow(date = new Date()) {
|
package/src/commands/skills.js
CHANGED
|
@@ -30,7 +30,9 @@ export function formatStatuses(rows, templateVersion) {
|
|
|
30
30
|
});
|
|
31
31
|
}
|
|
32
32
|
|
|
33
|
-
// 대화형/비대화형 공통 진입. opts: {io, templateVersion, sourceSkillsDir, tempDir, interactive, ui}
|
|
33
|
+
// 대화형/비대화형 공통 진입. opts: {io, templateVersion, sourceSkillsDir, tempDir, interactive, ui, installedOnly}
|
|
34
|
+
// installedOnly(#502 — 업데이트 모드): 비대화형 apply를 이미 설치된 어댑터로 한정한다.
|
|
35
|
+
// 미설치 IDE에 새로 설치하지 않는다 — 업데이트는 "있는 것을 최신으로"가 계약.
|
|
34
36
|
export async function runSkills(opts = {}) {
|
|
35
37
|
const io = opts.io || defaultIo();
|
|
36
38
|
const ui = opts.ui || skillsPrompts;
|
|
@@ -43,8 +45,12 @@ export async function runSkills(opts = {}) {
|
|
|
43
45
|
io.log("");
|
|
44
46
|
|
|
45
47
|
// 비대화형: 감지된(=관리 가능) 모든 어댑터에 apply(설치/업데이트) 순차 실행.
|
|
48
|
+
// installedOnly면 설치된 어댑터만 (업데이트 모드 — 미설치 IDE는 건드리지 않음).
|
|
46
49
|
if (!opts.interactive) {
|
|
47
|
-
for (const { adapter } of rows)
|
|
50
|
+
for (const { adapter, status } of rows) {
|
|
51
|
+
if (opts.installedOnly && !status.installed) continue;
|
|
52
|
+
safe(() => adapter.apply(io, ctx), false);
|
|
53
|
+
}
|
|
48
54
|
return 0;
|
|
49
55
|
}
|
|
50
56
|
|
package/src/commands/version.js
CHANGED
|
@@ -14,7 +14,7 @@ export function runVersion(context, tempDir, targetRoot = ".") {
|
|
|
14
14
|
const { version, types = [], paths = new Map(), branch = "main", versionCode = 1,
|
|
15
15
|
now, today, templateVersion = "unknown", deployTarget = "docker-ssh", publishTargets = [], includeSecretBackup = false,
|
|
16
16
|
changelogProvider = "github-ai", changelogBaseUrl = "", codeReviewCoderabbit = true,
|
|
17
|
-
deployBranch = "" } = context;
|
|
17
|
+
deployBranch = "", recordMode = "version" } = context;
|
|
18
18
|
|
|
19
19
|
const pathMarkers = new Map();
|
|
20
20
|
for (const [t] of paths) pathMarkers.set(t, markerForType(t));
|
|
@@ -22,8 +22,10 @@ export function runVersion(context, tempDir, targetRoot = ".") {
|
|
|
22
22
|
writeText(join(targetRoot, PATHS.versionFile),
|
|
23
23
|
buildVersionYml({
|
|
24
24
|
version, types, paths, pathMarkers, branch, deployBranch, versionCode, now, today,
|
|
25
|
+
// mode(#502): version 모드가 기존 full 통합 기록을 "version"으로 강등하지 않도록
|
|
26
|
+
// 호출부가 recordMode로 기존 값을 넘긴다 (full이 우세 — 업데이트 재실행 범위 축소 방지).
|
|
25
27
|
templateOptions: { templateVersion, deployTarget, publishTargets, includeSecretBackup, optionsDate: today,
|
|
26
|
-
changelogProvider, changelogBaseUrl, codeReviewCoderabbit },
|
|
28
|
+
changelogProvider, changelogBaseUrl, codeReviewCoderabbit, mode: recordMode },
|
|
27
29
|
}));
|
|
28
30
|
addVersionSectionToReadme(version, targetRoot);
|
|
29
31
|
copyScripts(tempDir, targetRoot);
|
package/src/core/version-yml.js
CHANGED
|
@@ -222,14 +222,17 @@ export function parseExisting(content) {
|
|
|
222
222
|
else if (/^\S/.test(l)) inPaths = false; // 들여쓰기 끝 → 블록 종료
|
|
223
223
|
}
|
|
224
224
|
}
|
|
225
|
-
// template: 블록 내 version
|
|
225
|
+
// template: 블록 내 version + mode (#502 — 업데이트 모드가 재실행할 통합 범위)
|
|
226
226
|
let templateVersion = "";
|
|
227
|
+
let templateMode = null;
|
|
227
228
|
let inTemplate = false;
|
|
228
229
|
for (const l of text.split("\n")) {
|
|
229
230
|
if (/^\s*template:/.test(l)) { inTemplate = true; continue; }
|
|
230
231
|
if (inTemplate) {
|
|
231
|
-
const
|
|
232
|
-
if (
|
|
232
|
+
const vm = l.match(/^\s*version:\s*"([0-9][0-9.]*)"/);
|
|
233
|
+
if (vm && !templateVersion) templateVersion = vm[1];
|
|
234
|
+
const mm = l.match(/^\s*mode:\s*"([a-z]+)"/);
|
|
235
|
+
if (mm && ["full", "version", "workflows"].includes(mm[1])) templateMode = mm[1];
|
|
233
236
|
if (/^\S/.test(l)) break;
|
|
234
237
|
}
|
|
235
238
|
}
|
|
@@ -238,7 +241,7 @@ export function parseExisting(content) {
|
|
|
238
241
|
const deployBranch = line(/^\s*deploy_branch:\s*"([^"]*)"/);
|
|
239
242
|
// 선택 워크플로우 옵션 (metadata.template.options — nexus/secret_backup)
|
|
240
243
|
const options = parseTemplateOptions(text);
|
|
241
|
-
return { version, versionCode, types, paths, templateVersion, options, defaultBranch, deployBranch };
|
|
244
|
+
return { version, versionCode, types, paths, templateVersion, templateMode, options, defaultBranch, deployBranch };
|
|
242
245
|
}
|
|
243
246
|
|
|
244
247
|
// version.yml 전체 생성 (.sh create_version_yml + save_template_options 신규 케이스 등가).
|
|
@@ -291,13 +294,15 @@ export function buildVersionYml({ version, types = [], paths = new Map(), pathMa
|
|
|
291
294
|
// template 옵션 블록 (.sh save_template_options 신규 추가 케이스). templateOptions 지정 시.
|
|
292
295
|
if (templateOptions) {
|
|
293
296
|
const { templateVersion = "unknown", deployTarget = "docker-ssh", publishTargets = [], includeSecretBackup = false, optionsDate = today,
|
|
294
|
-
changelogProvider = "github-ai", changelogBaseUrl = "", codeReviewCoderabbit = true, intent = null } = templateOptions;
|
|
297
|
+
changelogProvider = "github-ai", changelogBaseUrl = "", codeReviewCoderabbit = true, intent = null, mode = null } = templateOptions;
|
|
295
298
|
const publishJson = `[${publishTargets.map((t) => `"${t}"`).join(",")}]`;
|
|
296
299
|
// intent(프로젝트 성격, #485) — 미지정이면 deploy/publish에서 역추론해 기록 (재통합 시 진입 질문 생략용)
|
|
297
300
|
const intentVal = intent || inferIntent(deployTarget, publishTargets) || "manual";
|
|
298
301
|
out += ` template:\n`;
|
|
299
302
|
out += ` source: "projectops"\n`;
|
|
300
303
|
out += ` version: "${templateVersion}"\n`;
|
|
304
|
+
// mode(#502) — 이 레포에 통합된 범위. 업데이트 모드가 같은 범위를 재실행하는 기준.
|
|
305
|
+
if (mode) out += ` mode: "${mode}" # 통합 범위(full/version/workflows) — 업데이트 모드 재실행 기준\n`;
|
|
301
306
|
out += ` integrated_date: "${optionsDate}"\n`;
|
|
302
307
|
out += ` last_update_date: "${optionsDate}"\n`;
|
|
303
308
|
out += ` options:\n`;
|
package/src/core/wizard-env.js
CHANGED
|
@@ -34,6 +34,19 @@ export function setEnvLine(line, key, value) {
|
|
|
34
34
|
return out + cr;
|
|
35
35
|
}
|
|
36
36
|
|
|
37
|
+
// 설치본에서 env 실값 추출 (#502 — 업데이트 모드 carryover).
|
|
38
|
+
// 치환 시 @wizard 주석은 제거되지만 `KEY: "값"` 라인은 남는다(setEnvLine) — 그 값을 걷어온다.
|
|
39
|
+
// 템플릿 지식 불필요: substituteEnv가 values를 ask 키에만 참조하므로 무관 키가 섞여도 무해하다.
|
|
40
|
+
// 빈 값("")은 제외 — substituteEnv도 빈 chosen을 무시하고 기본값을 쓴다 (동일 규칙).
|
|
41
|
+
export function extractEnvValues(installedContent) {
|
|
42
|
+
const values = new Map();
|
|
43
|
+
for (const raw of String(installedContent || "").split(/\r?\n/)) {
|
|
44
|
+
const m = raw.match(/^\s*([A-Z_]+):\s*"([^"]*)"/);
|
|
45
|
+
if (m && m[2] !== "") values.set(m[1], m[2]);
|
|
46
|
+
}
|
|
47
|
+
return values;
|
|
48
|
+
}
|
|
49
|
+
|
|
37
50
|
// resolver — .sh resolve_token 등가. 값 계산은 주입된 resolvers로 위임(순수성 유지).
|
|
38
51
|
// resolvers: { repo, "spring-app-yml-dir"(type), "spring-app-yml-path"(type), "flutter-root" }
|
|
39
52
|
export function resolveToken(name, type, resolvers = {}) {
|
package/src/index.js
CHANGED
|
@@ -132,6 +132,8 @@ export async function run(argv, { cwd = process.cwd(), source = { type: "git" },
|
|
|
132
132
|
deployTarget,
|
|
133
133
|
publishTargets,
|
|
134
134
|
includeSecretBackup: opts.includeSecretBackup ?? existing?.options?.secretBackup ?? false,
|
|
135
|
+
// #502 — version 모드가 기존 full 통합 기록(mode)을 강등하지 않도록 (full이 우세)
|
|
136
|
+
recordMode: existing?.templateMode === "full" ? "full" : "version",
|
|
135
137
|
// 릴리스 배포 브랜치(#456): CLI 플래그 → version.yml 저장값 → 빈 값(미출력, 스킬이 develop 폴백)
|
|
136
138
|
deployBranch: opts.deployBranch || existing?.options?.deployBranch || "",
|
|
137
139
|
intent,
|
package/src/ui/prompts.js
CHANGED
|
@@ -6,17 +6,22 @@ import * as engine from "./readline-engine.js";
|
|
|
6
6
|
export const CANCEL = engine.CANCEL;
|
|
7
7
|
|
|
8
8
|
// 모드 선택 — 한국어 라벨, 내부 키 반환. 취소 시 CANCEL.
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
9
|
+
// update(#502): { from, to } — 기존 통합 레포로 판별된 경우. 맨 위에 업데이트 항목을 추가하고
|
|
10
|
+
// 기본 선택으로 둔다 (첫 항목 = 기본). 신규 레포(update 미전달)는 현행 5개 그대로.
|
|
11
|
+
export async function selectMode({ update = null } = {}) {
|
|
12
|
+
const options = [];
|
|
13
|
+
if (update) {
|
|
14
|
+
const range = update.from ? `v${update.from} → v${update.to}` : `v${update.to}`;
|
|
15
|
+
options.push({ value: "update", label: `업데이트 — 저장된 설정 그대로 ${range} 반영 (추천)` });
|
|
16
|
+
}
|
|
17
|
+
options.push(
|
|
18
|
+
{ value: "full", label: `전체 설치 — 버전관리 + 자동화 워크플로우 + 이슈·PR 템플릿${update ? "" : " (처음이라면 추천)"}` },
|
|
19
|
+
{ value: "version", label: "버전 관리만 — 버전 자동 증가·동기화 시스템만 설치" },
|
|
20
|
+
{ value: "workflows", label: "워크플로우만 — 빌드·배포 GitHub Actions만 설치" },
|
|
21
|
+
{ value: "issues", label: "이슈·PR 템플릿만 — GitHub 이슈/PR 양식만 설치" },
|
|
22
|
+
{ value: "skills", label: "AI 스킬만 — Claude·Cursor·Gemini·Codex·PI용 스킬만 설치" },
|
|
23
|
+
);
|
|
24
|
+
return engine.select({ message: "무엇을 설치할까요?", options });
|
|
20
25
|
}
|
|
21
26
|
|
|
22
27
|
// 프로젝트 확인 화면 메뉴 (계속/수정/취소).
|
package/src/ui/status-cards.js
CHANGED
|
@@ -85,7 +85,7 @@ export function printIdeStatus(statuses, out = (s) => process.stdout.write(s)) {
|
|
|
85
85
|
export function printInstallKind({ currentTemplateVersion = "", templateVersion = "" }, out = (s) => process.stdout.write(s)) {
|
|
86
86
|
if (currentTemplateVersion) {
|
|
87
87
|
out(`${GUT} ♻️ ${paint("업데이트", A.bold)} — 템플릿 ${paint(`v${currentTemplateVersion}`, A.dim)} → ${paint(`v${templateVersion}`, A.green)}\n`);
|
|
88
|
-
out(`${GUT} ${paint("version.yml에 이전 통합 기록이
|
|
88
|
+
out(`${GUT} ${paint("version.yml에 이전 통합 기록이 있습니다 — 메뉴 맨 위 '업데이트'가 저장된 설정 그대로 반영합니다", A.dim)}\n`);
|
|
89
89
|
} else {
|
|
90
90
|
out(`${GUT} 🆕 ${paint("신규 통합", A.bold)} — 이 프로젝트에 처음 설치합니다 (템플릿 ${paint(`v${templateVersion}`, A.green)})\n`);
|
|
91
91
|
out(`${GUT} ${paint("version.yml에 이전 통합 기록이 없어 신규로 봅니다", A.dim)}\n`);
|