Skip to content

Commit 58b5902

Browse files
authored
feat(cli): add --changelog to q version (#1375)
1 parent 59c34f9 commit 58b5902

File tree

3 files changed

+200
-5
lines changed

3 files changed

+200
-5
lines changed

crates/q_cli/src/cli/feed.rs

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
use serde::{
2+
Deserialize,
3+
Serialize,
4+
};
5+
6+
#[derive(Debug, Serialize, Deserialize)]
7+
pub struct Feed {
8+
pub entries: Vec<Entry>,
9+
}
10+
11+
#[derive(Debug, Clone, Serialize, Deserialize)]
12+
pub struct Entry {
13+
#[serde(rename = "type")]
14+
pub entry_type: String,
15+
pub date: String,
16+
pub version: String,
17+
#[serde(default)]
18+
pub hidden: bool,
19+
#[serde(default)]
20+
pub changes: Vec<Change>,
21+
}
22+
23+
#[derive(Debug, Clone, Serialize, Deserialize)]
24+
pub struct Change {
25+
#[serde(rename = "type")]
26+
pub change_type: String,
27+
pub description: String,
28+
}
29+
30+
impl Feed {
31+
pub fn load() -> Self {
32+
serde_json::from_str(include_str!("../../../../feed.json")).expect("feed.json is valid json")
33+
}
34+
35+
pub fn get_version_changelog(&self, version: &str) -> Option<Entry> {
36+
self.entries
37+
.iter()
38+
.find(|entry| entry.entry_type == "release" && entry.version == version && !entry.hidden)
39+
.cloned()
40+
}
41+
42+
pub fn get_all_changelogs(&self) -> Vec<Entry> {
43+
self.entries
44+
.iter()
45+
.filter(|entry| entry.entry_type == "release" && !entry.hidden)
46+
.cloned()
47+
.collect()
48+
}
49+
}

crates/q_cli/src/cli/mod.rs

Lines changed: 104 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ mod completion;
55
mod debug;
66
mod diagnostics;
77
mod doctor;
8+
mod feed;
89
mod hook;
910
mod init;
1011
mod inline;
@@ -43,6 +44,7 @@ use eyre::{
4344
WrapErr,
4445
bail,
4546
};
47+
use feed::Feed;
4648
use fig_auth::is_logged_in;
4749
use fig_ipc::local::open_ui_element;
4850
use fig_log::{
@@ -176,7 +178,12 @@ pub enum CliRootCommands {
176178
Telemetry(telemetry::TelemetrySubcommand),
177179
/// Version
178180
#[command(hide = true)]
179-
Version,
181+
Version {
182+
/// Show the changelog (use --changelog=all for all versions, or --changelog=x.x.x for a
183+
/// specific version)
184+
#[arg(long, num_args = 0..=1, default_missing_value = "")]
185+
changelog: Option<String>,
186+
},
180187
/// Open the dashboard
181188
Dashboard,
182189
/// AI assistant in your terminal
@@ -214,7 +221,7 @@ impl CliRootCommands {
214221
CliRootCommands::Integrations(_) => "integrations",
215222
CliRootCommands::Translate(_) => "translate",
216223
CliRootCommands::Telemetry(_) => "telemetry",
217-
CliRootCommands::Version => "version",
224+
CliRootCommands::Version { .. } => "version",
218225
CliRootCommands::Dashboard => "dashboard",
219226
CliRootCommands::Chat { .. } => "chat",
220227
CliRootCommands::Inline(_) => "inline",
@@ -324,7 +331,7 @@ impl Cli {
324331
CliRootCommands::Integrations(subcommand) => subcommand.execute().await,
325332
CliRootCommands::Translate(args) => args.execute().await,
326333
CliRootCommands::Telemetry(subcommand) => subcommand.execute().await,
327-
CliRootCommands::Version => Self::print_version(),
334+
CliRootCommands::Version { changelog } => Self::print_version(changelog),
328335
CliRootCommands::Dashboard => launch_dashboard(false).await,
329336
CliRootCommands::Chat(args) => q_chat::launch_chat(args).await,
330337
CliRootCommands::Inline(subcommand) => subcommand.execute(&cli_context).await,
@@ -361,9 +368,80 @@ impl Cli {
361368
Ok(ExitCode::SUCCESS)
362369
}
363370

371+
fn print_changelog_entry(entry: &feed::Entry) -> Result<()> {
372+
println!("Version {} ({})", entry.version, entry.date);
373+
374+
if entry.changes.is_empty() {
375+
println!(" No changes recorded for this version.");
376+
} else {
377+
for change in &entry.changes {
378+
let type_label = match change.change_type.as_str() {
379+
"added" => "Added",
380+
"fixed" => "Fixed",
381+
"changed" => "Changed",
382+
other => other,
383+
};
384+
385+
println!(" - {}: {}", type_label, change.description);
386+
}
387+
}
388+
389+
println!();
390+
Ok(())
391+
}
392+
364393
#[allow(clippy::unused_self)]
365-
fn print_version() -> Result<ExitCode> {
366-
let _ = writeln!(stdout(), "{}", Self::command().render_version());
394+
fn print_version(changelog: Option<String>) -> Result<ExitCode> {
395+
// If no changelog is requested, display normal version information
396+
if changelog.is_none() {
397+
let _ = writeln!(stdout(), "{}", Self::command().render_version());
398+
return Ok(ExitCode::SUCCESS);
399+
}
400+
401+
let changelog_value = changelog.unwrap_or_default();
402+
let feed = Feed::load();
403+
404+
// Display changelog for all versions
405+
if changelog_value == "all" {
406+
let entries = feed.get_all_changelogs();
407+
if entries.is_empty() {
408+
println!("No changelog information available.");
409+
} else {
410+
println!("Changelog for all versions:");
411+
for entry in entries {
412+
Self::print_changelog_entry(&entry)?;
413+
}
414+
}
415+
return Ok(ExitCode::SUCCESS);
416+
}
417+
418+
// Display changelog for a specific version (--changelog=x.x.x)
419+
if !changelog_value.is_empty() {
420+
match feed.get_version_changelog(&changelog_value) {
421+
Some(entry) => {
422+
println!("Changelog for version {}:", changelog_value);
423+
Self::print_changelog_entry(&entry)?;
424+
return Ok(ExitCode::SUCCESS);
425+
},
426+
None => {
427+
println!("No changelog information available for version {}.", changelog_value);
428+
return Ok(ExitCode::SUCCESS);
429+
},
430+
}
431+
}
432+
433+
// Display changelog for the current version (--changelog only)
434+
let current_version = env!("CARGO_PKG_VERSION");
435+
match feed.get_version_changelog(current_version) {
436+
Some(entry) => {
437+
println!("Changelog for version {}:", current_version);
438+
Self::print_changelog_entry(&entry)?;
439+
},
440+
None => {
441+
println!("No changelog information available for version {}.", current_version);
442+
},
443+
}
444+
367445
Ok(ExitCode::SUCCESS)
368446
}
369447
}
@@ -575,6 +653,27 @@ mod test {
575653
);
576654
}
577655

656+
#[test]
657+
fn test_version_changelog() {
658+
assert_parse!(["version", "--changelog"], CliRootCommands::Version {
659+
changelog: Some("".to_string()),
660+
});
661+
}
662+
663+
#[test]
664+
fn test_version_changelog_all() {
665+
assert_parse!(["version", "--changelog=all"], CliRootCommands::Version {
666+
changelog: Some("all".to_string()),
667+
});
668+
}
669+
670+
#[test]
671+
fn test_version_changelog_specific() {
672+
assert_parse!(["version", "--changelog=1.8.0"], CliRootCommands::Version {
673+
changelog: Some("1.8.0".to_string()),
674+
});
675+
}
676+
578677
#[test]
579678
fn test_chat_with_context_profile() {
580679
assert_parse!(

crates/q_cli/tests/cli.rs

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,53 @@ fn version_flag_has_status_code_zero() -> Result<()> {
1717
Ok(())
1818
}
1919

20+
#[test]
21+
fn version_changelog_has_status_code_zero() -> Result<()> {
22+
cli()
23+
.arg("version")
24+
.arg("--changelog")
25+
.assert()
26+
.success()
27+
.stdout(predicate::str::contains("Changelog for version"));
28+
Ok(())
29+
}
30+
31+
#[test]
32+
fn version_changelog_all_has_status_code_zero() -> Result<()> {
33+
cli()
34+
.arg("version")
35+
.arg("--changelog=all")
36+
.assert()
37+
.success()
38+
.stdout(predicate::str::contains("Changelog for all versions"));
39+
Ok(())
40+
}
41+
42+
#[test]
43+
fn version_changelog_specific_has_status_code_zero() -> Result<()> {
44+
cli()
45+
.arg("version")
46+
.arg("--changelog=1.8.0")
47+
.assert()
48+
.success()
49+
.stdout(predicate::str::contains("Changelog for version 1.8.0"));
50+
Ok(())
51+
}
52+
53+
#[test]
54+
fn version_changelog_nonexistent_version() -> Result<()> {
55+
// Test with a version that's unlikely to exist
56+
cli()
57+
.arg("version")
58+
.arg("--changelog=999.999.999")
59+
.assert()
60+
.success()
61+
.stdout(predicate::str::contains(
62+
"No changelog information available for version 999.999.999",
63+
));
64+
Ok(())
65+
}
66+
2067
#[test]
2168
fn help_flag_has_status_code_zero() -> Result<()> {
2269
cli().arg("--help").assert().success();

0 commit comments

Comments
 (0)