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
|
@@ -11,34 +11,34 @@ export const GUIDE_FILE = `${MIGRATION_DIR}/PROJECTOPS-MIGRATION-GUIDE.md`;
|
|
|
11
11
|
export const GUIDE_SCHEMA = 1;
|
|
12
12
|
|
|
13
13
|
// 고정 헤더 — 문서 목적 + AI Agent 해석 가이드라인. 최초 생성 시 1회만 쓴다 (엔트리와 분리).
|
|
14
|
-
const HEADER = `# ProjectOps
|
|
14
|
+
const HEADER = `# ProjectOps Migration Guide
|
|
15
15
|
|
|
16
|
-
|
|
17
|
-
|
|
16
|
+
This document is **written automatically every time a run finishes** by the projectops wizard (\`npx projectops\`).
|
|
17
|
+
Run entries are appended below in chronological order, and existing entries are never modified.
|
|
18
18
|
|
|
19
|
-
##
|
|
19
|
+
## How humans read it
|
|
20
20
|
|
|
21
|
-
-
|
|
22
|
-
-
|
|
23
|
-
-
|
|
21
|
+
- The **checklist** in each entry lists the manual work a person still has to finish after that run.
|
|
22
|
+
- The checklist only contains things that actually happened in the run. No items means nothing to do.
|
|
23
|
+
- Detailed run records (per-file decisions and before/after values) are in the entry metadata: \`trace_file\` (JSONL) and \`log_file\` (raw terminal output).
|
|
24
24
|
|
|
25
|
-
##
|
|
25
|
+
## Guidelines for AI agents
|
|
26
26
|
|
|
27
|
-
|
|
27
|
+
Parse the \`\`\`yaml metadata block (\`# projectops-migration\`) of each entry and decide and act with the rules below.
|
|
28
28
|
|
|
29
|
-
|
|
|
30
|
-
|
|
31
|
-
| \`workflows.leftover_old_gen\` |
|
|
32
|
-
| \`workflows.skipped_conflict\` |
|
|
33
|
-
| \`workflows.replaced_bak\` |
|
|
34
|
-
| \`env_applied\` |
|
|
35
|
-
| \`breaking_traversed\` |
|
|
36
|
-
| \`manual_actions_pending\` |
|
|
37
|
-
| \`trace_file\` |
|
|
38
|
-
| \`log_file\` |
|
|
29
|
+
| Field | Meaning | Recommended action |
|
|
30
|
+
|-------|---------|--------------------|
|
|
31
|
+
| \`workflows.leftover_old_gen\` | Old-generation workflows kept because they may be live | Check for trigger overlap with the new ones, then suggest deleting the old file once the switch is confirmed |
|
|
32
|
+
| \`workflows.skipped_conflict\` | The user's modified file was kept (new template not applied) | Diff the user's file against the new template and suggest a merge |
|
|
33
|
+
| \`workflows.replaced_bak\` | Replaced by the new one after a .bak backup | Compare .bak with the new file, review any lost customizations, then clean up the .bak |
|
|
34
|
+
| \`env_applied\` | Environment values applied to workflows | Compare with the real workflow env and warn on drift |
|
|
35
|
+
| \`breaking_traversed\` | Breaking changes this run passed through (full action steps are in the human section) | Check that items with \`action_required: true\` were handled |
|
|
36
|
+
| \`manual_actions_pending\` | Codes of the remaining manual work | If not empty, remind the user |
|
|
37
|
+
| \`trace_file\` | JSONL of per-file decisions and before/after values (Layer 2) | To learn why a file ended up this way, grep by file name |
|
|
38
|
+
| \`log_file\` | Raw terminal output (Layer 3) | For reproducing the run and forensic debugging |
|
|
39
39
|
|
|
40
|
-
-
|
|
41
|
-
-
|
|
40
|
+
- The schema is versioned by the \`schema\` field. Ignore unknown fields and use only the ones you know.
|
|
41
|
+
- When there are several entries, the **latest entry** is the reference for the current state. Older entries are history.
|
|
42
42
|
`;
|
|
43
43
|
|
|
44
44
|
// ── yaml 렌더 헬퍼 (외부 의존성 없이 수동 직렬화 — version-yml.js와 동일 원칙) ──
|
|
@@ -98,45 +98,45 @@ export function renderGuideEntry(report) {
|
|
|
98
98
|
L.push("");
|
|
99
99
|
L.push(`## ${r.now || ""} - v${from} → v${to} (${r.mode || "full"})`);
|
|
100
100
|
L.push("");
|
|
101
|
-
L.push(`-
|
|
102
|
-
L.push(`-
|
|
101
|
+
L.push(`- Types: ${(r.types ?? []).join(", ") || "-"} / deploy: ${r.options?.deploy ?? "-"} / publish: ${(r.options?.publish ?? []).join(",") || "none"}`);
|
|
102
|
+
L.push(`- Workflows: ${wf.added.length + wf.replacedBak.length} new/updated, ${(r.counters?.skipped ?? 0)} kept (unchanged or skipped on conflict)`);
|
|
103
103
|
L.push("");
|
|
104
104
|
|
|
105
105
|
// ── 확인 체크리스트 (동적 — 실제 발생분만) ──
|
|
106
106
|
const checklist = [];
|
|
107
107
|
if (leftoverOldGen.length) {
|
|
108
|
-
checklist.push(`- [ ]
|
|
109
|
-
for (const o of leftoverOldGen) checklist.push(` - \`${o.file}\`${o.replacement ? ` →
|
|
108
|
+
checklist.push(`- [ ] **Switch to the new workflow, then delete ${leftoverOldGen.length} old-generation deploy workflow(s)** (they may be live deployments, so the wizard left them alone):`);
|
|
109
|
+
for (const o of leftoverOldGen) checklist.push(` - \`${o.file}\`${o.replacement ? ` → new \`${o.replacement}\`` : ""}`);
|
|
110
110
|
}
|
|
111
111
|
if (wf.replacedBak.length || legacyNeutralized.length) {
|
|
112
|
-
checklist.push(`- [ ]
|
|
113
|
-
for (const f of wf.replacedBak) checklist.push(` - \`${f}.bak\` (
|
|
114
|
-
for (const a of legacyNeutralized) if (a.to && String(a.to).endsWith(".bak")) checklist.push(` - \`${a.to}\` (
|
|
112
|
+
checklist.push(`- [ ] **Review the .bak backup files, then clean them up** (compare with the new file to be sure no customization was lost):`);
|
|
113
|
+
for (const f of wf.replacedBak) checklist.push(` - \`${f}.bak\` (backup from conflict replacement)`);
|
|
114
|
+
for (const a of legacyNeutralized) if (a.to && String(a.to).endsWith(".bak")) checklist.push(` - \`${a.to}\` (legacy neutralized)`);
|
|
115
115
|
}
|
|
116
116
|
if (wf.skippedConflict.length) {
|
|
117
|
-
checklist.push(`- [ ]
|
|
117
|
+
checklist.push(`- [ ] **Kept ${wf.skippedConflict.length} modified file(s), review a merge with the new template**: ${wf.skippedConflict.map((f) => `\`${f}\``).join(", ")}`);
|
|
118
118
|
// #654 — 새 템플릿 사본이 있으면 비교 방법까지 적는다
|
|
119
119
|
for (const d of wf.skippedConflictDetails.filter((x) => x.incoming)) {
|
|
120
|
-
const cnt = d.added != null && d.removed != null ? ` (
|
|
120
|
+
const cnt = d.added != null && d.removed != null ? ` (+${d.added} / -${d.removed} lines)` : "";
|
|
121
121
|
checklist.push(` - \`${d.file}\`${cnt}: \`diff -u .github/workflows/${d.file} ${d.incoming}\``);
|
|
122
122
|
}
|
|
123
123
|
}
|
|
124
124
|
if (wf.added.length || wf.replacedBak.length) {
|
|
125
|
-
checklist.push(`- [ ]
|
|
125
|
+
checklist.push(`- [ ] **Check that the GitHub Secrets required by the new/updated CICD are registered** (Settings → Secrets → Actions, including \`_GITHUB_PAT_TOKEN\`)`);
|
|
126
126
|
}
|
|
127
127
|
if (envByType.size) {
|
|
128
|
-
checklist.push(`- [ ]
|
|
128
|
+
checklist.push(`- [ ] **Verify the applied deploy environment values** (edit the workflow env directly if they differ from your real environment):`);
|
|
129
129
|
for (const [t, kv] of envByType) {
|
|
130
130
|
for (const [k, v] of kv) checklist.push(` - ${t} \`${k}\` = \`${v}\``);
|
|
131
131
|
}
|
|
132
132
|
}
|
|
133
133
|
if (r.branches?.created === true) {
|
|
134
|
-
checklist.push(`- [x]
|
|
134
|
+
checklist.push(`- [x] Development (release source) branch \`${r.branches?.deployBranch ?? "develop"}\`: created and verified by the wizard`);
|
|
135
135
|
} else if (r.branches?.ready === false) {
|
|
136
|
-
checklist.push(`- [ ]
|
|
136
|
+
checklist.push(`- [ ] Create the development (release source) branch \`${r.branches?.deployBranch ?? "develop"}\` (required for the release PR to work)`);
|
|
137
137
|
}
|
|
138
138
|
if (checklist.length) {
|
|
139
|
-
L.push("###
|
|
139
|
+
L.push("### Checklist");
|
|
140
140
|
L.push("");
|
|
141
141
|
L.push(...checklist);
|
|
142
142
|
L.push("");
|
|
@@ -144,7 +144,7 @@ export function renderGuideEntry(report) {
|
|
|
144
144
|
|
|
145
145
|
// ── 버전 점프에서 통과한 호환성 변경 (조치 방법 전문 — 터미널에서 스킵해도 여기 남는다) ──
|
|
146
146
|
if (breakingAll.length) {
|
|
147
|
-
L.push(`###
|
|
147
|
+
L.push(`### Breaking changes passed through (v${breaking.current} → v${breaking.target})`);
|
|
148
148
|
L.push("");
|
|
149
149
|
for (const it of breakingAll) {
|
|
150
150
|
const sev = (breaking.critical ?? []).includes(it) ? "CRITICAL" : "WARNING";
|
|
@@ -155,7 +155,7 @@ export function renderGuideEntry(report) {
|
|
|
155
155
|
}
|
|
156
156
|
|
|
157
157
|
// ── AI 메타데이터 ──
|
|
158
|
-
L.push("### AI
|
|
158
|
+
L.push("### AI metadata");
|
|
159
159
|
L.push("");
|
|
160
160
|
L.push("```yaml");
|
|
161
161
|
L.push("# projectops-migration (machine-readable)");
|
|
@@ -164,7 +164,7 @@ export function renderGuideEntry(report) {
|
|
|
164
164
|
L.push(`template: { from: ${yq(from)}, to: ${yq(to)} }`);
|
|
165
165
|
L.push(`mode: ${r.mode || "full"}`);
|
|
166
166
|
L.push(`types: ${ylist(r.types)}`);
|
|
167
|
-
L.push(`options: { deploy: ${yq(r.options?.deploy ?? "")}, publish: ${ylist(r.options?.publish)}, secret_backup: ${r.options?.secretBackup === true}, coderabbit: ${r.options?.coderabbit === true}, changelog_provider: ${yq(r.options?.changelogProvider ?? "")}, intent: ${yq(r.options?.intent ?? "")}, semver_auto: ${r.options?.semverAuto === true}, app_release: ${r.options?.appRelease === true} }`);
|
|
167
|
+
L.push(`options: { deploy: ${yq(r.options?.deploy ?? "")}, publish: ${ylist(r.options?.publish)}, secret_backup: ${r.options?.secretBackup === true}, coderabbit: ${r.options?.coderabbit === true}, changelog_provider: ${yq(r.options?.changelogProvider ?? "")}, intent: ${yq(r.options?.intent ?? "")}, semver_auto: ${r.options?.semverAuto === true}, language: ${yq(r.options?.language ?? "")}, label_style: ${yq(r.options?.labelStyle ?? "")}, close_on_release: ${r.options?.closeOnRelease === true}, app_release: ${r.options?.appRelease === true} }`);
|
|
168
168
|
L.push(`branches: { default: ${yq(r.branches?.defaultBranch ?? "main")}, deploy: ${yq(r.branches?.deployBranch ?? "develop")}, deploy_branch_created: ${r.branches?.created === true} }`);
|
|
169
169
|
L.push("workflows:");
|
|
170
170
|
L.push(` added: ${ylist(wf.added)}`);
|
|
@@ -2,10 +2,14 @@
|
|
|
2
2
|
// 감지(registry × rules) → 계획 표시 → safe 티어 확인 1회 → 적용 → confirm 티어 안내.
|
|
3
3
|
// 버전 번호를 믿지 않는다: 절반의 레거시 레포는 version.yml에 template 메타가 없다(실측).
|
|
4
4
|
import { MIGRATIONS } from "./registry.js";
|
|
5
|
+
import { t } from "../../i18n/index.js";
|
|
5
6
|
import * as workflowRule from "./rules/obsolete-workflows.js";
|
|
6
7
|
import * as rootFileRule from "./rules/root-files.js";
|
|
7
8
|
import * as legacyDirRule from "./rules/legacy-dirs.js";
|
|
8
9
|
|
|
10
|
+
// 사유는 화면 언어로 번역(reasonKey), 키가 없으면 원문 그대로
|
|
11
|
+
const reasonOf = (e) => (e.reasonKey ? t(e.reasonKey) : e.reason);
|
|
12
|
+
|
|
9
13
|
const RULES = {
|
|
10
14
|
workflow: workflowRule,
|
|
11
15
|
"root-file": rootFileRule,
|
|
@@ -53,60 +57,60 @@ export async function runMigrations({ targetRoot = ".", askYesNo = null, log = c
|
|
|
53
57
|
|
|
54
58
|
let applied = [];
|
|
55
59
|
if (safe.length > 0) {
|
|
56
|
-
log(
|
|
60
|
+
log(t("migrate.safe.found", { n: safe.length }));
|
|
57
61
|
for (const e of safe) {
|
|
58
62
|
const arrow = e.replacedBy ? ` → ${e.replacedBy}` : "";
|
|
59
63
|
log(` • ${e.file}${arrow}`);
|
|
60
|
-
log(
|
|
64
|
+
log(t("migrate.safe.reason", { reason: reasonOf(e), since: e.since }));
|
|
61
65
|
}
|
|
62
66
|
const yes = askYesNo
|
|
63
|
-
? await askYesNo(
|
|
67
|
+
? await askYesNo(t("migrate.safe.ask", { n: safe.length }), true)
|
|
64
68
|
: true;
|
|
65
69
|
if (yes) {
|
|
66
70
|
applied = applySafeMigrations(targetRoot, safe);
|
|
67
71
|
const ok = applied.filter((r) => r.action !== "error");
|
|
68
72
|
const failed = applied.filter((r) => r.action === "error");
|
|
69
|
-
log(
|
|
73
|
+
log(t("migrate.safe.done", { n: ok.length, failed: failed.length ? t("migrate.safe.failed", { n: failed.length }) : "" }));
|
|
70
74
|
for (const f of failed) log(` ⚠️ ${f.from}: ${f.error}`);
|
|
71
75
|
} else {
|
|
72
|
-
log(
|
|
76
|
+
log(t("migrate.safe.skip"));
|
|
73
77
|
}
|
|
74
78
|
}
|
|
75
79
|
|
|
76
80
|
// ask 티어 (#476) — 사용자 문서가 담긴 구명칭 폴더: 대화형은 확인 후 이동, 비대화형은 안내만
|
|
77
81
|
let askPending = [];
|
|
78
82
|
if (ask.length > 0) {
|
|
79
|
-
log(
|
|
83
|
+
log(t("migrate.dir.found", { n: ask.length }));
|
|
80
84
|
for (const e of ask) {
|
|
81
85
|
log(` • ${e.file}/ → ${e.replacedBy}/`);
|
|
82
|
-
log(` (${e
|
|
86
|
+
log(` (${reasonOf(e)})`);
|
|
83
87
|
}
|
|
84
88
|
if (askYesNo) {
|
|
85
|
-
const yes = await askYesNo("
|
|
89
|
+
const yes = await askYesNo(t("migrate.dir.ask"), true);
|
|
86
90
|
if (yes === true) {
|
|
87
91
|
const results = applySafeMigrations(targetRoot, ask);
|
|
88
92
|
applied = applied.concat(results);
|
|
89
93
|
for (const r of results) {
|
|
90
94
|
if (r.action === "error") log(` ⚠️ ${r.from}: ${r.error}`);
|
|
91
|
-
else log(
|
|
95
|
+
else log(t("migrate.dir.moved", { from: r.from, to: r.to, moved: r.moved ?? 0, kept: r.skipped ? t("migrate.dir.kept", { n: r.skipped }) : "" }));
|
|
92
96
|
}
|
|
93
97
|
} else {
|
|
94
98
|
askPending = ask;
|
|
95
|
-
log(
|
|
99
|
+
log(t("migrate.dir.skip"));
|
|
96
100
|
}
|
|
97
101
|
} else {
|
|
98
102
|
askPending = ask;
|
|
99
|
-
log(
|
|
103
|
+
log(t("migrate.dir.nonInteractive"));
|
|
100
104
|
}
|
|
101
105
|
}
|
|
102
106
|
|
|
103
107
|
if (confirm.length > 0) {
|
|
104
|
-
log(
|
|
108
|
+
log(t("migrate.confirm.found", { n: confirm.length }));
|
|
105
109
|
for (const e of confirm) {
|
|
106
|
-
const arrow = e.replacedBy ?
|
|
107
|
-
log(` • ${e.file}${arrow}
|
|
110
|
+
const arrow = e.replacedBy ? t("migrate.confirm.newer", { name: e.replacedBy }) : "";
|
|
111
|
+
log(` • ${e.file}${arrow}: ${reasonOf(e)}`);
|
|
108
112
|
}
|
|
109
|
-
log("
|
|
113
|
+
log(t("migrate.confirm.hint"));
|
|
110
114
|
}
|
|
111
115
|
|
|
112
116
|
return { applied, confirmPending: confirm, askPending };
|
|
@@ -24,183 +24,198 @@
|
|
|
24
24
|
// (rules/settings-extractors.js의 EXTRACTORS 키)
|
|
25
25
|
//
|
|
26
26
|
// 데이터 출처: git 히스토리 삭제/리네임 전수 + 실제 통합 레포 14개 스캔 (2026-07-11, 설계 문서 참조)
|
|
27
|
+
import { tEn } from "../../i18n/index.js";
|
|
28
|
+
|
|
27
29
|
export const MIGRATIONS = [
|
|
28
30
|
// ── workflow / safe — 순수 리네임·대체 (공존 시 중복 실행 실해) ──────────────
|
|
29
31
|
{ id: "wf-version-control", category: "workflow", tier: "safe",
|
|
30
32
|
file: "PROJECT-VERSION-CONTROL.yaml", replacedBy: "PROJECT-COMMON-VERSION-CONTROL.yaml",
|
|
31
|
-
since: "2.x",
|
|
33
|
+
since: "2.x", reasonKey: "migrate.reason.gen1VersionControl" },
|
|
32
34
|
{ id: "wf-readme-version-update", category: "workflow", tier: "safe",
|
|
33
35
|
file: "PROJECT-README-VERSION-UPDATE.yaml", replacedBy: "PROJECT-COMMON-README-VERSION-UPDATE.yaml",
|
|
34
|
-
since: "2.x",
|
|
36
|
+
since: "2.x", reasonKey: "migrate.reason.gen1Readme" },
|
|
35
37
|
{ id: "wf-sync-issue-labels-v1", category: "workflow", tier: "safe",
|
|
36
38
|
file: "PROJECT-SYNC-ISSUE-LABELS.yaml", replacedBy: "PROJECT-COMMON-SYNC-ISSUE-LABELS.yaml",
|
|
37
|
-
since: "2.x",
|
|
39
|
+
since: "2.x", reasonKey: "migrate.reason.gen1Labels" },
|
|
38
40
|
{ id: "wf-sync-issue-labels-v0", category: "workflow", tier: "safe",
|
|
39
41
|
file: "sync-issue-labels.yaml", replacedBy: "PROJECT-COMMON-SYNC-ISSUE-LABELS.yaml",
|
|
40
|
-
since: "2.x",
|
|
42
|
+
since: "2.x", reasonKey: "migrate.reason.gen0Labels" },
|
|
41
43
|
{ id: "wf-issue-comment-v1", category: "workflow", tier: "safe",
|
|
42
44
|
file: "PROJECT-ISSUE-COMMENT.yaml", replacedBy: "PROJECT-COMMON-SUH-ISSUE-HELPER.yaml",
|
|
43
|
-
since: "2.x",
|
|
45
|
+
since: "2.x", reasonKey: "migrate.reason.helperGen1" },
|
|
44
46
|
{ id: "wf-issue-comment-v2", category: "workflow", tier: "safe",
|
|
45
47
|
file: "PROJECT-COMMON-ISSUE-COMMENT.yaml", replacedBy: "PROJECT-COMMON-SUH-ISSUE-HELPER.yaml",
|
|
46
|
-
since: "2.x",
|
|
48
|
+
since: "2.x", reasonKey: "migrate.reason.helperGen2" },
|
|
47
49
|
{ id: "wf-issue-helper-api", category: "workflow", tier: "safe",
|
|
48
50
|
file: "PROJECT-COMMON-SUH-ISSUE-HELPER-API.yaml", replacedBy: "PROJECT-COMMON-SUH-ISSUE-HELPER.yaml",
|
|
49
|
-
since: "4.3.0",
|
|
51
|
+
since: "4.3.0", reasonKey: "migrate.reason.helperApi" },
|
|
50
52
|
{ id: "wf-issue-helper-module", category: "workflow", tier: "safe",
|
|
51
53
|
file: "PROJECT-COMMON-SUH-ISSUE-HELPER-MODULE.yml", replacedBy: "PROJECT-COMMON-SUH-ISSUE-HELPER.yaml",
|
|
52
|
-
since: "4.3.0",
|
|
54
|
+
since: "4.3.0", reasonKey: "migrate.reason.helperModule",
|
|
53
55
|
settingsExtractor: "suh-issue-helper-module" }, // 무해화 전 with: 커스텀 값을 version.yml로 이관
|
|
54
56
|
{ id: "wf-auto-changelog-v1", category: "workflow", tier: "safe",
|
|
55
57
|
file: "PROJECT-AUTO-CHANGELOG-CONTROL.yaml", replacedBy: "PROJECT-COMMON-RELEASE-CHANGELOG.yaml",
|
|
56
|
-
since: "2.x",
|
|
58
|
+
since: "2.x", reasonKey: "migrate.reason.releaseGen1" },
|
|
57
59
|
{ id: "wf-auto-changelog-v2", category: "workflow", tier: "safe",
|
|
58
60
|
file: "PROJECT-COMMON-AUTO-CHANGELOG-CONTROL.yaml", replacedBy: "PROJECT-COMMON-RELEASE-CHANGELOG.yaml",
|
|
59
|
-
since: "4.3.0",
|
|
61
|
+
since: "4.3.0", reasonKey: "migrate.reason.releaseRename" },
|
|
60
62
|
{ id: "wf-comqa-bot", category: "workflow", tier: "safe",
|
|
61
63
|
file: "COMQA-ISSUE-CREATION-BOT.yaml", replacedBy: "PROJECT-COMMON-QA-ISSUE-CREATION-BOT.yaml",
|
|
62
|
-
since: "2.x",
|
|
64
|
+
since: "2.x", reasonKey: "migrate.reason.qaBot" },
|
|
63
65
|
{ id: "wf-template-util-sync", category: "workflow", tier: "safe",
|
|
64
66
|
file: "TEMPLATE-UTIL-VERSION-SYNC.yml", replacedBy: "PROJECT-COMMON-TEMPLATE-UTIL-VERSION-SYNC.yml",
|
|
65
|
-
since: "2.x",
|
|
67
|
+
since: "2.x", reasonKey: "migrate.reason.utilSync" },
|
|
66
68
|
{ id: "wf-backlog-manager", category: "workflow", tier: "safe",
|
|
67
69
|
file: "PROJECT-COMMON-PROJECT-BACKLOG-MANAGER.yaml", replacedBy: "PROJECT-COMMON-PROJECTS-SYNC-MANAGER.yaml",
|
|
68
|
-
since: "3.x",
|
|
70
|
+
since: "3.x", reasonKey: "migrate.reason.projectsSync" },
|
|
69
71
|
{ id: "wf-suh-lab-build-trigger", category: "workflow", tier: "safe",
|
|
70
72
|
file: "PROJECT-FLUTTER-SUH-LAB-APP-BUILD-TRIGGER.yaml", replacedBy: "PROJECT-FLUTTER-PROJECTOPS-APP-BUILD-TRIGGER.yaml",
|
|
71
|
-
since: "4.3.0",
|
|
73
|
+
since: "4.3.0", reasonKey: "migrate.reason.buildTrigger" },
|
|
72
74
|
{ id: "wf-next-ci", category: "workflow", tier: "safe",
|
|
73
75
|
file: "PROJECT-NEXT-CI.yaml", replacedBy: "PROJECT-REACT-CI.yaml",
|
|
74
|
-
since: "4.1.0",
|
|
76
|
+
since: "4.1.0", reasonKey: "migrate.reason.nextType" },
|
|
75
77
|
{ id: "wf-next-cicd", category: "workflow", tier: "safe",
|
|
76
78
|
file: "PROJECT-NEXT-CICD.yaml", replacedBy: "PROJECT-REACT-CICD.yaml",
|
|
77
|
-
since: "4.1.0",
|
|
79
|
+
since: "4.1.0", reasonKey: "migrate.reason.nextType" },
|
|
78
80
|
{ id: "wf-flutter-ci-yml-ext", category: "workflow", tier: "safe",
|
|
79
81
|
file: "PROJECT-FLUTTER-CI.yml", replacedBy: "PROJECT-FLUTTER-CI.yaml",
|
|
80
|
-
since: "2.x",
|
|
82
|
+
since: "2.x", reasonKey: "migrate.reason.ymlExt" },
|
|
81
83
|
{ id: "wf-flutter-android-pr-ci", category: "workflow", tier: "safe",
|
|
82
84
|
file: "PROJECT-FLUTTER-ANDROID-PR-CI.yaml", replacedBy: "PROJECT-FLUTTER-CI.yaml",
|
|
83
|
-
since: "3.x",
|
|
85
|
+
since: "3.x", reasonKey: "migrate.reason.mergedCi" },
|
|
84
86
|
|
|
85
87
|
// ── workflow / confirm — 배포/업로드 계열 (현역 배포일 수 있어 자동 조치 금지) ──
|
|
86
88
|
{ id: "wf-syn-spring-simple", category: "workflow", tier: "confirm",
|
|
87
89
|
file: "PROJECT-SPRING-SYNOLOGY-SIMPLE-CICD.yaml", replacedBy: "PROJECT-SPRING-SIMPLE-CICD.yaml",
|
|
88
|
-
since: "3.0.137",
|
|
90
|
+
since: "3.0.137", reasonKey: "migrate.reason.synSsh" },
|
|
89
91
|
{ id: "wf-syn-spring-nonstop", category: "workflow", tier: "confirm",
|
|
90
92
|
file: "PROJECT-SPRING-SYNOLOGY-NONSTOP-CICD.yaml", replacedBy: "PROJECT-SPRING-NONSTOP-NGINX-CICD.yaml",
|
|
91
|
-
since: "3.0.137",
|
|
93
|
+
since: "3.0.137", reasonKey: "migrate.reason.synNonstop" },
|
|
92
94
|
{ id: "wf-syn-spring-preview", category: "workflow", tier: "confirm",
|
|
93
95
|
file: "PROJECT-SPRING-SYNOLOGY-PR-PREVIEW.yaml", replacedBy: "PROJECT-SPRING-PR-PREVIEW.yaml",
|
|
94
|
-
since: "3.0.137",
|
|
96
|
+
since: "3.0.137", reasonKey: "migrate.reason.syn" },
|
|
95
97
|
{ id: "wf-syn-flutter-android", category: "workflow", tier: "confirm",
|
|
96
98
|
file: "PROJECT-FLUTTER-ANDROID-SYNOLOGY-CICD.yaml", replacedBy: "PROJECT-FLUTTER-ANDROID-SELFHOSTED-CICD.yaml",
|
|
97
|
-
since: "3.0.137",
|
|
99
|
+
since: "3.0.137", reasonKey: "migrate.reason.synSmb" },
|
|
98
100
|
{ id: "wf-syn-python-cicd", category: "workflow", tier: "confirm",
|
|
99
101
|
file: "PROJECT-PYTHON-SYNOLOGY-CICD.yaml", replacedBy: "PROJECT-PYTHON-SIMPLE-CICD.yaml",
|
|
100
|
-
since: "3.0.137",
|
|
102
|
+
since: "3.0.137", reasonKey: "migrate.reason.syn" },
|
|
101
103
|
{ id: "wf-syn-python-preview", category: "workflow", tier: "confirm",
|
|
102
104
|
file: "PROJECT-PYTHON-SYNOLOGY-PR-PREVIEW.yaml", replacedBy: "PROJECT-PYTHON-PR-PREVIEW.yaml",
|
|
103
|
-
since: "3.0.137",
|
|
105
|
+
since: "3.0.137", reasonKey: "migrate.reason.syn" },
|
|
104
106
|
{ id: "wf-syn-secret-upload", category: "workflow", tier: "confirm",
|
|
105
107
|
file: "PROJECT-COMMON-SYNOLOGY-SECRET-FILE-UPLOAD.yaml", replacedBy: "PROJECT-COMMON-SECRET-FILE-UPLOAD.yaml",
|
|
106
|
-
since: "3.0.137",
|
|
108
|
+
since: "3.0.137", reasonKey: "migrate.reason.synSecret" },
|
|
109
|
+
// #661 — 템플릿에서 사라졌는데 레거시로 잡히지 않던 릴리스 워크플로우. 이 파일의 trunk-based 분기는
|
|
110
|
+
// 지금은 없는 `changelog_manager.py ai-summary` 를 불러 릴리스 때 실패한다. 그 레포의 유일한
|
|
111
|
+
// 릴리스 경로일 수 있어 자동으로 건드리지 않고 안내만 한다. 같은 파일명을 쓰는 사용자 파일을
|
|
112
|
+
// 오탐하지 않도록 내용 마커로 한 번 더 확인한다.
|
|
113
|
+
{ id: "wf-release-publish-v1", category: "workflow", tier: "confirm",
|
|
114
|
+
file: "PROJECT-COMMON-RELEASE-PUBLISH.yaml", replacedBy: "PROJECT-COMMON-RELEASE-CHANGELOG.yaml",
|
|
115
|
+
since: "2.x", reasonKey: "migrate.reason.releasePublish1", contentMarker: "ai-summary" },
|
|
107
116
|
{ id: "wf-spring-ci-v1", category: "workflow", tier: "confirm",
|
|
108
117
|
file: "PROJECT-SPRING-CI.yaml", replacedBy: null,
|
|
109
|
-
since: "2.x",
|
|
118
|
+
since: "2.x", reasonKey: "migrate.reason.springCi1" },
|
|
110
119
|
{ id: "wf-spring-cicd-v1", category: "workflow", tier: "confirm",
|
|
111
120
|
file: "PROJECT-SPRING-CICD.yaml", replacedBy: "PROJECT-SPRING-SIMPLE-CICD.yaml",
|
|
112
|
-
since: "2.x",
|
|
121
|
+
since: "2.x", reasonKey: "migrate.reason.springDeploy1" },
|
|
113
122
|
{ id: "wf-python-cicd-v1", category: "workflow", tier: "confirm",
|
|
114
123
|
file: "PROJECT-PYTHON-CICD.yaml", replacedBy: "PROJECT-PYTHON-SIMPLE-CICD.yaml",
|
|
115
|
-
since: "3.x",
|
|
124
|
+
since: "3.x", reasonKey: "migrate.reason.pythonDeploy1" },
|
|
116
125
|
{ id: "wf-android-cicd-v1", category: "workflow", tier: "confirm",
|
|
117
126
|
file: "PROJECT-ANDROID-CICD.yaml", replacedBy: "PROJECT-FLUTTER-ANDROID-PLAYSTORE-CICD.yaml",
|
|
118
|
-
since: "2.x",
|
|
127
|
+
since: "2.x", reasonKey: "migrate.reason.androidDeploy1" },
|
|
119
128
|
{ id: "wf-ios-testflight-v1", category: "workflow", tier: "confirm",
|
|
120
129
|
file: "PROJECT-IOS-TESTFLIGHT-CICD.yml", replacedBy: "PROJECT-FLUTTER-IOS-TESTFLIGHT.yaml",
|
|
121
|
-
since: "2.x",
|
|
130
|
+
since: "2.x", reasonKey: "migrate.reason.iosDeploy1" },
|
|
122
131
|
{ id: "wf-flutter-ios-cicd", category: "workflow", tier: "confirm",
|
|
123
132
|
file: "PROJECT-FLUTTER-IOS-CICD.yaml", replacedBy: "PROJECT-FLUTTER-IOS-TESTFLIGHT.yaml",
|
|
124
|
-
since: "3.x",
|
|
133
|
+
since: "3.x", reasonKey: "migrate.reason.iosTestflight" },
|
|
125
134
|
{ id: "wf-file-upload-spring", category: "workflow", tier: "confirm",
|
|
126
135
|
file: "PROJECT-SPRING-AUTO-FILE-UPLOAD.yaml", replacedBy: "PROJECT-COMMON-SECRET-FILE-UPLOAD.yaml",
|
|
127
|
-
since: "3.x",
|
|
136
|
+
since: "3.x", reasonKey: "migrate.reason.secretUpload" },
|
|
128
137
|
{ id: "wf-file-upload-flutter", category: "workflow", tier: "confirm",
|
|
129
138
|
file: "PROJECT-FLUTTER-AUTO-FILE-UPLOAD.yaml", replacedBy: "PROJECT-COMMON-SECRET-FILE-UPLOAD.yaml",
|
|
130
|
-
since: "3.x",
|
|
139
|
+
since: "3.x", reasonKey: "migrate.reason.secretUpload" },
|
|
131
140
|
{ id: "wf-file-upload-python", category: "workflow", tier: "confirm",
|
|
132
141
|
file: "PROJECT-PYTHON-AUTO-FILE-UPLOAD.yaml", replacedBy: "PROJECT-COMMON-SECRET-FILE-UPLOAD.yaml",
|
|
133
|
-
since: "3.x",
|
|
142
|
+
since: "3.x", reasonKey: "migrate.reason.secretUpload" },
|
|
134
143
|
{ id: "wf-file-upload-v1", category: "workflow", tier: "confirm",
|
|
135
144
|
file: "PROJECT-FILE-AUTO-UPLOAD.yaml", replacedBy: "PROJECT-COMMON-SECRET-FILE-UPLOAD.yaml",
|
|
136
|
-
since: "2.x",
|
|
145
|
+
since: "2.x", reasonKey: "migrate.reason.secretUpload" },
|
|
137
146
|
{ id: "wf-nexus-publish-v1", category: "workflow", tier: "confirm",
|
|
138
147
|
file: "PROJECT-NEXUS-PUBLISH.yml", replacedBy: "PROJECT-SPRING-NEXUS-PUBLISH.yml",
|
|
139
|
-
since: "2.x",
|
|
148
|
+
since: "2.x", reasonKey: "migrate.reason.nexusPublish" },
|
|
140
149
|
{ id: "wf-nexus-ci-v1", category: "workflow", tier: "confirm",
|
|
141
150
|
file: "PROJECT-NEXUS-MODULE-CI-BUILD-CHECK.yml", replacedBy: "PROJECT-SPRING-NEXUS-CI.yml",
|
|
142
|
-
since: "2.x",
|
|
151
|
+
since: "2.x", reasonKey: "migrate.reason.nexusCi" },
|
|
143
152
|
{ id: "wf-ci-build-check-v0", category: "workflow", tier: "confirm",
|
|
144
153
|
file: "PROJECT-CI-BUILD-CHECK.yml", replacedBy: "PROJECT-SPRING-NEXUS-CI.yml",
|
|
145
|
-
since: "2.x",
|
|
154
|
+
since: "2.x", reasonKey: "migrate.reason.libCi0" },
|
|
146
155
|
{ id: "wf-publish-v0", category: "workflow", tier: "confirm",
|
|
147
156
|
file: "PROJECT-PUBLISH.yml", replacedBy: "PROJECT-SPRING-NEXUS-PUBLISH.yml",
|
|
148
|
-
since: "2.x",
|
|
157
|
+
since: "2.x", reasonKey: "migrate.reason.libPublish0" },
|
|
149
158
|
{ id: "wf-deploy-trigger", category: "workflow", tier: "confirm",
|
|
150
159
|
file: "PROJECT-DEPLOY-TRIGGER.yaml", replacedBy: null,
|
|
151
|
-
since: "3.x",
|
|
160
|
+
since: "3.x", reasonKey: "migrate.reason.deployTrigger" },
|
|
152
161
|
|
|
153
162
|
// ── root-file / safe — 구 설치 가이드 (사용자가 매번 수동 삭제하던 것) ─────────
|
|
154
163
|
{ id: "root-setup-guide-v2", category: "root-file", tier: "safe",
|
|
155
164
|
file: "SUH-DEVOPS-TEMPLATE-SETUP-GUIDE.md", replacedBy: "PROJECTOPS-SETUP-GUIDE.md",
|
|
156
|
-
since: "4.3.0",
|
|
165
|
+
since: "4.3.0", reasonKey: "migrate.reason.guideRebrand" },
|
|
157
166
|
{ id: "root-setup-guide-v1", category: "root-file", tier: "safe",
|
|
158
167
|
file: "SETUP-GUIDE.md", replacedBy: "PROJECTOPS-SETUP-GUIDE.md",
|
|
159
|
-
since: "2.x",
|
|
168
|
+
since: "2.x", reasonKey: "migrate.reason.guideOld",
|
|
160
169
|
contentMarker: "SUH" }, // 범용 파일명이라 내용에 템플릿 마커가 있을 때만 소유 판정
|
|
161
170
|
|
|
162
171
|
// ── legacy-dir / ask — 구명칭 산출물 폴더 (사용자 문서 보존 이동, #476) ────────
|
|
163
172
|
{ id: "dir-docs-suh-template", category: "legacy-dir", tier: "ask",
|
|
164
173
|
file: "docs/suh-template", replacedBy: "docs/projectops",
|
|
165
|
-
since: "4.2.9",
|
|
174
|
+
since: "4.2.9", reasonKey: "migrate.reason.docsDir" },
|
|
166
175
|
|
|
167
176
|
// ── util-file / safe — Flutter 마법사 스크립트 Python 단일화 (#500) ──────────
|
|
168
177
|
// 로컬 실행 도구라 CI 중복 실행 위험은 없지만, 신형 py와 공존 시 사용자가 구 스크립트를
|
|
169
178
|
// 실행해 갈라진 동작을 볼 수 있다. 마법사 폴더에 사용자 상태가 없어 삭제가 기대 동작.
|
|
170
179
|
{ id: "util-playstore-setup-sh", category: "util-file", tier: "safe",
|
|
171
180
|
file: ".github/util/flutter/playstore-wizard/playstore-wizard-setup.sh", replacedBy: "playstore-wizard.py",
|
|
172
|
-
since: "4.3.0",
|
|
181
|
+
since: "4.3.0", reasonKey: "migrate.reason.wizSetup" },
|
|
173
182
|
{ id: "util-playstore-setup-ps1", category: "util-file", tier: "safe",
|
|
174
183
|
file: ".github/util/flutter/playstore-wizard/playstore-wizard-setup.ps1", replacedBy: "playstore-wizard.py",
|
|
175
|
-
since: "4.3.0",
|
|
184
|
+
since: "4.3.0", reasonKey: "migrate.reason.wizSetup" },
|
|
176
185
|
{ id: "util-playstore-apply-sh", category: "util-file", tier: "safe",
|
|
177
186
|
file: ".github/util/flutter/playstore-wizard/playstore-wizard-apply.sh", replacedBy: "playstore-wizard.py",
|
|
178
|
-
since: "4.3.0",
|
|
187
|
+
since: "4.3.0", reasonKey: "migrate.reason.wizApply" },
|
|
179
188
|
{ id: "util-playstore-apply-ps1", category: "util-file", tier: "safe",
|
|
180
189
|
file: ".github/util/flutter/playstore-wizard/playstore-wizard-apply.ps1", replacedBy: "playstore-wizard.py",
|
|
181
|
-
since: "4.3.0",
|
|
190
|
+
since: "4.3.0", reasonKey: "migrate.reason.wizApply" },
|
|
182
191
|
{ id: "util-playstore-detect-sh", category: "util-file", tier: "safe",
|
|
183
192
|
file: ".github/util/flutter/playstore-wizard/detect-application-id.sh", replacedBy: "playstore-wizard.py",
|
|
184
|
-
since: "4.3.0",
|
|
193
|
+
since: "4.3.0", reasonKey: "migrate.reason.wizDetect" },
|
|
185
194
|
{ id: "util-playstore-detect-ps1", category: "util-file", tier: "safe",
|
|
186
195
|
file: ".github/util/flutter/playstore-wizard/detect-application-id.ps1", replacedBy: "playstore-wizard.py",
|
|
187
|
-
since: "4.3.0",
|
|
196
|
+
since: "4.3.0", reasonKey: "migrate.reason.wizDetect" },
|
|
188
197
|
{ id: "util-playstore-patch-py", category: "util-file", tier: "safe",
|
|
189
198
|
file: ".github/util/flutter/playstore-wizard/patch-build-gradle.py", replacedBy: "playstore-wizard.py",
|
|
190
|
-
since: "4.3.0",
|
|
199
|
+
since: "4.3.0", reasonKey: "migrate.reason.wizPatch" },
|
|
191
200
|
{ id: "util-firebase-setup-sh", category: "util-file", tier: "safe",
|
|
192
201
|
file: ".github/util/flutter/firebase-wizard/firebase-wizard-setup.sh", replacedBy: "firebase-wizard.py",
|
|
193
|
-
since: "4.3.0",
|
|
202
|
+
since: "4.3.0", reasonKey: "migrate.reason.wizSetup" },
|
|
194
203
|
{ id: "util-firebase-setup-ps1", category: "util-file", tier: "safe",
|
|
195
204
|
file: ".github/util/flutter/firebase-wizard/firebase-wizard-setup.ps1", replacedBy: "firebase-wizard.py",
|
|
196
|
-
since: "4.3.0",
|
|
205
|
+
since: "4.3.0", reasonKey: "migrate.reason.wizSetup" },
|
|
197
206
|
{ id: "util-firebase-test-sh", category: "util-file", tier: "safe",
|
|
198
207
|
file: ".github/util/flutter/firebase-wizard/test/setup-script-test.sh", replacedBy: "test/setup-script-test.py",
|
|
199
|
-
since: "4.3.0",
|
|
208
|
+
since: "4.3.0", reasonKey: "migrate.reason.wizTest" },
|
|
200
209
|
{ id: "util-firebase-test-ps1", category: "util-file", tier: "safe",
|
|
201
210
|
file: ".github/util/flutter/firebase-wizard/test/setup-script-test.ps1", replacedBy: "test/setup-script-test.py",
|
|
202
|
-
since: "4.3.0",
|
|
211
|
+
since: "4.3.0", reasonKey: "migrate.reason.wizTest" },
|
|
203
212
|
{ id: "util-testflight-setup-sh", category: "util-file", tier: "safe",
|
|
204
213
|
file: ".github/util/flutter/testflight-wizard/testflight-wizard-setup.sh", replacedBy: "testflight-wizard.py",
|
|
205
|
-
since: "4.3.0",
|
|
214
|
+
since: "4.3.0", reasonKey: "migrate.reason.wizSetup" },
|
|
206
215
|
];
|
|
216
|
+
|
|
217
|
+
// 사유는 영문 정본(tEn)으로 노출해 실행 로그·가이드에 그대로 쓰고,
|
|
218
|
+
// 화면 표시는 reasonKey를 t()로 번역해 쓴다.
|
|
219
|
+
for (const m of MIGRATIONS) {
|
|
220
|
+
Object.defineProperty(m, "reason", { enumerable: true, get: () => tEn(m.reasonKey) });
|
|
221
|
+
}
|