projectops 4.2.2 → 4.2.3

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 CHANGED
@@ -7,7 +7,7 @@
7
7
  > 이슈 등록부터 커밋, 보고서, 배포까지. 개발자는 코드만 작성하세요.
8
8
 
9
9
  <!-- AUTO-VERSION-SECTION: DO NOT EDIT MANUALLY -->
10
- ## 최신 버전 : v4.2.1 (2026-07-09)
10
+ ## 최신 버전 : v4.2.2 (2026-07-09)
11
11
 
12
12
  [전체 버전 기록 보기](CHANGELOG.md)
13
13
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "projectops",
3
- "version": "4.2.2",
3
+ "version": "4.2.3",
4
4
  "description": "ProjectOps — 완전 자동화 GitHub 프로젝트 관리 템플릿 통합 CLI (구 SUH-DEVOPS-TEMPLATE 마법사)",
5
5
  "keywords": [
6
6
  "devops",
@@ -6,6 +6,7 @@
6
6
  // 계약: 취소(ESC/Ctrl+C)는 CANCEL 심볼 반환. 각 함수 async.
7
7
  import { emitKeypressEvents } from "node:readline";
8
8
  import { stdin, stdout } from "node:process";
9
+ import { visualWidth } from "./ansi.js";
9
10
 
10
11
  export const CANCEL = Symbol("cancel");
11
12
 
@@ -28,18 +29,31 @@ const S_BAR = paint("│", c.gray);
28
29
  const S_Q = paint("◆", c.cyan);
29
30
  const S_DONE = paint("◇", c.green);
30
31
 
32
+ // 한 논리 라인이 터미널 폭에서 차지하는 물리 줄 수 (wrap 고려).
33
+ // WHY: 긴 라벨(한글 2칸 포함)이 터미널 폭을 넘으면 터미널이 자동 줄바꿈해 물리적으로 2줄+가 된다.
34
+ // 커서 되감기(ESC[nA)는 물리 줄 기준이므로, 논리 줄 수만 세면 wrap된 만큼 덜 올라가
35
+ // 이전 출력이 안 지워지고 매 렌더마다 화면이 아래로 밀린다. (실측: Windows PowerShell 폭 80)
36
+ export function physicalRows(line, cols) {
37
+ const w = visualWidth(line); // ANSI 시퀀스 제외 + CJK 2칸
38
+ if (cols <= 0) return 1;
39
+ // 빈 줄도 최소 1행. 폭의 정수배로 딱 떨어지면 wrap 안 되므로 그대로(80칸 라인@80폭 = 1행).
40
+ if (w === 0) return 1;
41
+ return Math.max(1, Math.ceil(w / cols));
42
+ }
43
+
31
44
  // 여러 줄 지운 뒤 커서를 블록 시작으로 되돌리는 렌더러.
32
- // prevLines 만큼 위로 올라가 지우고 새로 그린다.
45
+ // 직전 렌더의 "물리 줄 수"만큼 위로 올라가 지우고 새로 그린다 (wrap 안전).
33
46
  function makeRenderer() {
34
- let prevLines = 0;
47
+ let prevRows = 0; // 직전 렌더가 실제로 차지한 물리 줄 수
35
48
  return {
36
49
  render(lines) {
37
- if (prevLines > 0) stdout.write(`${ESC}${prevLines}A`); // 위로
50
+ if (prevRows > 0) stdout.write(`${ESC}${prevRows}A`); // 물리 줄 기준으로 위로
38
51
  stdout.write(`${ESC}0J`); // 커서 아래 전부 지우기
39
52
  stdout.write(lines.join("\n") + "\n");
40
- prevLines = lines.length;
53
+ const cols = stdout.columns || 80; // 터미널 폭 (파이프 등으로 없으면 80 가정)
54
+ prevRows = lines.reduce((sum, l) => sum + physicalRows(l, cols), 0);
41
55
  },
42
- reset() { prevLines = 0; },
56
+ reset() { prevRows = 0; },
43
57
  };
44
58
  }
45
59
 
@@ -80,11 +80,15 @@ export function printIdeStatus(statuses, out = (s) => process.stdout.write(s)) {
80
80
  }
81
81
 
82
82
  // 층5 — 신규 통합 vs 업데이트 판별 라인 (Breaking 박스는 breaking-check.js)
83
+ // WHY 판정: version.yml의 metadata.template.version(이전 통합 흔적)이 있으면 업데이트, 없으면 신규.
84
+ // 사용자가 "무슨 기준으로 신규/업데이트인지"를 바로 알도록 판정 근거를 화면에 밝힌다.
83
85
  export function printInstallKind({ currentTemplateVersion = "", templateVersion = "" }, out = (s) => process.stdout.write(s)) {
84
86
  if (currentTemplateVersion) {
85
87
  out(`${GUT} ♻️ ${paint("업데이트", A.bold)} — 템플릿 ${paint(`v${currentTemplateVersion}`, A.dim)} → ${paint(`v${templateVersion}`, A.green)}\n`);
88
+ out(`${GUT} ${paint("version.yml에 이전 통합 기록이 있어 업데이트로 진행합니다", A.dim)}\n`);
86
89
  } else {
87
90
  out(`${GUT} 🆕 ${paint("신규 통합", A.bold)} — 이 프로젝트에 처음 설치합니다 (템플릿 ${paint(`v${templateVersion}`, A.green)})\n`);
91
+ out(`${GUT} ${paint("version.yml에 이전 통합 기록이 없어 신규로 봅니다", A.dim)}\n`);
88
92
  }
89
93
  out(`${GUT}\n`);
90
94
  }