mirror of
https://github.com/tauri-apps/tauri.git
synced 2024-12-15 13:41:39 +03:00
feat(cli): colorful cli (#3635)
Co-authored-by: Lucas Nogueira <lucas@tauri.studio>
This commit is contained in:
parent
bcd43168a5
commit
49d2f13fc0
6
.changes/colorful-cli.md
Normal file
6
.changes/colorful-cli.md
Normal file
@ -0,0 +1,6 @@
|
||||
---
|
||||
"cli.rs": "patch"
|
||||
"cli.js": "patch"
|
||||
---
|
||||
|
||||
Improve readability of the `info` subcommand output.
|
@ -18,11 +18,7 @@ use std::{fs::File, io::prelude::*};
|
||||
use zip::write::FileOptions;
|
||||
|
||||
use crate::{bundle::Bundle, Settings};
|
||||
use std::{
|
||||
ffi::OsStr,
|
||||
fs::{self},
|
||||
io::Write,
|
||||
};
|
||||
use std::{fs, io::Write};
|
||||
|
||||
use anyhow::Context;
|
||||
use std::path::{Path, PathBuf};
|
||||
@ -43,6 +39,8 @@ pub fn bundle_project(settings: &Settings, bundles: &[Bundle]) -> crate::Result<
|
||||
// This is the Mac OS App packaged
|
||||
#[cfg(target_os = "macos")]
|
||||
fn bundle_update(settings: &Settings, bundles: &[Bundle]) -> crate::Result<Vec<PathBuf>> {
|
||||
use std::ffi::OsStr;
|
||||
|
||||
// find our .app or rebuild our bundle
|
||||
let bundle_path = match bundles
|
||||
.iter()
|
||||
@ -83,6 +81,8 @@ fn bundle_update(settings: &Settings, bundles: &[Bundle]) -> crate::Result<Vec<P
|
||||
// No assets are replaced
|
||||
#[cfg(target_os = "linux")]
|
||||
fn bundle_update(settings: &Settings, bundles: &[Bundle]) -> crate::Result<Vec<PathBuf>> {
|
||||
use std::ffi::OsStr;
|
||||
|
||||
// build our app actually we support only appimage on linux
|
||||
let bundle_path = match bundles
|
||||
.iter()
|
||||
|
@ -7,6 +7,7 @@ use crate::helpers::{
|
||||
};
|
||||
use crate::Result;
|
||||
use clap::Parser;
|
||||
use colored::Colorize;
|
||||
use serde::Deserialize;
|
||||
|
||||
use std::{
|
||||
@ -311,96 +312,6 @@ fn active_rust_toolchain() -> crate::Result<Option<String>> {
|
||||
Ok(toolchain)
|
||||
}
|
||||
|
||||
struct InfoBlock {
|
||||
section: bool,
|
||||
key: &'static str,
|
||||
value: Option<String>,
|
||||
suffix: Option<String>,
|
||||
}
|
||||
|
||||
impl InfoBlock {
|
||||
fn new(key: &'static str) -> Self {
|
||||
Self {
|
||||
section: false,
|
||||
key,
|
||||
value: None,
|
||||
suffix: None,
|
||||
}
|
||||
}
|
||||
|
||||
fn section(mut self) -> Self {
|
||||
self.section = true;
|
||||
self
|
||||
}
|
||||
|
||||
fn value<V: Into<Option<String>>>(mut self, value: V) -> Self {
|
||||
self.value = value.into();
|
||||
self
|
||||
}
|
||||
|
||||
fn suffix<S: Into<Option<String>>>(mut self, suffix: S) -> Self {
|
||||
self.suffix = suffix.into();
|
||||
self
|
||||
}
|
||||
|
||||
fn display(&self) {
|
||||
if self.section {
|
||||
println!();
|
||||
}
|
||||
print!("{}", self.key);
|
||||
if let Some(value) = &self.value {
|
||||
print!(" - {}", value);
|
||||
}
|
||||
if let Some(suffix) = &self.suffix {
|
||||
print!("{}", suffix);
|
||||
}
|
||||
println!();
|
||||
}
|
||||
}
|
||||
|
||||
struct VersionBlock {
|
||||
section: bool,
|
||||
key: &'static str,
|
||||
version: Option<String>,
|
||||
target_version: Option<String>,
|
||||
}
|
||||
|
||||
impl VersionBlock {
|
||||
fn new<V: Into<Option<String>>>(key: &'static str, version: V) -> Self {
|
||||
Self {
|
||||
section: false,
|
||||
key,
|
||||
version: version.into(),
|
||||
target_version: None,
|
||||
}
|
||||
}
|
||||
|
||||
fn target_version<V: Into<Option<String>>>(mut self, version: V) -> Self {
|
||||
self.target_version = version.into();
|
||||
self
|
||||
}
|
||||
|
||||
fn display(&self) {
|
||||
if self.section {
|
||||
println!();
|
||||
}
|
||||
print!("{}", self.key);
|
||||
if let Some(version) = &self.version {
|
||||
print!(" - {}", version);
|
||||
} else {
|
||||
print!(" - Not installed");
|
||||
}
|
||||
if let (Some(version), Some(target_version)) = (&self.version, &self.target_version) {
|
||||
let version = semver::Version::parse(version).unwrap();
|
||||
let target_version = semver::Version::parse(target_version).unwrap();
|
||||
if version < target_version {
|
||||
print!(" (outdated, latest: {})", target_version);
|
||||
}
|
||||
}
|
||||
println!();
|
||||
}
|
||||
}
|
||||
|
||||
fn crate_version(
|
||||
tauri_dir: &Path,
|
||||
manifest: Option<&CargoManifest>,
|
||||
@ -529,23 +440,120 @@ fn crate_version(
|
||||
(crate_version_string, suffix)
|
||||
}
|
||||
|
||||
fn indent(spaces: usize) {
|
||||
print!(
|
||||
"{}",
|
||||
vec![0; spaces].iter().map(|_| " ").collect::<String>()
|
||||
);
|
||||
}
|
||||
|
||||
struct Section(&'static str);
|
||||
impl Section {
|
||||
fn display(&self) {
|
||||
println!();
|
||||
println!("{}", self.0.yellow().bold());
|
||||
}
|
||||
}
|
||||
|
||||
struct VersionBlock {
|
||||
name: String,
|
||||
version: String,
|
||||
target_version: String,
|
||||
indentation: usize,
|
||||
skip_update: bool,
|
||||
}
|
||||
|
||||
impl VersionBlock {
|
||||
fn new(name: impl Into<String>, version: impl Into<String>) -> Self {
|
||||
Self {
|
||||
name: name.into(),
|
||||
version: version.into(),
|
||||
target_version: "".into(),
|
||||
indentation: 2,
|
||||
skip_update: false,
|
||||
}
|
||||
}
|
||||
|
||||
fn skip_update(mut self) -> Self {
|
||||
self.skip_update = true;
|
||||
self
|
||||
}
|
||||
|
||||
fn target_version(mut self, version: impl Into<String>) -> Self {
|
||||
self.target_version = version.into();
|
||||
self
|
||||
}
|
||||
|
||||
fn display(&self) {
|
||||
indent(self.indentation);
|
||||
print!("{} ", "›".cyan());
|
||||
print!("{}", self.name.bold());
|
||||
print!(": ");
|
||||
print!(
|
||||
"{}",
|
||||
if self.version.is_empty() {
|
||||
"Not installed!".red().to_string()
|
||||
} else {
|
||||
self.version.clone()
|
||||
}
|
||||
);
|
||||
if !self.target_version.is_empty() && !self.skip_update {
|
||||
print!(
|
||||
"({}, latest: {})",
|
||||
"outdated".red(),
|
||||
self.target_version.green()
|
||||
);
|
||||
}
|
||||
println!();
|
||||
}
|
||||
}
|
||||
|
||||
struct InfoBlock {
|
||||
key: String,
|
||||
value: String,
|
||||
indentation: usize,
|
||||
}
|
||||
|
||||
impl InfoBlock {
|
||||
fn new(key: impl Into<String>, val: impl Into<String>) -> Self {
|
||||
Self {
|
||||
key: key.into(),
|
||||
value: val.into(),
|
||||
indentation: 2,
|
||||
}
|
||||
}
|
||||
|
||||
fn display(&self) {
|
||||
indent(self.indentation);
|
||||
print!("{} ", "›".cyan());
|
||||
print!("{}", self.key.bold());
|
||||
print!(": ");
|
||||
print!("{}", self.value.clone());
|
||||
println!();
|
||||
}
|
||||
}
|
||||
|
||||
pub fn command(_options: Options) -> Result<()> {
|
||||
Section("Environment").display();
|
||||
|
||||
let os_info = os_info::get();
|
||||
InfoBlock {
|
||||
section: true,
|
||||
key: "Operating System",
|
||||
value: Some(format!(
|
||||
"{}, version {} {:?}",
|
||||
VersionBlock::new(
|
||||
"OS",
|
||||
format!(
|
||||
"{} {} {:?}",
|
||||
os_info.os_type(),
|
||||
os_info.version(),
|
||||
os_info.bitness()
|
||||
)),
|
||||
suffix: None,
|
||||
}
|
||||
),
|
||||
)
|
||||
.display();
|
||||
|
||||
#[cfg(windows)]
|
||||
VersionBlock::new("Webview2", webview2_version().unwrap_or_default()).display();
|
||||
VersionBlock::new(
|
||||
"Webview2",
|
||||
webview2_version().unwrap_or_default().unwrap_or_default(),
|
||||
)
|
||||
.display();
|
||||
|
||||
#[cfg(windows)]
|
||||
{
|
||||
@ -554,24 +562,12 @@ pub fn command(_options: Options) -> Result<()> {
|
||||
.unwrap_or_default();
|
||||
|
||||
if build_tools.is_empty() {
|
||||
InfoBlock {
|
||||
section: false,
|
||||
key: "Visual Studio Build Tools - Not installed",
|
||||
value: None,
|
||||
suffix: None,
|
||||
}
|
||||
.display();
|
||||
InfoBlock::new("MSVC", "").display();
|
||||
} else {
|
||||
InfoBlock {
|
||||
section: false,
|
||||
key: "Visual Studio Build Tools:",
|
||||
value: None,
|
||||
suffix: None,
|
||||
}
|
||||
.display();
|
||||
|
||||
InfoBlock::new("MSVC", "").display();
|
||||
for i in build_tools {
|
||||
VersionBlock::new(" ", i).display();
|
||||
indent(6);
|
||||
println!("{}", format!("{} {}", "-".cyan(), i));
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -585,6 +581,87 @@ pub fn command(_options: Options) -> Result<()> {
|
||||
.unwrap_or_default();
|
||||
panic::set_hook(hook);
|
||||
|
||||
let metadata = serde_json::from_str::<VersionMetadata>(include_str!("../metadata.json"))?;
|
||||
VersionBlock::new(
|
||||
"Node.js",
|
||||
get_version("node", &[])
|
||||
.unwrap_or_default()
|
||||
.unwrap_or_default()
|
||||
.chars()
|
||||
.skip(1)
|
||||
.collect::<String>(),
|
||||
)
|
||||
.target_version(metadata.js_cli.node.replace(">= ", ""))
|
||||
.skip_update()
|
||||
.display();
|
||||
|
||||
VersionBlock::new(
|
||||
"npm",
|
||||
get_version("npm", &[])
|
||||
.unwrap_or_default()
|
||||
.unwrap_or_default(),
|
||||
)
|
||||
.display();
|
||||
VersionBlock::new(
|
||||
"pnpm",
|
||||
get_version("pnpm", &[])
|
||||
.unwrap_or_default()
|
||||
.unwrap_or_default(),
|
||||
)
|
||||
.display();
|
||||
VersionBlock::new(
|
||||
"yarn",
|
||||
get_version("yarn", &[])
|
||||
.unwrap_or_default()
|
||||
.unwrap_or_default(),
|
||||
)
|
||||
.display();
|
||||
VersionBlock::new(
|
||||
"rustup",
|
||||
get_version("rustup", &[])
|
||||
.unwrap_or_default()
|
||||
.map(|v| {
|
||||
let mut s = v.split(' ');
|
||||
s.next();
|
||||
s.next().unwrap().to_string()
|
||||
})
|
||||
.unwrap_or_default(),
|
||||
)
|
||||
.display();
|
||||
VersionBlock::new(
|
||||
"rustc",
|
||||
get_version("rustc", &[])
|
||||
.unwrap_or_default()
|
||||
.map(|v| {
|
||||
let mut s = v.split(' ');
|
||||
s.next();
|
||||
s.next().unwrap().to_string()
|
||||
})
|
||||
.unwrap_or_default(),
|
||||
)
|
||||
.display();
|
||||
VersionBlock::new(
|
||||
"cargo",
|
||||
get_version("cargo", &[])
|
||||
.unwrap_or_default()
|
||||
.map(|v| {
|
||||
let mut s = v.split(' ');
|
||||
s.next();
|
||||
s.next().unwrap().to_string()
|
||||
})
|
||||
.unwrap_or_default(),
|
||||
)
|
||||
.display();
|
||||
InfoBlock::new(
|
||||
"Rust toolchain",
|
||||
active_rust_toolchain()
|
||||
.unwrap_or_default()
|
||||
.unwrap_or_default(),
|
||||
)
|
||||
.display();
|
||||
|
||||
Section("Packages").display();
|
||||
|
||||
let mut package_manager = PackageManager::Npm;
|
||||
if let Some(app_dir) = &app_dir {
|
||||
let file_names = read_dir(app_dir)
|
||||
@ -601,76 +678,29 @@ pub fn command(_options: Options) -> Result<()> {
|
||||
.collect::<Vec<String>>();
|
||||
package_manager = get_package_manager(&file_names)?;
|
||||
}
|
||||
|
||||
if let Some(node_version) = get_version("node", &[]).unwrap_or_default() {
|
||||
InfoBlock::new("Node.js environment").section().display();
|
||||
let metadata = serde_json::from_str::<VersionMetadata>(include_str!("../metadata.json"))?;
|
||||
VersionBlock::new(
|
||||
format!("{} {}", "@tauri-apps/cli", "[NPM]".dimmed()),
|
||||
metadata.js_cli.version,
|
||||
)
|
||||
.target_version(
|
||||
npm_latest_version(&package_manager, "@tauri-apps/cli")
|
||||
.unwrap_or_default()
|
||||
.unwrap_or_default(),
|
||||
)
|
||||
.display();
|
||||
if let Some(app_dir) = &app_dir {
|
||||
VersionBlock::new(
|
||||
" Node.js",
|
||||
node_version.chars().skip(1).collect::<String>(),
|
||||
format!("{} {}", "@tauri-apps/api", "[NPM]".dimmed()),
|
||||
npm_package_version(&package_manager, "@tauri-apps/api", app_dir)
|
||||
.unwrap_or_default()
|
||||
.unwrap_or_default(),
|
||||
)
|
||||
.target_version(
|
||||
npm_latest_version(&package_manager, "@tauri-apps/api")
|
||||
.unwrap_or_default()
|
||||
.unwrap_or_default(),
|
||||
)
|
||||
.target_version(metadata.js_cli.node.replace(">= ", ""))
|
||||
.display();
|
||||
|
||||
VersionBlock::new(" @tauri-apps/cli", metadata.js_cli.version)
|
||||
.target_version(npm_latest_version(&package_manager, "@tauri-apps/cli").unwrap_or_default())
|
||||
.display();
|
||||
if let Some(app_dir) = &app_dir {
|
||||
VersionBlock::new(
|
||||
" @tauri-apps/api",
|
||||
npm_package_version(&package_manager, "@tauri-apps/api", app_dir).unwrap_or_default(),
|
||||
)
|
||||
.target_version(npm_latest_version(&package_manager, "@tauri-apps/api").unwrap_or_default())
|
||||
.display();
|
||||
}
|
||||
|
||||
InfoBlock::new("Global packages").section().display();
|
||||
|
||||
VersionBlock::new(" npm", get_version("npm", &[]).unwrap_or_default()).display();
|
||||
VersionBlock::new(" pnpm", get_version("pnpm", &[]).unwrap_or_default()).display();
|
||||
VersionBlock::new(" yarn", get_version("yarn", &[]).unwrap_or_default()).display();
|
||||
}
|
||||
|
||||
InfoBlock::new("Rust environment").section().display();
|
||||
VersionBlock::new(
|
||||
" rustup",
|
||||
get_version("rustup", &[]).unwrap_or_default().map(|v| {
|
||||
let mut s = v.split(' ');
|
||||
s.next();
|
||||
s.next().unwrap().to_string()
|
||||
}),
|
||||
)
|
||||
.display();
|
||||
VersionBlock::new(
|
||||
" rustc",
|
||||
get_version("rustc", &[]).unwrap_or_default().map(|v| {
|
||||
let mut s = v.split(' ');
|
||||
s.next();
|
||||
s.next().unwrap().to_string()
|
||||
}),
|
||||
)
|
||||
.display();
|
||||
VersionBlock::new(
|
||||
" cargo",
|
||||
get_version("cargo", &[]).unwrap_or_default().map(|v| {
|
||||
let mut s = v.split(' ');
|
||||
s.next();
|
||||
s.next().unwrap().to_string()
|
||||
}),
|
||||
)
|
||||
.display();
|
||||
VersionBlock::new(" toolchain", active_rust_toolchain().unwrap_or_default()).display();
|
||||
|
||||
if let Some(app_dir) = app_dir {
|
||||
InfoBlock::new("App directory structure")
|
||||
.section()
|
||||
.display();
|
||||
for entry in read_dir(app_dir)? {
|
||||
let entry = entry?;
|
||||
if entry.path().is_dir() {
|
||||
println!("/{}", entry.path().file_name().unwrap().to_string_lossy());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let hook = panic::take_hook();
|
||||
@ -683,9 +713,7 @@ pub fn command(_options: Options) -> Result<()> {
|
||||
panic::set_hook(hook);
|
||||
|
||||
if tauri_dir.is_some() || app_dir.is_some() {
|
||||
InfoBlock::new("App").section().display();
|
||||
|
||||
if let Some(tauri_dir) = tauri_dir {
|
||||
if let Some(tauri_dir) = tauri_dir.clone() {
|
||||
let manifest: Option<CargoManifest> =
|
||||
if let Ok(manifest_contents) = read_to_string(tauri_dir.join("Cargo.toml")) {
|
||||
toml::from_str(&manifest_contents).ok()
|
||||
@ -700,46 +728,57 @@ pub fn command(_options: Options) -> Result<()> {
|
||||
};
|
||||
|
||||
for (dep, label) in [
|
||||
("tauri", " tauri"),
|
||||
("tauri-build", " tauri-build"),
|
||||
("tao", " tao"),
|
||||
("wry", " wry"),
|
||||
("tauri", format!("{} {}", "tauri", "[RUST]".dimmed())),
|
||||
(
|
||||
"tauri-build",
|
||||
format!("{} {}", "tauri-build", "[RUST]".dimmed()),
|
||||
),
|
||||
("tao", format!("{} {}", "tao", "[RUST]".dimmed())),
|
||||
("wry", format!("{} {}", "wry", "[RUST]".dimmed())),
|
||||
] {
|
||||
let (version_string, version_suffix) =
|
||||
crate_version(&tauri_dir, manifest.as_ref(), lock.as_ref(), dep);
|
||||
InfoBlock::new(label)
|
||||
.value(version_string)
|
||||
.suffix(version_suffix)
|
||||
.display();
|
||||
VersionBlock::new(
|
||||
label,
|
||||
format!(
|
||||
"{},{}",
|
||||
version_string,
|
||||
version_suffix.unwrap_or_else(|| "".into())
|
||||
),
|
||||
)
|
||||
.display();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if tauri_dir.is_some() || app_dir.is_some() {
|
||||
Section("App").display();
|
||||
if tauri_dir.is_some() {
|
||||
if let Ok(config) = get_config(None) {
|
||||
let config_guard = config.lock().unwrap();
|
||||
let config = config_guard.as_ref().unwrap();
|
||||
InfoBlock::new(" build-type")
|
||||
.value(if config.tauri.bundle.active {
|
||||
InfoBlock::new(
|
||||
"build-type",
|
||||
if config.tauri.bundle.active {
|
||||
"bundle".to_string()
|
||||
} else {
|
||||
"build".to_string()
|
||||
})
|
||||
.display();
|
||||
InfoBlock::new(" CSP")
|
||||
.value(
|
||||
config
|
||||
.tauri
|
||||
.security
|
||||
.csp
|
||||
.as_ref()
|
||||
.map(|c| c.to_string())
|
||||
.unwrap_or_else(|| "unset".to_string()),
|
||||
)
|
||||
.display();
|
||||
InfoBlock::new(" distDir")
|
||||
.value(config.build.dist_dir.to_string())
|
||||
.display();
|
||||
InfoBlock::new(" devPath")
|
||||
.value(config.build.dev_path.to_string())
|
||||
.display();
|
||||
},
|
||||
)
|
||||
.display();
|
||||
InfoBlock::new(
|
||||
"CSP",
|
||||
config
|
||||
.tauri
|
||||
.security
|
||||
.csp
|
||||
.clone()
|
||||
.map(|c| c.to_string())
|
||||
.unwrap_or_else(|| "unset".to_string()),
|
||||
)
|
||||
.display();
|
||||
InfoBlock::new("distDir", config.build.dist_dir.to_string()).display();
|
||||
InfoBlock::new("devPath", config.build.dev_path.to_string()).display();
|
||||
}
|
||||
}
|
||||
|
||||
@ -747,14 +786,10 @@ pub fn command(_options: Options) -> Result<()> {
|
||||
if let Ok(package_json) = read_to_string(app_dir.join("package.json")) {
|
||||
let (framework, bundler) = infer_framework(&package_json);
|
||||
if let Some(framework) = framework {
|
||||
InfoBlock::new(" framework")
|
||||
.value(framework.to_string())
|
||||
.display();
|
||||
InfoBlock::new("framework", framework.to_string()).display();
|
||||
}
|
||||
if let Some(bundler) = bundler {
|
||||
InfoBlock::new(" bundler")
|
||||
.value(bundler.to_string())
|
||||
.display();
|
||||
InfoBlock::new("bundler", bundler.to_string()).display();
|
||||
}
|
||||
} else {
|
||||
println!("package.json not found");
|
||||
@ -762,6 +797,27 @@ pub fn command(_options: Options) -> Result<()> {
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(app_dir) = app_dir {
|
||||
Section("App directory structure").display();
|
||||
let dirs = read_dir(app_dir)?
|
||||
.filter(|p| p.is_ok() && p.as_ref().unwrap().path().is_dir())
|
||||
.collect::<Vec<Result<std::fs::DirEntry, _>>>();
|
||||
let dirs_len = dirs.len();
|
||||
for (i, entry) in dirs.into_iter().enumerate() {
|
||||
let entry = entry?;
|
||||
let prefix = if i + 1 == dirs_len {
|
||||
"└─".cyan()
|
||||
} else {
|
||||
"├─".cyan()
|
||||
};
|
||||
println!(
|
||||
" {} {}",
|
||||
prefix,
|
||||
entry.path().file_name().unwrap().to_string_lossy()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
@ -194,17 +194,19 @@ pub fn command(mut options: Options) -> Result<()> {
|
||||
|
||||
fn request_input<T>(prompt: &str, default: Option<T>, skip: bool) -> Result<Option<T>>
|
||||
where
|
||||
T: Clone + FromStr + Display,
|
||||
T: Clone + FromStr + Display + ToString,
|
||||
T::Err: Display + std::fmt::Debug,
|
||||
{
|
||||
if skip {
|
||||
Ok(default)
|
||||
} else {
|
||||
let mut builder = Input::new();
|
||||
let theme = dialoguer::theme::ColorfulTheme::default();
|
||||
let mut builder = Input::with_theme(&theme);
|
||||
builder.with_prompt(prompt);
|
||||
|
||||
if let Some(v) = default {
|
||||
builder.default(v);
|
||||
builder.default(v.clone());
|
||||
builder.with_initial_text(v.to_string());
|
||||
}
|
||||
|
||||
builder.interact_text().map(Some).map_err(Into::into)
|
||||
|
Loading…
Reference in New Issue
Block a user