feat: complete structured CLI output

- unify human, plain, and JSON CLI output modes

- standardize diagnostics, lifecycle events, colors, quiet, and verbose handling

- preserve argument forwarding and improve CLI help and summaries

- document and release version 0.9.5
This commit is contained in:
2026-10-21 18:28:31 +08:00
parent bfd2b9d251
commit 0f456b4201
63 changed files with 2619 additions and 1317 deletions

View File

@@ -15,8 +15,8 @@
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
use crate::utils::log::{error, warn};
use crate::utils::text::{BOLD_CYAN, BOLD_GREEN, printc};
use crate::prelude::*;
use serde::Deserialize;
use std::fs;
use std::io::Read;
@@ -55,32 +55,32 @@ struct ReleaseAsset {
/// `0` on success / already up to date / pacman-managed refuse; `1` on error.
pub fn flag_update(args: &[String]) -> i32 {
if args.first().is_some_and(|a| a == "--help") {
printc("USAGE:", BOLD_GREEN);
printc(" dcr --update", BOLD_CYAN);
println!();
printc("DESCRIPTION:", BOLD_GREEN);
println!(" Updates DCR to the latest version from GitHub Releases.");
let mut help = crate::utils::help::HelpBuilder::new("dcr --update");
help.section("USAGE").example("dcr --update");
help.description("Updates DCR to the latest version from GitHub Releases.");
help.render();
return 0;
}
if !args.is_empty() {
warn("Command does not support additional arguments");
warn!("Command does not support additional arguments");
return 1;
}
let current_exe = match std::env::current_exe() {
Ok(path) => path,
Err(_) => {
error("Failed to resolve current binary path");
error!("Failed to resolve current binary path");
return 1;
}
};
// Refuse self-update when the binary is owned by pacman/AUR.
if let Some(package_name) = pacman_owned_package(&current_exe) {
warn("This dcr binary is managed by pacman/AUR");
println!(
"Update via package manager: yay/paru -Syu {package_name} or sudo pacman -Syu {package_name}"
warn!("This dcr binary is managed by pacman/AUR");
crate::utils::output::status(
"Update via package manager:",
format!("yay/paru -Syu {package_name} or sudo pacman -Syu {package_name}"),
);
return 0;
}
@@ -91,7 +91,7 @@ pub fn flag_update(args: &[String]) -> i32 {
let release = match fetch_latest_release() {
Ok(release) => release,
Err(err) => {
error(&format!("Failed to check for updates: {err}"));
error!("Failed to check for updates: {err}");
return 1;
}
};
@@ -99,7 +99,7 @@ pub fn flag_update(args: &[String]) -> i32 {
// Normalize the tag by stripping a leading `v` before comparison.
let latest_version = release.tag_name.trim_start_matches('v');
if latest_version == current_version {
println!("Latest version is already installed: {current_version}");
crate::utils::output::status("Up to date", current_version);
return 0;
}
@@ -109,14 +109,14 @@ pub fn flag_update(args: &[String]) -> i32 {
.iter()
.find(|asset| candidate_names.iter().any(|name| name == &asset.name))
else {
error(&format!("Binary for target {target} not found"));
error!("Binary for target {target} not found");
return 1;
};
let bytes = match download_asset(&asset.browser_download_url) {
Ok(bytes) => bytes,
Err(err) => {
error(&format!("Failed to download update: {err}"));
error!("Failed to download update: {err}");
return 1;
}
};
@@ -124,7 +124,7 @@ pub fn flag_update(args: &[String]) -> i32 {
let temp_path = temp_binary_path(&current_exe);
if fs::write(&temp_path, &bytes).is_err() {
error("Failed to write temporary binary");
error!("Failed to write temporary binary");
return 1;
}
set_executable_permissions(&temp_path);
@@ -132,12 +132,14 @@ pub fn flag_update(args: &[String]) -> i32 {
// Replace the running executable in place, then clean up the temp file.
if self_replace::self_replace(&temp_path).is_err() {
let _ = fs::remove_file(&temp_path);
error("Failed to replace current binary");
error!("Failed to replace current binary");
return 1;
}
let _ = fs::remove_file(&temp_path);
println!("Update completed: {current_version} -> {latest_version}");
crate::utils::output::success(format!(
"Updated DCR: {current_version} -> {latest_version}"
));
0
}