Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ arrow-array = "58.3.0"
arrow-schema = "58.3.0"
async-channel = "2.2"
async-trait = "^0.1"
axoupdater = "0.9"
axum = "0.8"
mockall = "0.14.0"
bytes = "1.11.1"
Expand Down
1 change: 1 addition & 0 deletions rust/crates/sift_cli/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ path = "src/main.rs"

[dependencies]
anyhow = { workspace = true }
axoupdater = { workspace = true }
arrow-array = { workspace = true }
arrow-schema = { workspace = true }
chrono = { workspace = true }
Expand Down
22 changes: 22 additions & 0 deletions rust/crates/sift_cli/src/cli/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,28 @@ pub enum Cmd {

/// Ping the Sift API to verify credentials and connectivity
Ping,

/// Update sift-cli to the latest release (or a pinned version)
Update(UpdateArgs),
}

#[derive(clap::Args)]
pub struct UpdateArgs {
/// Pin to a specific version (e.g. 0.3.0). Defaults to the latest release.
#[arg(long)]
pub version: Option<String>,

/// Include prereleases when picking the latest version
#[arg(long)]
pub pre: bool,

/// Report what would happen without applying the update
#[arg(long)]
pub check: bool,

/// Skip the confirmation prompt
#[arg(short = 'y', long)]
pub yes: bool,
}

/// Serve the bundled Sift CLI user documentation over HTTP.
Expand Down
1 change: 1 addition & 0 deletions rust/crates/sift_cli/src/cmd/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ pub mod import;
pub mod install;
pub mod mcp;
pub mod ping;
pub mod update;

pub struct Context {
pub grpc_uri: String,
Expand Down
141 changes: 141 additions & 0 deletions rust/crates/sift_cli/src/cmd/update.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
use std::{
io::{self, Write},
process::ExitCode,
};

use anyhow::{Context as _, Result};
use axoupdater::{AxoUpdater, UpdateRequest, Version};
use crossterm::style::Stylize;

use crate::cli::UpdateArgs;
use crate::cmd::config::get_config_file_path;
use crate::util::tty::Output;

pub async fn run(args: UpdateArgs) -> Result<ExitCode> {
let UpdateArgs {
version,
pre,
check,
yes,
} = args;

let mut updater = AxoUpdater::new_for("sift_cli");

if updater.load_receipt().is_err() {
Output::new()
.line("sift-cli wasn't installed by the official installer.".to_string())
.tip(
"re-run the curl installer to update: \
https://github.com/sift-stack/sift/releases/latest",
)
.eprint();
return Ok(ExitCode::FAILURE);
}

updater.disable_installer_output();

let current = Version::parse(env!("CARGO_PKG_VERSION"))
.context("invalid CARGO_PKG_VERSION baked into binary")?;
let current_is_prerelease = !current.pre.is_empty();

let specifier = match version.as_deref() {
Some(v) => {
v.parse::<Version>()
.context("--version must be a valid semver string like 0.3.0")?;
UpdateRequest::SpecificVersion(v.to_string())
}
None if pre || current_is_prerelease => UpdateRequest::LatestMaybePrerelease,
None => UpdateRequest::Latest,
};
updater.configure_version_specifier(specifier);

let target = updater
.query_new_version()
.await
.context("failed to query latest release from GitHub")?
.cloned();

let Some(target) = target else {
Output::new()
.line(format!(
"sift-cli {} is already the latest release.",
current.to_string().bold(),
))
.print();
return Ok(ExitCode::SUCCESS);
};

if target == current {
Output::new()
.line(format!(
"sift-cli {} is already at the requested version.",
current.to_string().bold(),
))
.print();
return Ok(ExitCode::SUCCESS);
}

if target < current && version.is_none() {
Output::new()
.line(format!(
"Refusing to downgrade: latest release ({}) is older than installed ({}).",
target.to_string().yellow(),
current.to_string().bold(),
))
.tip("pass `--version <X>` to pin a specific version, or `--pre` to include prereleases")
.eprint();
return Ok(ExitCode::FAILURE);
}

let arrow = if target < current { "↓" } else { "→" };
println!(
"Update: {} {arrow} {}",
current.to_string().yellow(),
target.to_string().green(),
);

if check {
println!("({} passed — no changes applied)", "--check".bold());
return Ok(ExitCode::SUCCESS);
}

if !yes && !confirm()? {
println!("Update aborted.");
return Ok(ExitCode::SUCCESS);
}

let result = updater
.run()
.await
.context("update failed")?;

let mut out = Output::new();
match result {
Some(outcome) => {
out.line(format!(
"Updated sift-cli to {}",
outcome.new_version.to_string().green(),
));
}
None => {
out.line("Update completed.".to_string());
}
}
if let Ok(path) = get_config_file_path() {
out.line(format!("Your config at {} is unchanged.", path.display()));
}
out.print();

Ok(ExitCode::SUCCESS)
}

fn confirm() -> Result<bool> {
print!("Proceed? [y/N] ");
io::stdout().flush()?;
let mut buf = String::new();
io::stdin().read_line(&mut buf)?;
Ok(matches!(
buf.trim().to_lowercase().as_str(),
"y" | "yes"
))
}
1 change: 1 addition & 0 deletions rust/crates/sift_cli/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ fn run(clargs: cli::Args) -> Result<ExitCode> {
ConfigCmd::Update(args) => return cmd::config::update(clargs.profile, args),
},
Cmd::Doc(args) => return run_future(cmd::doc::serve(args)),
Cmd::Update(args) => return run_future(cmd::update::run(args)),
Cmd::Install(cmd) => match cmd {
InstallCmd::Completions(cmd) => match cmd {
cli::CompletionsCmd::Print(args) => return cmd::install::completions::print(args),
Expand Down
Loading