Skip to content

Commit 0be286c

Browse files
minsoo-webclaude
andauthored
fix: improve update command UX with confirmation and brew sync (#7)
* fix: improve update command with brew update, confirmation prompt, and -v flag - Add `brew update` before `brew upgrade` to sync formula cache (best-effort) - Add Y/n confirmation prompt before upgrade (default: Yes) - Add `-y/--yes` flag to Update subcommand to skip confirmation - Show upgrade instructions instead of hanging in non-TTY environments - Detect missing CWD before running brew and show helpful error message - Add `-v` short flag for `--version` - Apply confirmation prompt to both Homebrew and Cargo paths - Update Unknown path to suggest `brew update && brew upgrade` Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * chore: bump version to 0.4.1 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
1 parent fcf41e5 commit 0be286c

8 files changed

Lines changed: 474 additions & 9 deletions

File tree

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "chromaport"
3-
version = "0.4.0"
3+
version = "0.4.1"
44
edition = "2021"
55
description = "Migrate VS Code / Cursor themes to Superset, Warp, and more"
66
license = "MIT"
Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
# Brainstorm: update 커맨드 개선
2+
3+
**Date:** 2026-03-10
4+
**Status:** Complete
5+
6+
## What We're Building
7+
8+
`chromaport update` 서브커맨드의 안정성과 UX를 개선한다.
9+
10+
### 현재 문제
11+
12+
1. **CWD 미존재 시 brew 실패**: 삭제된 디렉토리(예: 정리된 git worktree)에서 실행하면 `brew upgrade``getcwd` 에러로 실패함
13+
2. **Formula 미동기화**: GitHub release는 0.4.0이지만 Homebrew tap formula가 아직 0.3.0이면, `brew upgrade`가 "already installed"를 반환하는데 코드는 이를 성공으로 처리하고 "Updated successfully!" 출력
14+
3. **`-v` 플래그 미지원**: `chromaport -v`가 에러 발생, `--version`만 동작
15+
16+
### 변경 사항
17+
18+
1. **`brew update` 추가**: `brew upgrade` 전에 `brew update`를 실행하여 formula를 최신 상태로 갱신
19+
2. **확인 후 실행**: 업데이트 명령어를 보여주고 Y/n 확인 후 실행
20+
3. **CWD 에러 메시지 개선**: brew 실패 시 CWD 문제를 감지하고 친절한 안내 메시지 출력
21+
4. **`-v` 플래그 추가**: clap의 `short_flag = 'v'``--version`의 단축 플래그 지원
22+
23+
## Why This Approach
24+
25+
- **`brew update` 추가**: formula 동기화 문제의 근본 원인 해결. `brew upgrade`만으로는 로컬 formula가 오래된 경우 업데이트 불가
26+
- **확인 후 실행**: 사용자가 실행될 명령어를 확인할 수 있어 투명성 확보. 네트워크 작업이므로 확인이 합리적
27+
- **에러 메시지 개선**: `$HOME`으로 자동 변경하는 대신, 사용자에게 상황을 설명하고 직접 해결하도록 안내. 예측 가능한 동작 유지
28+
- **clap short_flag**: 가장 관용적이고 간단한 방법. 별도 인자 정의 불필요
29+
30+
## Key Decisions
31+
32+
| 결정 | 선택 | 대안 | 이유 |
33+
|------|------|------|------|
34+
| 서브커맨드 이름 | `update` 유지 | `update-check`로 변경 | 직접 실행 기능을 유지하므로 `update`가 적절 |
35+
| brew 실행 방식 | `brew update``brew upgrade` | `brew upgrade`만 실행 | formula 동기화 문제 해결 |
36+
| 실행 전 확인 | Y/n 프롬프트 | 바로 실행 / 안내만 | 투명성과 편의성 균형 |
37+
| CWD 문제 대응 | 에러 메시지 개선 | `$HOME`으로 자동 변경 | 사용자에게 명시적 안내가 더 예측 가능 |
38+
| `-v` 구현 | clap `short_flag` | 별도 `-v` 인자 | clap 관용적 패턴, 최소 코드 |
39+
40+
## Implementation Notes
41+
42+
### update 실행 플로우 (개선 후)
43+
44+
```
45+
1. GitHub API로 최신 버전 확인
46+
2. 새 버전 없으면 "이미 최신입니다" 출력 후 종료
47+
3. 새 버전 있으면:
48+
a. "새 버전이 있습니다: 0.3.0 → 0.4.0" 출력
49+
b. 설치 방법 감지 (Homebrew/Cargo/Unknown)
50+
c. Homebrew인 경우:
51+
- "다음 명령어를 실행합니다: brew update && brew upgrade chromaport"
52+
- "계속하시겠습니까? (Y/n)" 프롬프트
53+
- 확인 시 brew update 실행 → brew upgrade chromaport 실행
54+
- brew 실패 시 CWD 에러 감지 → 친절한 안내 메시지
55+
d. Cargo인 경우: 기존과 동일 (cargo install chromaport)
56+
e. Unknown인 경우: 기존과 동일 (수동 안내)
57+
```
58+
59+
### CWD 에러 감지
60+
61+
brew 실패 시 stderr에 "current working directory" 또는 "getcwd" 포함 여부로 감지하거나, 실행 전에 `std::env::current_dir()` 체크 후 선제적 안내.
62+
63+
### passive notification 메시지 변경
64+
65+
`print_update_notice()`의 "Run `chromaport update` to upgrade." 메시지는 그대로 유지 (서브커맨드명 변경 없음).
66+
67+
### `-v` 플래그
68+
69+
```rust
70+
#[command(
71+
version,
72+
short_flag = 'v', // 추가
73+
about = "..."
74+
)]
75+
```
76+
77+
## Scope
78+
79+
- `src/cli.rs`: `-v` 플래그 추가
80+
- `src/update.rs`: `run_update()` 개선 (brew update 추가, Y/n 확인, CWD 에러 처리)
81+
- `tests/cli.rs`: `-v` 플래그 테스트 추가, update 관련 테스트 업데이트
Lines changed: 280 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,280 @@
1+
---
2+
title: "fix: update 커맨드 안정성 및 UX 개선"
3+
type: fix
4+
status: completed
5+
date: 2026-03-10
6+
origin: docs/brainstorms/2026-03-10-update-command-improvements-brainstorm.md
7+
---
8+
9+
# fix: update 커맨드 안정성 및 UX 개선
10+
11+
## Overview
12+
13+
`chromaport update`의 세 가지 문제를 수정한다:
14+
15+
1. **CWD 미존재 시 brew 실패**: 삭제된 디렉토리에서 실행하면 brew가 `getcwd` 에러로 실패
16+
2. **Formula 미동기화**: `brew update` 없이 `brew upgrade`만 실행하여 오래된 formula로 "already installed" 반환
17+
3. **`-v` 미지원**: `chromaport -v`가 에러 발생
18+
19+
(see brainstorm: docs/brainstorms/2026-03-10-update-command-improvements-brainstorm.md)
20+
21+
## Acceptance Criteria
22+
23+
- [x] `brew update``brew upgrade chromaport` 순서로 실행
24+
- [x] `brew update` 실패 시 경고 출력 후 `brew upgrade` 계속 진행 (best-effort)
25+
- [x] `brew update` stdout 억제, stderr는 실패 시에만 출력
26+
- [x] 업그레이드 전 Y/n 확인 프롬프트 표시 (기본값: Yes)
27+
- [x] `Update` 서브커맨드에 `-y/--yes` 플래그 추가하여 프롬프트 스킵
28+
- [x] Non-TTY 환경에서 `--yes` 없이 실행 시 명령어 안내만 출력 후 종료
29+
- [x] CWD 미존재 시 brew 실행 전에 감지하여 친절한 에러 메시지 출력
30+
- [x] `chromaport -v``chromaport --version`과 동일하게 동작
31+
- [x] Cargo install 경로에도 확인 프롬프트 적용
32+
- [x] `Unknown` 경로는 기존대로 수동 안내만 (프롬프트 없음)
33+
- [x] 기존 테스트 통과 + 새 테스트 추가
34+
35+
## MVP
36+
37+
### 1. `src/cli.rs``-v` 플래그 + Update 서브커맨드 확장
38+
39+
```rust
40+
#[derive(Parser)]
41+
#[command(
42+
version,
43+
about = "Migrate VS Code / Cursor themes to Superset, Warp, Ghostty, and more",
44+
long_about = None
45+
)]
46+
pub struct Cli {
47+
#[command(subcommand)]
48+
pub command: Option<Command>,
49+
50+
#[arg(short, long, value_enum)]
51+
pub editor: Option<Editor>,
52+
53+
#[arg(short, long, value_enum)]
54+
pub target: Option<Target>,
55+
56+
#[arg(short = 'y', long)]
57+
pub yes: bool,
58+
59+
#[arg(long)]
60+
pub activate: bool,
61+
62+
#[arg(long, hide = true)]
63+
pub no_activate: bool,
64+
}
65+
66+
#[derive(Subcommand)]
67+
pub enum Command {
68+
/// Check for updates and upgrade chromaport
69+
Update {
70+
/// Skip confirmation prompt
71+
#[arg(short = 'y', long)]
72+
yes: bool,
73+
},
74+
}
75+
```
76+
77+
`-v` 플래그: clap 4에서 `--version`의 short flag를 `-v`로 변경하려면 기본 `-V` 대신 커스텀 arg를 사용해야 할 수 있음. 구현 시 clap 문서 확인 필요. 우선순위가 낮으므로 별도 커밋으로 분리.
78+
79+
### 2. `src/interactive.rs``confirm_update()` 추가
80+
81+
```rust
82+
/// 업데이트 실행 전 사용자 확인
83+
pub fn confirm_update(current: &str, latest: &str, method: &str) -> Result<bool> {
84+
let message = format!(
85+
"Upgrade chromaport {} → {} via {}?",
86+
current, latest, method
87+
);
88+
match inquire::Confirm::new(&message)
89+
.with_default(true) // Y/n — 사용자가 명시적으로 update를 실행했으므로 기본 Yes
90+
.prompt()
91+
{
92+
Ok(answer) => Ok(answer),
93+
Err(e) => handle_inquire_error(e),
94+
}
95+
}
96+
```
97+
98+
### 3. `src/update.rs``run_update()` 개선
99+
100+
```rust
101+
pub fn run_update(yes: bool) -> Result<()> {
102+
println!("Checking for updates...");
103+
104+
let latest_str = fetch_latest_version()?;
105+
let current = current_version();
106+
107+
let cur = Version::parse(current).context("invalid current version")?;
108+
let latest = Version::parse(&latest_str).context("invalid latest version")?;
109+
110+
if latest <= cur {
111+
println!("chromaport is already up to date (v{current}).");
112+
return Ok(());
113+
}
114+
115+
// Update cache
116+
let _ = write_cache(&UpdateCache {
117+
last_checked_at: now_iso8601(),
118+
latest_version: latest_str.clone(),
119+
});
120+
121+
match detect_install_method() {
122+
InstallMethod::Homebrew => {
123+
println!("A new version is available: {current} → {latest_str}");
124+
125+
// CWD 존재 여부 체크
126+
if std::env::current_dir().is_err() {
127+
eprintln!("Error: 현재 디렉토리가 존재하지 않습니다.");
128+
eprintln!("유효한 디렉토리로 이동한 후 다시 시도해주세요:");
129+
eprintln!(" cd ~ && chromaport update");
130+
std::process::exit(1);
131+
}
132+
133+
// 확인 프롬프트
134+
if !yes {
135+
if atty::isnt(atty::Stream::Stdin) {
136+
// Non-TTY: 안내만 출력
137+
println!("\nRun the following command to upgrade:");
138+
println!(" brew update && brew upgrade chromaport");
139+
return Ok(());
140+
}
141+
if !interactive::confirm_update(current, &latest_str, "Homebrew")? {
142+
return Ok(());
143+
}
144+
}
145+
146+
// brew update (best-effort)
147+
let brew_update = std::process::Command::new("brew")
148+
.arg("update")
149+
.stdout(std::process::Stdio::null())
150+
.stderr(std::process::Stdio::piped())
151+
.status();
152+
153+
match brew_update {
154+
Ok(status) if !status.success() => {
155+
eprintln!("Warning: `brew update` failed. Proceeding with upgrade...");
156+
}
157+
Err(e) => {
158+
eprintln!("Warning: `brew update` failed ({e}). Proceeding with upgrade...");
159+
}
160+
_ => {}
161+
}
162+
163+
// brew upgrade
164+
println!("Upgrading chromaport via Homebrew...");
165+
let status = std::process::Command::new("brew")
166+
.args(["upgrade", "chromaport"])
167+
.status()
168+
.context("failed to run `brew upgrade chromaport`")?;
169+
170+
if status.success() {
171+
println!("✔ Updated successfully!");
172+
} else {
173+
anyhow::bail!(
174+
"`brew upgrade chromaport` failed (exit code {})",
175+
status.code().unwrap_or(1)
176+
);
177+
}
178+
}
179+
InstallMethod::Cargo => {
180+
println!("A new version is available: {current} → {latest_str}");
181+
182+
// 확인 프롬프트 (Homebrew와 동일한 패턴)
183+
if !yes {
184+
if atty::isnt(atty::Stream::Stdin) {
185+
println!("\nRun the following command to upgrade:");
186+
println!(" cargo install chromaport");
187+
return Ok(());
188+
}
189+
if !interactive::confirm_update(current, &latest_str, "Cargo")? {
190+
return Ok(());
191+
}
192+
}
193+
194+
println!("Upgrading chromaport via Cargo...");
195+
let status = std::process::Command::new("cargo")
196+
.args(["install", "chromaport"])
197+
.status()
198+
.context("failed to run `cargo install chromaport`")?;
199+
200+
if status.success() {
201+
println!("✔ Updated successfully!");
202+
} else {
203+
anyhow::bail!(
204+
"`cargo install chromaport` failed (exit code {})",
205+
status.code().unwrap_or(1)
206+
);
207+
}
208+
}
209+
InstallMethod::Unknown => {
210+
// 기존 동작 유지 — 수동 안내만, 프롬프트 없음
211+
println!("A new release is available: {current} → {latest_str}\n");
212+
println!("Could not detect install method. Update manually:");
213+
println!(" brew update && brew upgrade chromaport");
214+
println!(" # or");
215+
println!(" cargo install chromaport");
216+
println!("\nhttps://github.com/hamsurang/chromaport/releases/tag/v{latest_str}");
217+
}
218+
}
219+
220+
Ok(())
221+
}
222+
```
223+
224+
### 4. `src/main.rs` — 서브커맨드 디스패치 업데이트
225+
226+
```rust
227+
if let Some(Command::Update { yes }) = cli.command {
228+
return update::run_update(yes);
229+
}
230+
```
231+
232+
### 5. `tests/cli.rs` — 테스트 추가
233+
234+
```rust
235+
#[test]
236+
fn update_accepts_yes_flag() {
237+
cmd().args(["update", "--yes"]).assert().success();
238+
// 실제 GitHub API 호출하므로 네트워크 필요
239+
}
240+
241+
#[test]
242+
fn update_accepts_short_yes_flag() {
243+
cmd().args(["update", "-y"]).assert().success();
244+
}
245+
246+
#[test]
247+
fn short_version_flag() {
248+
cmd()
249+
.arg("-v") // 또는 -V, clap 구현에 따라
250+
.assert()
251+
.success()
252+
.stdout(predicates::str::contains("chromaport"));
253+
}
254+
```
255+
256+
### 6. `Cargo.toml` — 의존성 추가 (필요 시)
257+
258+
TTY 감지를 위한 `atty` 크레이트 추가가 필요할 수 있음. 또는 `std::io::IsTerminal` (Rust 1.70+)을 사용하면 외부 의존성 불필요.
259+
260+
```toml
261+
# Rust 1.70+ 라면 std::io::IsTerminal 사용 권장 (의존성 추가 불필요)
262+
# 그렇지 않으면:
263+
# atty = "0.2"
264+
```
265+
266+
## Implementation Order
267+
268+
1. `-v` 플래그 추가 (`src/cli.rs`) — 독립적, 작은 변경
269+
2. `Update` 서브커맨드에 `yes` 필드 추가 + `main.rs` 디스패치 수정
270+
3. `confirm_update()` 추가 (`src/interactive.rs`)
271+
4. `run_update()` 개선 (`src/update.rs`) — CWD 체크, 확인 프롬프트, brew update 추가
272+
5. 테스트 추가 및 검증
273+
274+
## Sources
275+
276+
- **Origin brainstorm:** [docs/brainstorms/2026-03-10-update-command-improvements-brainstorm.md](docs/brainstorms/2026-03-10-update-command-improvements-brainstorm.md)
277+
- Key decisions: `update` 이름 유지, brew update 추가, Y/n 확인, CWD 에러 메시지 개선
278+
- **Prior plan:** [docs/plans/2026-03-09-feat-cli-update-notifier-plan.md](docs/plans/2026-03-09-feat-cli-update-notifier-plan.md)
279+
- **ureq 3.x migration:** [docs/solutions/build-errors/ureq-3x-api-migration.md](docs/solutions/build-errors/ureq-3x-api-migration.md)
280+
- Existing patterns: `src/interactive.rs:57-63` (`confirm_activate()`)

src/cli.rs

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,14 @@ use clap::{Parser, Subcommand, ValueEnum};
44
#[command(
55
version,
66
about = "Migrate VS Code / Cursor themes to Superset, Warp, Ghostty, and more",
7-
long_about = None
7+
long_about = None,
8+
disable_version_flag = true
89
)]
910
pub struct Cli {
11+
/// Print version
12+
#[arg(short = 'v', long = "version", action = clap::ArgAction::Version)]
13+
pub version: (),
14+
1015
#[command(subcommand)]
1116
pub command: Option<Command>,
1217

@@ -35,7 +40,11 @@ pub struct Cli {
3540
#[derive(Subcommand)]
3641
pub enum Command {
3742
/// Check for updates and upgrade chromaport
38-
Update,
43+
Update {
44+
/// Skip confirmation prompt
45+
#[arg(short = 'y', long)]
46+
yes: bool,
47+
},
3948
}
4049

4150
#[derive(Clone, ValueEnum, Debug, PartialEq)]

0 commit comments

Comments
 (0)