projectops 4.31.5 → 4.33.0
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 +144 -145
- package/package.json +2 -2
- package/src/cli/args.js +65 -29
- package/src/cli/errors.js +9 -7
- package/src/cli/help.js +51 -3
- package/src/commands/doctor.js +80 -78
- package/src/commands/full.js +11 -2
- package/src/commands/interactive.js +71 -49
- package/src/commands/issues.js +4 -0
- package/src/commands/skills.js +8 -7
- package/src/commands/version.js +4 -2
- package/src/commands/workflows.js +3 -1
- package/src/context.js +4 -0
- package/src/core/breaking-check.js +6 -5
- package/src/core/copy/simple.js +4 -0
- package/src/core/copy/workflows.js +10 -5
- package/src/core/exclusions.js +8 -0
- package/src/core/ide/adapters/claude.js +18 -17
- package/src/core/ide/adapters/codex.js +12 -11
- package/src/core/ide/adapters/cursor.js +11 -10
- package/src/core/ide/adapters/gemini.js +11 -10
- package/src/core/ide/adapters/pi-common.js +2 -1
- package/src/core/ide/adapters/pi-harness.js +13 -16
- package/src/core/ide/adapters/pi.js +12 -11
- package/src/core/label-style.js +78 -0
- package/src/core/migration-guide.js +38 -38
- package/src/core/migrations/index.js +19 -15
- package/src/core/migrations/registry.js +70 -55
- package/src/core/options-ask.js +65 -64
- package/src/core/orphan-workflows.js +2 -1
- package/src/core/paths-resolve.js +31 -30
- package/src/core/repo-language.js +47 -0
- package/src/core/run-trace.js +10 -0
- package/src/core/version-yml.js +68 -36
- package/src/i18n/core.en.js +31 -0
- package/src/i18n/core.ko.js +31 -0
- package/src/i18n/doctor.en.js +89 -0
- package/src/i18n/doctor.ko.js +89 -0
- package/src/i18n/en.js +141 -0
- package/src/i18n/ide.en.js +102 -0
- package/src/i18n/ide.ko.js +102 -0
- package/src/i18n/index.js +73 -0
- package/src/i18n/ko.js +134 -0
- package/src/i18n/migrate.en.js +66 -0
- package/src/i18n/migrate.ko.js +66 -0
- package/src/i18n/options.en.js +104 -0
- package/src/i18n/options.ko.js +104 -0
- package/src/i18n/summary.en.js +70 -0
- package/src/i18n/summary.ko.js +70 -0
- package/src/index.js +46 -28
- package/src/ui/banner.js +8 -6
- package/src/ui/env-plan.js +19 -18
- package/src/ui/prompts.js +25 -24
- package/src/ui/readline-engine.js +18 -10
- package/src/ui/skills-prompts.js +8 -7
- package/src/ui/status-cards.js +38 -38
- package/src/ui/summary.js +103 -111
package/src/ui/summary.js
CHANGED
|
@@ -1,70 +1,71 @@
|
|
|
1
|
-
//
|
|
2
|
-
// ctx: { mode, types:[], version, counters:{ workflows, utilModules } }
|
|
1
|
+
// Completion summary, printed to stderr after an install.
|
|
2
|
+
// ctx: { mode, types:[], version, counters:{ workflows, workflowFiles, utilModules }, ... }
|
|
3
|
+
// All wording lives in src/i18n/summary.*.js so the screen follows the display language.
|
|
3
4
|
import { existsSync, readdirSync } from "node:fs";
|
|
4
5
|
import { join } from "node:path";
|
|
5
6
|
import { WORKFLOW_COMMON_PREFIX } from "../core/paths.js";
|
|
7
|
+
import { t } from "../i18n/index.js";
|
|
6
8
|
|
|
7
9
|
const SEPARATOR = "────────────────────────────────────────";
|
|
8
10
|
|
|
9
|
-
export function printSummary(ctx, targetRoot = ".") {
|
|
11
|
+
export function printSummary(ctx, targetRoot = ".", write = null) {
|
|
10
12
|
const { mode, types = [], version = "", counters = {} } = ctx || {};
|
|
11
|
-
const deployBranchName = ctx?.deployBranch || "develop";
|
|
12
|
-
const deployBranchReady = ctx?.deployBranchReady === true; //
|
|
13
|
-
const err = (s = "") => process.stderr.write(`${s}\n`);
|
|
14
|
-
//
|
|
15
|
-
const isTty = !!process.stderr.isTTY;
|
|
13
|
+
const deployBranchName = ctx?.deployBranch || "develop";
|
|
14
|
+
const deployBranchReady = ctx?.deployBranchReady === true; // the wizard confirmed or created it in this run
|
|
15
|
+
const err = (s = "") => (write ? write(`${s}\n`) : process.stderr.write(`${s}\n`));
|
|
16
|
+
// Colors only on a TTY.
|
|
17
|
+
const isTty = write ? false : !!process.stderr.isTTY;
|
|
16
18
|
const BOLD = isTty ? "\u001b[1m" : "";
|
|
17
19
|
const YELLOW = isTty ? "\x1b[1;33m" : "";
|
|
18
20
|
const CYAN = isTty ? "\x1b[0;36m" : "";
|
|
19
21
|
const NC = isTty ? "\x1b[0m" : "";
|
|
20
22
|
const utilModulesCopied = counters.utilModules ?? 0;
|
|
21
|
-
// README
|
|
23
|
+
// Without a README the version section is skipped, so do not claim it was added or auto-updated.
|
|
22
24
|
const hasReadme = existsSync(join(targetRoot, "README.md"));
|
|
23
25
|
|
|
24
26
|
err("");
|
|
25
27
|
err(SEPARATOR);
|
|
26
28
|
err("");
|
|
27
|
-
err("
|
|
29
|
+
err(t("done.title"));
|
|
28
30
|
err("");
|
|
29
31
|
err(SEPARATOR);
|
|
30
32
|
err("");
|
|
31
|
-
err("
|
|
33
|
+
err(t("done.integrated"));
|
|
32
34
|
|
|
33
|
-
// 모드별 체크리스트 (.sh L5449~5481)
|
|
34
35
|
switch (mode) {
|
|
35
36
|
case "full":
|
|
36
|
-
err(
|
|
37
|
-
if (hasReadme) err(
|
|
38
|
-
err(
|
|
39
|
-
if (utilModulesCopied > 0) err(` ✅
|
|
40
|
-
err(
|
|
41
|
-
err(
|
|
42
|
-
err(
|
|
43
|
-
err(
|
|
37
|
+
err(` ✅ ${t("done.feat.version")}`);
|
|
38
|
+
if (hasReadme) err(` ✅ ${t("done.feat.readme")}`);
|
|
39
|
+
err(` ✅ ${t("done.feat.workflows")}`);
|
|
40
|
+
if (utilModulesCopied > 0) err(` ✅ ${t("done.feat.util", { count: utilModulesCopied })}`);
|
|
41
|
+
err(` ✅ ${t("done.feat.templates")}`);
|
|
42
|
+
err(` ✅ ${t("done.feat.coderabbit")}`);
|
|
43
|
+
err(` ✅ ${t("done.feat.gitignore")}`);
|
|
44
|
+
err(` ✅ ${t("done.feat.guide")}`);
|
|
44
45
|
break;
|
|
45
46
|
case "version":
|
|
46
|
-
err(
|
|
47
|
-
if (hasReadme) err(
|
|
48
|
-
err(
|
|
49
|
-
err(
|
|
47
|
+
err(` ✅ ${t("done.feat.version")}`);
|
|
48
|
+
if (hasReadme) err(` ✅ ${t("done.feat.readme")}`);
|
|
49
|
+
err(` ✅ ${t("done.feat.gitignore")}`);
|
|
50
|
+
err(` ✅ ${t("done.feat.guide")}`);
|
|
50
51
|
break;
|
|
51
52
|
case "workflows":
|
|
52
|
-
err(
|
|
53
|
-
if (utilModulesCopied > 0) err(` ✅
|
|
54
|
-
err(
|
|
53
|
+
err(` ✅ ${t("done.feat.workflows")}`);
|
|
54
|
+
if (utilModulesCopied > 0) err(` ✅ ${t("done.feat.util", { count: utilModulesCopied })}`);
|
|
55
|
+
err(` ✅ ${t("done.feat.guide")}`);
|
|
55
56
|
break;
|
|
56
57
|
case "issues":
|
|
57
|
-
err(
|
|
58
|
+
err(` ✅ ${t("done.feat.templates")}`);
|
|
58
59
|
break;
|
|
59
60
|
case "skills":
|
|
60
|
-
err(
|
|
61
|
+
err(` ✅ ${t("done.feat.skills")}`);
|
|
61
62
|
break;
|
|
62
63
|
}
|
|
63
64
|
|
|
64
|
-
//
|
|
65
|
+
// Skills mode adds no files or workflows, so the screen ends here.
|
|
65
66
|
if (mode === "skills") {
|
|
66
67
|
err("");
|
|
67
|
-
err(
|
|
68
|
+
err(` 📖 TEMPLATE REPO: https://github.com/Cassiiopeia/projectops`);
|
|
68
69
|
err("");
|
|
69
70
|
err(SEPARATOR);
|
|
70
71
|
err("");
|
|
@@ -72,34 +73,32 @@ export function printSummary(ctx, targetRoot = ".") {
|
|
|
72
73
|
}
|
|
73
74
|
|
|
74
75
|
err("");
|
|
75
|
-
err("
|
|
76
|
-
err(` 📄
|
|
77
|
-
err(hasReadme ? "
|
|
76
|
+
err(t("done.addedFiles"));
|
|
77
|
+
err(` 📄 ${t("done.versionYml", { version, types: types.join(",") })}`);
|
|
78
|
+
err(` 📝 ${hasReadme ? t("done.readmeAdded") : t("done.readmeMissing")}`);
|
|
78
79
|
err("");
|
|
79
|
-
err("
|
|
80
|
+
err(t("done.addedWorkflows"));
|
|
80
81
|
|
|
81
|
-
//
|
|
82
|
-
// 오표기했고 카운터와 소스가 달라 (0개) 불일치가 났다. 복사 엔진의 copiedFiles가 유일한 소스.)
|
|
82
|
+
// Only files the copy engine actually wrote are listed (copiedFiles is the single source).
|
|
83
83
|
const copiedFiles = counters.workflowFiles ?? [];
|
|
84
84
|
const commonWorkflows = copiedFiles.filter((f) => f.startsWith(`${WORKFLOW_COMMON_PREFIX}-`));
|
|
85
85
|
const typeWorkflows = copiedFiles.filter((f) => !f.startsWith(`${WORKFLOW_COMMON_PREFIX}-`));
|
|
86
86
|
|
|
87
87
|
if (copiedFiles.length > 0) {
|
|
88
|
-
err(` 📦
|
|
88
|
+
err(` 📦 ${t("done.installedCount", { count: copiedFiles.length })}`);
|
|
89
89
|
for (const wf of commonWorkflows) err(` 📌 ${wf}`);
|
|
90
90
|
for (const wf of typeWorkflows) err(` 🎯 ${wf}`);
|
|
91
91
|
} else {
|
|
92
|
-
err(
|
|
92
|
+
err(` 📦 ${t("done.noneInstalled")}`);
|
|
93
93
|
}
|
|
94
94
|
|
|
95
|
-
//
|
|
96
|
-
// 건너뛴 것이 없으면 아무것도 출력하지 않는다 (기존 출력 유지).
|
|
95
|
+
// Workflows the user modified are kept as-is: say what differs and how to merge.
|
|
97
96
|
const kept = ctx?.skippedConflicts ?? [];
|
|
98
97
|
if (kept.length > 0) {
|
|
99
98
|
err("");
|
|
100
|
-
err(` ${YELLOW}✋
|
|
99
|
+
err(` ${YELLOW}✋ ${t("done.keptTitle", { count: kept.length })}${NC}`);
|
|
101
100
|
for (const k of kept) {
|
|
102
|
-
const cnt = k.added != null && k.removed != null ? ` (
|
|
101
|
+
const cnt = k.added != null && k.removed != null ? ` (${t("done.keptDiff", { added: k.added, removed: k.removed })})` : "";
|
|
103
102
|
err(` 📝 ${k.filename}${cnt}`);
|
|
104
103
|
if (k.incoming) {
|
|
105
104
|
err(` diff -u .github/workflows/${k.filename} ${k.incoming}`);
|
|
@@ -107,19 +106,18 @@ export function printSummary(ctx, targetRoot = ".") {
|
|
|
107
106
|
}
|
|
108
107
|
}
|
|
109
108
|
if (kept.some((k) => k.incoming)) {
|
|
110
|
-
err(
|
|
111
|
-
err(
|
|
109
|
+
err(` 💡 ${t("done.keptHint1")}`);
|
|
110
|
+
err(` ${t("done.keptHint2")}`);
|
|
112
111
|
}
|
|
113
112
|
}
|
|
114
113
|
|
|
115
|
-
//
|
|
116
|
-
// 사용자가 자기 수정이 사라졌는지 git diff 없이도 알 수 있어야 한다.
|
|
114
|
+
// No baseline to tell whether the file was edited: it was replaced and the original kept as .bak.
|
|
117
115
|
const replaced = ctx?.replacedBak ?? [];
|
|
118
116
|
if (replaced.length > 0) {
|
|
119
117
|
err("");
|
|
120
|
-
err(` ${YELLOW}⚠️
|
|
118
|
+
err(` ${YELLOW}⚠️ ${t("done.replacedTitle", { count: replaced.length })}${NC}`);
|
|
121
119
|
for (const f of replaced) err(` 💾 .github/workflows/${f}.bak`);
|
|
122
|
-
err(
|
|
120
|
+
err(` ${t("done.replacedHint")}`);
|
|
123
121
|
}
|
|
124
122
|
|
|
125
123
|
err("");
|
|
@@ -128,150 +126,144 @@ export function printSummary(ctx, targetRoot = ".") {
|
|
|
128
126
|
err(" └─ changelog_manager.py");
|
|
129
127
|
err("");
|
|
130
128
|
|
|
131
|
-
// util 모듈 목록 (.sh L5566~5581) — 타입별 .github/util/{type}/*/ 스캔
|
|
132
129
|
if (utilModulesCopied > 0) {
|
|
133
|
-
err(
|
|
134
|
-
for (const
|
|
135
|
-
const utilDir = join(targetRoot, ".github/util",
|
|
130
|
+
err(` 🧙 ${t("done.utilTitle")}`);
|
|
131
|
+
for (const type of types) {
|
|
132
|
+
const utilDir = join(targetRoot, ".github/util", type);
|
|
136
133
|
if (!existsSync(utilDir)) continue;
|
|
137
134
|
let entries = [];
|
|
138
|
-
try { entries = readdirSync(utilDir, { withFileTypes: true }); } catch { /*
|
|
135
|
+
try { entries = readdirSync(utilDir, { withFileTypes: true }); } catch { /* ignore */ }
|
|
139
136
|
for (const e of entries) {
|
|
140
|
-
if (e.isDirectory()) err(` ├─ ${e.name} (${
|
|
137
|
+
if (e.isDirectory()) err(` ├─ ${e.name} (${type})`);
|
|
141
138
|
}
|
|
142
139
|
}
|
|
143
140
|
err("");
|
|
144
141
|
}
|
|
145
142
|
|
|
146
|
-
// 프로젝트 타입별 안내 (.sh L5583~5599)
|
|
147
143
|
if (types.includes("spring")) {
|
|
148
|
-
err(
|
|
149
|
-
err(
|
|
150
|
-
err(
|
|
151
|
-
err(
|
|
144
|
+
err(` 💡 ${t("done.spring.title")}`);
|
|
145
|
+
err(` • ${t("done.spring.1")}`);
|
|
146
|
+
err(` • ${t("done.spring.2")}`);
|
|
147
|
+
err(` • ${t("done.spring.3")}`);
|
|
152
148
|
err("");
|
|
153
149
|
}
|
|
154
150
|
if (types.includes("flutter") && utilModulesCopied > 0) {
|
|
155
|
-
err(
|
|
151
|
+
err(` 💡 ${t("done.flutter.title")}`);
|
|
156
152
|
err(" • iOS TestFlight: .github/util/flutter/testflight-wizard/testflight-wizard.html");
|
|
157
153
|
err(" • Android Play Store: .github/util/flutter/playstore-wizard/playstore-wizard.html");
|
|
158
154
|
err(" • Firebase App Distribution: .github/util/flutter/firebase-wizard/firebase-wizard.html");
|
|
159
|
-
err(
|
|
155
|
+
err(` • ${t("done.flutter.open")}`);
|
|
160
156
|
err("");
|
|
161
157
|
}
|
|
162
158
|
|
|
163
159
|
err(" 📖 TEMPLATE REPO: https://github.com/Cassiiopeia/projectops");
|
|
164
|
-
err(
|
|
165
|
-
// #493 — 이번 실행의 마이그레이션 기록. "뭐가 남았고 AI에게 어떻게 시키는지"가 바로 보이게 행동 유도형으로 안내.
|
|
166
|
-
// 실행 기록 위치 (#561) — 무슨 일이 있었는지 나중에 확인할 자리를 알린다.
|
|
160
|
+
err(` 📚 ${t("done.workflowGuide")}: .github/workflows/project-types/README.md`);
|
|
167
161
|
|
|
168
|
-
//
|
|
162
|
+
// Post-install verification: expand only when something is wrong, one line otherwise.
|
|
169
163
|
const verification = ctx?.verification;
|
|
170
164
|
if (verification) {
|
|
171
165
|
const unresolved = verification.unresolved || [];
|
|
172
166
|
err(SEPARATOR);
|
|
173
167
|
err("");
|
|
174
168
|
if (unresolved.length === 0) {
|
|
175
|
-
err(
|
|
169
|
+
err(`✅ ${t("done.verify.ok")}`);
|
|
176
170
|
} else {
|
|
177
|
-
err(`${YELLOW}⚠️
|
|
171
|
+
err(`${YELLOW}⚠️ ${t("done.verify.bad", { count: unresolved.length })}${NC}`);
|
|
178
172
|
err("");
|
|
179
|
-
err(
|
|
180
|
-
err(
|
|
173
|
+
err(` ${t("done.verify.explain1")}`);
|
|
174
|
+
err(` ${t("done.verify.explain2")}`);
|
|
181
175
|
err("");
|
|
182
176
|
for (const u of unresolved.slice(0, 10)) {
|
|
183
177
|
err(` ${u.filename}:${u.line} ${u.token}`);
|
|
184
178
|
}
|
|
185
|
-
if (unresolved.length > 10) err(`
|
|
179
|
+
if (unresolved.length > 10) err(` ${t("done.verify.more", { count: unresolved.length - 10 })}`);
|
|
186
180
|
}
|
|
187
181
|
err("");
|
|
188
182
|
|
|
189
|
-
//
|
|
183
|
+
// Secrets needed by the installed workflows only.
|
|
190
184
|
const secrets = verification.secrets;
|
|
191
185
|
if (secrets && secrets.size > 0) {
|
|
192
|
-
err(`${CYAN}🔑
|
|
186
|
+
err(`${CYAN}🔑 ${t("done.secrets.title", { count: secrets.size })}${NC}`);
|
|
193
187
|
err(" → Repository Settings > Secrets and variables > Actions");
|
|
194
188
|
err("");
|
|
195
189
|
for (const [name, users] of secrets) {
|
|
196
|
-
const where = users.length > 2 ?
|
|
190
|
+
const where = users.length > 2 ? t("done.secrets.more", { list: users.slice(0, 2).join(", "), count: users.length - 2 }) : users.join(", ");
|
|
197
191
|
err(` ${name}`);
|
|
198
192
|
err(` └ ${where}`);
|
|
199
193
|
}
|
|
200
194
|
err("");
|
|
201
|
-
err(
|
|
195
|
+
err(` 💡 ${t("done.secrets.hint")}`);
|
|
202
196
|
err("");
|
|
203
197
|
}
|
|
204
198
|
}
|
|
205
199
|
|
|
206
|
-
// 필수 3가지 작업 안내 (.sh L5605~5625 — 원문 유지)
|
|
207
200
|
err(SEPARATOR);
|
|
208
201
|
err("");
|
|
209
|
-
err(`${YELLOW}⚠️
|
|
202
|
+
err(`${YELLOW}⚠️ ${t("done.todo.title")}${NC}`);
|
|
210
203
|
err("");
|
|
211
|
-
//
|
|
212
|
-
err(
|
|
213
|
-
err(
|
|
214
|
-
err(
|
|
204
|
+
// A PAT is optional: releases complete without it, it only makes follow-up automation smoother.
|
|
205
|
+
err(` 1️⃣ ${t("done.todo.pat")}`);
|
|
206
|
+
err(` → ${t("done.todo.pat1")}`);
|
|
207
|
+
err(` ${t("done.todo.pat2")}`);
|
|
215
208
|
err(" → Repository Settings > Secrets > Actions");
|
|
216
209
|
err(" → Secret Name: _GITHUB_PAT_TOKEN / Scopes: repo, workflow");
|
|
217
210
|
err("");
|
|
218
|
-
//
|
|
211
|
+
// Do not re-instruct a task the wizard already did.
|
|
219
212
|
if (deployBranchReady) {
|
|
220
|
-
err(` 2️⃣ ✅ ${
|
|
213
|
+
err(` 2️⃣ ✅ ${t("done.todo.branchReady", { branch: deployBranchName })}`);
|
|
221
214
|
} else {
|
|
222
|
-
err(` 2️⃣ ${
|
|
215
|
+
err(` 2️⃣ ${t("done.todo.branch", { branch: deployBranchName })}`);
|
|
223
216
|
err(` → git checkout -b ${deployBranchName} && git push -u origin ${deployBranchName}`);
|
|
224
217
|
}
|
|
225
218
|
err("");
|
|
226
|
-
//
|
|
219
|
+
// Only explain what the user selected: instructions for a disabled feature blur what to do.
|
|
227
220
|
let step = 3;
|
|
228
221
|
if (ctx?.aiPrSummary !== false) {
|
|
229
|
-
err(` ${step}️⃣ (
|
|
230
|
-
err(
|
|
231
|
-
err(
|
|
222
|
+
err(` ${step}️⃣ ${t("done.todo.ai")}`);
|
|
223
|
+
err(` → ${t("done.todo.ai1")}`);
|
|
224
|
+
err(` ${t("done.todo.ai2")}`);
|
|
232
225
|
err("");
|
|
233
|
-
err(
|
|
226
|
+
err(` ① ${t("done.todo.aiKey")}`);
|
|
234
227
|
err(" https://aistudio.google.com/apikey");
|
|
235
|
-
err(
|
|
228
|
+
err(` ② ${t("done.todo.aiRegister")}`);
|
|
236
229
|
err(" Settings > Secrets and variables > Actions > New repository secret");
|
|
237
|
-
err(` ${BOLD}Name: GEMINI_API_KEY${NC} Secret:
|
|
230
|
+
err(` ${BOLD}Name: GEMINI_API_KEY${NC} Secret: ${t("done.todo.aiSecret")}`);
|
|
238
231
|
err("");
|
|
239
|
-
err(
|
|
240
|
-
err(" OPENAI_API_KEY
|
|
232
|
+
err(` 💡 ${t("done.todo.aiOthers")}`);
|
|
233
|
+
err(" OPENAI_API_KEY, ANTHROPIC_API_KEY, GROQ_API_KEY, MISTRAL_API_KEY");
|
|
241
234
|
err("");
|
|
242
235
|
step++;
|
|
243
236
|
}
|
|
244
237
|
if (ctx?.codeReviewCoderabbit === true) {
|
|
245
|
-
err(` ${step}️⃣
|
|
246
|
-
err(
|
|
247
|
-
err(
|
|
248
|
-
err(
|
|
238
|
+
err(` ${step}️⃣ ${t("done.todo.coderabbit")}`);
|
|
239
|
+
err(` → ${t("done.todo.coderabbit1")}`);
|
|
240
|
+
err(` → ${t("done.todo.coderabbit2")}`);
|
|
241
|
+
err(` → ${t("done.todo.coderabbit3")}`);
|
|
249
242
|
err("");
|
|
250
243
|
step++;
|
|
251
244
|
}
|
|
252
|
-
//
|
|
253
|
-
//
|
|
254
|
-
err(` ${step}️⃣ (
|
|
255
|
-
err(
|
|
256
|
-
err(
|
|
245
|
+
// Label sync only runs on pushes that change issue-labels.yml, and the first push after install can miss it.
|
|
246
|
+
// Status labels missing means label automation is silently ignored, so ask for one manual run.
|
|
247
|
+
err(` ${step}️⃣ ${t("done.todo.labels")}`);
|
|
248
|
+
err(` → ${t("done.todo.labels1")}`);
|
|
249
|
+
err(` → ${t("done.todo.labels2")}`);
|
|
257
250
|
err("");
|
|
258
251
|
step++;
|
|
259
252
|
err(SEPARATOR);
|
|
260
253
|
err("");
|
|
261
|
-
err(`${CYAN}📖
|
|
254
|
+
err(`${CYAN}📖 ${t("done.guide")}${NC}`);
|
|
262
255
|
err(" → PROJECTOPS-SETUP-GUIDE.md");
|
|
263
256
|
err("");
|
|
264
|
-
//
|
|
265
|
-
// 사용자가 "로그 얘기가 없다"고 느낀다. 문제가 생겼을 때 가장 먼저 찾는 것이므로 마지막 자리다.
|
|
257
|
+
// The run record goes last: with a long secret list the top scrolls away, and this is the first thing people need when something breaks.
|
|
266
258
|
if (ctx?.logFile || ctx?.logDir || ctx?.migrationGuidePath) {
|
|
267
|
-
err(`${CYAN}📁
|
|
259
|
+
err(`${CYAN}📁 ${t("done.log.title")}${NC}`);
|
|
268
260
|
if (ctx?.logFile || ctx?.logDir) {
|
|
269
|
-
err(`
|
|
270
|
-
if (ctx.traceFile) err(`
|
|
261
|
+
err(` ${t("done.log.log")}: ${ctx.logFile || `${ctx.logDir}/`}`);
|
|
262
|
+
if (ctx.traceFile) err(` ${t("done.log.events")}: ${ctx.traceFile}`);
|
|
271
263
|
}
|
|
272
|
-
if (ctx?.migrationGuidePath) err(`
|
|
273
|
-
err(
|
|
274
|
-
err(
|
|
264
|
+
if (ctx?.migrationGuidePath) err(` ${t("done.log.guide")}: ${ctx.migrationGuidePath}`);
|
|
265
|
+
err(` ${t("done.log.note")}`);
|
|
266
|
+
err(` 💡 ${t("done.log.ask")}`);
|
|
275
267
|
err("");
|
|
276
268
|
}
|
|
277
269
|
}
|