Merge branch 'dev' into next

This commit is contained in:
Lucas Nogueira 2023-04-03 09:58:14 -03:00
commit 55900a2968
No known key found for this signature in database
GPG Key ID: 7C32FCA95C8C95D7
38 changed files with 1611 additions and 1153 deletions

View File

@ -1,6 +0,0 @@
---
"api": minor
"tauri": minor
---
Added `raw` encoding option to read stdout and stderr raw bytes.

View File

@ -1449,7 +1449,7 @@
"additionalProperties": false
},
{
"description": "Download the bootstrapper and run it. Requires internet connection. Results in a smaller installer size, but is not recommended on Windows 7.",
"description": "Download the bootstrapper and run it. Requires an internet connection. Results in a smaller installer size, but is not recommended on Windows 7.",
"type": "object",
"required": [
"type"
@ -1470,7 +1470,7 @@
"additionalProperties": false
},
{
"description": "Embed the bootstrapper and run it. Requires internet connection. Increases the installer size by around 1.8MB, but offers better support on Windows 7.",
"description": "Embed the bootstrapper and run it. Requires an internet connection. Increases the installer size by around 1.8MB, but offers better support on Windows 7.",
"type": "object",
"required": [
"type"
@ -1491,7 +1491,7 @@
"additionalProperties": false
},
{
"description": "Embed the offline installer and run it. Does not require internet connection. Increases the installer size by around 127MB.",
"description": "Embed the offline installer and run it. Does not require an internet connection. Increases the installer size by around 127MB.",
"type": "object",
"required": [
"type"

View File

@ -2602,21 +2602,7 @@ fn handle_user_message<T: UserEvent>(
let _ = webview.print();
}
}
WebviewMessage::WebviewEvent(event) => {
let window_event_listeners = windows
.borrow()
.get(&id)
.map(|w| w.window_event_listeners.clone());
if let Some(window_event_listeners) = window_event_listeners {
if let Some(event) = WindowEventWrapper::from(&event).0 {
let listeners = window_event_listeners.lock().unwrap();
let handlers = listeners.values();
for handler in handlers {
handler(&event);
}
}
}
}
WebviewMessage::WebviewEvent(_event) => { /* already handled */ }
},
Message::CreateWebview(window_id, handler) => match handler(event_loop, web_context) {
Ok(webview) => {
@ -2910,6 +2896,24 @@ fn handle_event_loop<T: UserEvent>(
global_listener(id.0, &event);
}
}
Event::UserEvent(Message::Webview(id, WebviewMessage::WebviewEvent(event))) => {
if let Some(event) = WindowEventWrapper::from(&event).0 {
let windows = windows.borrow();
let window = windows.get(&id);
if let Some(window) = window {
callback(RunEvent::WindowEvent {
label: window.label.clone(),
event: event.clone(),
});
let listeners = window.window_event_listeners.lock().unwrap();
let handlers = listeners.values();
for handler in handlers {
handler(&event);
}
}
}
}
Event::WindowEvent {
event, window_id, ..
} => {

View File

@ -506,7 +506,7 @@ pub enum WebviewInstallMode {
/// Do not install the Webview2 as part of the Windows Installer.
Skip,
/// Download the bootstrapper and run it.
/// Requires internet connection.
/// Requires an internet connection.
/// Results in a smaller installer size, but is not recommended on Windows 7.
DownloadBootstrapper {
/// Instructs the installer to run the bootstrapper in silent mode. Defaults to `true`.
@ -514,7 +514,7 @@ pub enum WebviewInstallMode {
silent: bool,
},
/// Embed the bootstrapper and run it.
/// Requires internet connection.
/// Requires an internet connection.
/// Increases the installer size by around 1.8MB, but offers better support on Windows 7.
EmbedBootstrapper {
/// Instructs the installer to run the bootstrapper in silent mode. Defaults to `true`.
@ -522,7 +522,7 @@ pub enum WebviewInstallMode {
silent: bool,
},
/// Embed the offline installer and run it.
/// Does not require internet connection.
/// Does not require an internet connection.
/// Increases the installer size by around 127MB.
OfflineInstaller {
/// Instructs the installer to run the installer in silent mode. Defaults to `true`.

View File

@ -73,7 +73,7 @@ os_pipe = { version = "1.0", optional = true }
raw-window-handle = "0.5"
minisign-verify = { version = "0.2", optional = true }
time = { version = "0.3", features = [ "parsing", "formatting" ], optional = true }
os_info = { version = "3.5", optional = true }
os_info = { version = "3", optional = true }
regex = { version = "1.6.0", optional = true }
glob = "0.3"
data-url = { version = "0.2", optional = true }
@ -85,7 +85,7 @@ encoding_rs = "0.8.31"
[target."cfg(any(target_os = \"macos\", windows, target_os = \"linux\", target_os = \"dragonfly\", target_os = \"freebsd\", target_os = \"openbsd\", target_os = \"netbsd\"))".dependencies]
rfd = { version = "0.11", optional = true, features = [ "gtk3", "common-controls-v6" ] }
notify-rust = { version = "4.5", default-features = false, features = [ "d" ], optional = true }
notify-rust = { version = "4.5", optional = true }
[target."cfg(any(target_os = \"linux\", target_os = \"dragonfly\", target_os = \"freebsd\", target_os = \"openbsd\", target_os = \"netbsd\"))".dependencies]
gtk = { version = "0.16", features = [ "v3_24" ] }

View File

@ -216,8 +216,9 @@ impl Cmd {
#[module_command_handler(window_create)]
async fn create_webview<R: Runtime>(
context: InvokeContext<R>,
options: Box<WindowConfig>,
mut options: Box<WindowConfig>,
) -> super::Result<()> {
options.additional_browser_args = None;
crate::window::WindowBuilder::from_config(&context.window, *options)
.build()
.map_err(crate::error::into_anyhow)?;

View File

@ -384,18 +384,16 @@ impl<R: Runtime> UpdateBuilder<R> {
// If we got a success, we stop the loop
// and we set our remote_release variable
if let Ok(res) = resp {
let res = res.read().await?;
let status = res.status();
// got status code 2XX
if StatusCode::from_u16(res.status)
.map_err(|e| Error::Builder(e.to_string()))?
.is_success()
{
if status.is_success() {
// if we got 204
if StatusCode::NO_CONTENT.as_u16() == res.status {
if status == StatusCode::NO_CONTENT {
// return with `UpToDate` error
// we should catch on the client
return Err(Error::UpToDate);
};
let res = res.read().await?;
// Convert the remote result to our local struct
let built_release = serde_json::from_value(res.data).map_err(Into::into);
// make sure all went well and the remote data is compatible

View File

@ -7,9 +7,18 @@
fn main() {
let mut context = tauri::generate_context!();
if std::env::var("TARGET").unwrap_or_default() == "nsis" {
// /D sets the default installation directory ($INSTDIR),
// overriding InstallDir and InstallDirRegKey.
// It must be the last parameter used in the command line and must not contain any quotes, even if the path contains spaces.
// Only absolute paths are supported.
// NOTE: we only need this because this is an integration test and we don't want to install the app in the programs folder
context.config_mut().tauri.updater.windows.installer_args = vec![format!(
"/D={}",
std::env::current_exe().unwrap().parent().unwrap().display()
tauri::utils::platform::current_exe()
.unwrap()
.parent()
.unwrap()
.display()
)];
}
tauri::Builder::default()

View File

@ -7,7 +7,7 @@ license = ""
repository = ""
default-run = "app"
edition = "2021"
rust-version = "1.59"
rust-version = "1.60"
[build-dependencies]
tauri-build = { workspace = true }

View File

@ -57,11 +57,11 @@ interface Duration {
* @since 1.0.0
*/
interface ClientOptions {
maxRedirections?: number
/**
* Defines the maximum number of redirects the client should follow.
* If set to 0, no redirects will be followed.
*/
maxRedirections?: number
connectTimeout?: number | Duration
}

View File

@ -772,7 +772,7 @@ async function extname(path: string): Promise<string> {
* import { basename, resolveResource } from '@tauri-apps/api/path';
* const resourcePath = await resolveResource('app.conf');
* const base = await basename(resourcePath);
* assert(base === 'app');
* assert(base === 'app.conf');
* ```
*
* @param ext An optional file extension to be removed from the returned path.

View File

@ -2196,10 +2196,6 @@ interface WindowOptions {
* The user agent for the webview.
*/
userAgent?: string
/**
* Additional arguments for the webview. **Windows Only**
*/
additionalBrowserArguments?: string
}
function mapMonitor(m: Monitor | null): Monitor | null {

View File

@ -96,7 +96,7 @@ pub fn bundle_project(settings: &Settings, bundles: &[Bundle]) -> crate::Result<
"--volname",
product_name,
"--icon",
product_name,
&bundle_file_name,
"180",
"170",
"--app-drop-link",

View File

@ -113,23 +113,25 @@ pub fn extract_zip(data: &[u8], path: &Path) -> crate::Result<()> {
for i in 0..zipa.len() {
let mut file = zipa.by_index(i)?;
let dest_path = path.join(file.name());
if file.is_dir() {
create_dir_all(&dest_path)?;
continue;
if let Some(name) = file.enclosed_name() {
let dest_path = path.join(name);
if file.is_dir() {
create_dir_all(&dest_path)?;
continue;
}
let parent = dest_path.parent().expect("Failed to get parent");
if !parent.exists() {
create_dir_all(parent)?;
}
let mut buff: Vec<u8> = Vec::new();
file.read_to_end(&mut buff)?;
let mut fileout = File::create(dest_path).expect("Failed to open file");
fileout.write_all(&buff)?;
}
let parent = dest_path.parent().expect("Failed to get parent");
if !parent.exists() {
create_dir_all(parent)?;
}
let mut buff: Vec<u8> = Vec::new();
file.read_to_end(&mut buff)?;
let mut fileout = File::create(dest_path).expect("Failed to open file");
fileout.write_all(&buff)?;
}
Ok(())

View File

@ -70,7 +70,7 @@ include_dir = "0.7"
minisign = "0.7"
base64 = "0.21.0"
ureq = "2.5"
os_info = "3.5"
os_info = "3"
semver = "1.0"
regex = "1.6.0"
unicode-width = "0.1"

View File

@ -1449,7 +1449,7 @@
"additionalProperties": false
},
{
"description": "Download the bootstrapper and run it. Requires internet connection. Results in a smaller installer size, but is not recommended on Windows 7.",
"description": "Download the bootstrapper and run it. Requires an internet connection. Results in a smaller installer size, but is not recommended on Windows 7.",
"type": "object",
"required": [
"type"
@ -1470,7 +1470,7 @@
"additionalProperties": false
},
{
"description": "Embed the bootstrapper and run it. Requires internet connection. Increases the installer size by around 1.8MB, but offers better support on Windows 7.",
"description": "Embed the bootstrapper and run it. Requires an internet connection. Increases the installer size by around 1.8MB, but offers better support on Windows 7.",
"type": "object",
"required": [
"type"
@ -1491,7 +1491,7 @@
"additionalProperties": false
},
{
"description": "Embed the offline installer and run it. Does not require internet connection. Increases the installer size by around 127MB.",
"description": "Embed the offline installer and run it. Does not require an internet connection. Increases the installer size by around 127MB.",
"type": "object",
"required": [
"type"

Binary file not shown.

View File

@ -313,7 +313,7 @@ pub fn setup(options: &mut Options, mobile: bool) -> Result<AppInterface> {
use crate::helpers::web_dev_server::start_dev_server;
if path.exists() {
let path = path.canonicalize()?;
let server_url = start_dev_server(path, options.port);
let server_url = start_dev_server(path, options.port)?;
let server_url = format!("http://{server_url}");
dev_path = AppUrl::Url(WindowUrl::External(server_url.parse().unwrap()));

View File

@ -31,17 +31,10 @@ struct State {
tx: Sender<()>,
}
pub fn start_dev_server<P: AsRef<Path>>(path: P, port: Option<u16>) -> SocketAddr {
pub fn start_dev_server<P: AsRef<Path>>(path: P, port: Option<u16>) -> crate::Result<SocketAddr> {
let serve_dir = path.as_ref().to_path_buf();
let server_url = SocketAddr::new(
Ipv4Addr::new(127, 0, 0, 1).into(),
port.unwrap_or_else(|| {
std::env::var("TAURI_DEV_SERVER_PORT")
.unwrap_or_else(|_| "1430".to_string())
.parse()
.unwrap()
}),
);
let (server_url_tx, server_url_rx) = std::sync::mpsc::channel();
std::thread::spawn(move || {
tokio::runtime::Builder::new_current_thread()
@ -74,6 +67,32 @@ pub fn start_dev_server<P: AsRef<Path>>(path: P, port: Option<u16>) -> SocketAdd
}
});
let mut auto_port = false;
let mut port = port.unwrap_or_else(|| {
std::env::var("TAURI_DEV_SERVER_PORT")
.unwrap_or_else(|_| {
auto_port = true;
"1430".to_string()
})
.parse()
.unwrap()
});
let (server, server_url) = loop {
let server_url = SocketAddr::new(Ipv4Addr::new(127, 0, 0, 1).into(), port);
let server = Server::try_bind(&server_url);
if !auto_port {
break (server, server_url);
}
if server.is_ok() {
break (server, server_url);
}
port += 1;
};
let state = Arc::new(State {
serve_dir,
tx,
@ -96,14 +115,24 @@ pub fn start_dev_server<P: AsRef<Path>>(path: P, port: Option<u16>) -> SocketAdd
ws.on_upgrade(|socket| async move { ws_handler(socket, state).await })
}),
);
Server::bind(&server_url)
.serve(router.into_make_service())
.await
.unwrap();
match server {
Ok(server) => {
server_url_tx.send(Ok(server_url)).unwrap();
server.serve(router.into_make_service()).await.unwrap();
}
Err(e) => {
server_url_tx
.send(Err(anyhow::anyhow!(
"failed to start development server on {server_url}: {e}"
)))
.unwrap();
}
}
})
});
server_url
server_url_rx.recv().unwrap()
}
async fn handler<T>(req: Request<T>, state: Arc<State>) -> impl IntoResponse {

View File

@ -1,959 +0,0 @@
// Copyright 2019-2023 Tauri Programme within The Commons Conservancy
// SPDX-License-Identifier: Apache-2.0
// SPDX-License-Identifier: MIT
use crate::{
helpers::{config::get as get_config, framework::infer_from_package_json as infer_framework},
interface::rust::get_workspace_dir,
Result,
};
use clap::Parser;
use colored::Colorize;
use serde::Deserialize;
use std::{
collections::HashMap,
fmt::Write,
fs::{read_dir, read_to_string},
panic,
path::{Path, PathBuf},
process::Command,
};
#[derive(Deserialize)]
struct YarnVersionInfo {
data: Vec<String>,
}
#[derive(Clone, Deserialize)]
struct CargoLockPackage {
name: String,
version: String,
source: Option<String>,
}
#[derive(Deserialize)]
struct CargoLock {
package: Vec<CargoLockPackage>,
}
#[derive(Deserialize)]
struct JsCliVersionMetadata {
version: String,
node: String,
}
#[derive(Deserialize)]
#[serde(rename_all = "camelCase")]
struct VersionMetadata {
#[serde(rename = "cli.js")]
js_cli: JsCliVersionMetadata,
}
#[derive(Clone, Deserialize)]
struct CargoManifestDependencyPackage {
version: Option<String>,
git: Option<String>,
branch: Option<String>,
rev: Option<String>,
path: Option<PathBuf>,
}
#[derive(Clone, Deserialize)]
#[serde(untagged)]
enum CargoManifestDependency {
Version(String),
Package(CargoManifestDependencyPackage),
}
#[derive(Deserialize)]
struct CargoManifestPackage {
version: String,
}
#[derive(Deserialize)]
struct CargoManifest {
package: CargoManifestPackage,
dependencies: HashMap<String, CargoManifestDependency>,
}
#[derive(Debug, PartialEq, Eq)]
enum PackageManager {
Npm,
Pnpm,
Yarn,
Berry,
}
#[derive(Debug, Parser)]
#[clap(about = "Shows information about Tauri dependencies and project configuration")]
pub struct Options;
fn version_metadata() -> Result<VersionMetadata> {
serde_json::from_str::<VersionMetadata>(include_str!("../metadata.json")).map_err(Into::into)
}
#[cfg(not(debug_assertions))]
pub(crate) fn cli_current_version() -> Result<String> {
version_metadata().map(|meta| meta.js_cli.version)
}
#[cfg(not(debug_assertions))]
pub(crate) fn cli_upstream_version() -> Result<String> {
let upstream_metadata = match ureq::get(
"https://raw.githubusercontent.com/tauri-apps/tauri/dev/tooling/cli/metadata.json",
)
.timeout(std::time::Duration::from_secs(3))
.call()
{
Ok(r) => r,
Err(ureq::Error::Status(code, _response)) => {
let message = format!("Unable to find updates at the moment. Code: {}", code);
return Err(anyhow::Error::msg(message));
}
Err(ureq::Error::Transport(transport)) => {
let message = format!(
"Unable to find updates at the moment. Error: {:?}",
transport.kind()
);
return Err(anyhow::Error::msg(message));
}
};
upstream_metadata
.into_string()
.and_then(|meta_str| Ok(serde_json::from_str::<VersionMetadata>(&meta_str)))
.and_then(|json| Ok(json.unwrap().js_cli.version))
.map_err(|e| anyhow::Error::new(e))
}
fn crate_latest_version(name: &str) -> Option<String> {
let url = format!("https://docs.rs/crate/{name}/");
match ureq::get(&url).call() {
Ok(response) => match (response.status(), response.header("location")) {
(302, Some(location)) => Some(location.replace(&url, "")),
_ => None,
},
Err(_) => None,
}
}
#[allow(clippy::let_and_return)]
fn cross_command(bin: &str) -> Command {
#[cfg(target_os = "windows")]
let cmd = {
let mut cmd = Command::new("cmd");
cmd.arg("/c").arg(bin);
cmd
};
#[cfg(not(target_os = "windows"))]
let cmd = Command::new(bin);
cmd
}
fn npm_latest_version(pm: &PackageManager, name: &str) -> crate::Result<Option<String>> {
match pm {
PackageManager::Yarn => {
let mut cmd = cross_command("yarn");
let output = cmd
.arg("info")
.arg(name)
.args(["version", "--json"])
.output()?;
if output.status.success() {
let stdout = String::from_utf8_lossy(&output.stdout);
let info: YarnVersionInfo = serde_json::from_str(&stdout)?;
Ok(Some(info.data.last().unwrap().to_string()))
} else {
Ok(None)
}
}
PackageManager::Berry => {
let mut cmd = cross_command("yarn");
let output = cmd
.arg("npm")
.arg("info")
.arg(name)
.args(["--fields", "version", "--json"])
.output()?;
if output.status.success() {
let info: crate::PackageJson =
serde_json::from_reader(std::io::Cursor::new(output.stdout)).unwrap();
Ok(info.version)
} else {
Ok(None)
}
}
PackageManager::Npm => {
let mut cmd = cross_command("npm");
let output = cmd.arg("show").arg(name).arg("version").output()?;
if output.status.success() {
let stdout = String::from_utf8_lossy(&output.stdout);
Ok(Some(stdout.replace('\n', "")))
} else {
Ok(None)
}
}
PackageManager::Pnpm => {
let mut cmd = cross_command("pnpm");
let output = cmd.arg("info").arg(name).arg("version").output()?;
if output.status.success() {
let stdout = String::from_utf8_lossy(&output.stdout);
Ok(Some(stdout.replace('\n', "")))
} else {
Ok(None)
}
}
}
}
fn npm_package_version<P: AsRef<Path>>(
pm: &PackageManager,
name: &str,
app_dir: P,
) -> crate::Result<Option<String>> {
let (output, regex) = match pm {
PackageManager::Yarn => (
cross_command("yarn")
.args(["list", "--pattern"])
.arg(name)
.args(["--depth", "0"])
.current_dir(app_dir)
.output()?,
None,
),
PackageManager::Berry => (
cross_command("yarn")
.arg("info")
.arg(name)
.current_dir(app_dir)
.output()?,
Some(regex::Regex::new("Version: ([\\da-zA-Z\\-\\.]+)").unwrap()),
),
PackageManager::Npm => (
cross_command("npm")
.arg("list")
.arg(name)
.args(["version", "--depth", "0"])
.current_dir(app_dir)
.output()?,
None,
),
PackageManager::Pnpm => (
cross_command("pnpm")
.arg("list")
.arg(name)
.args(["--parseable", "--depth", "0"])
.current_dir(app_dir)
.output()?,
None,
),
};
if output.status.success() {
let stdout = String::from_utf8_lossy(&output.stdout);
let regex = regex.unwrap_or_else(|| regex::Regex::new("@([\\da-zA-Z\\-\\.]+)").unwrap());
Ok(
regex
.captures_iter(&stdout)
.last()
.and_then(|cap| cap.get(1).map(|v| v.as_str().to_string())),
)
} else {
Ok(None)
}
}
fn get_version(command: &str, args: &[&str]) -> crate::Result<Option<String>> {
let output = cross_command(command)
.args(args)
.arg("--version")
.output()?;
let version = if output.status.success() {
Some(String::from_utf8_lossy(&output.stdout).replace(['\n', '\r'], ""))
} else {
None
};
Ok(version)
}
#[cfg(windows)]
fn webview2_version() -> crate::Result<Option<String>> {
let powershell_path = std::env::var("SYSTEMROOT").map_or_else(
|_| "powershell.exe".to_string(),
|p| format!("{p}\\System32\\WindowsPowerShell\\v1.0\\powershell.exe"),
);
// check 64bit machine-wide installation
let output = Command::new(&powershell_path)
.args(["-NoProfile", "-Command"])
.arg("Get-ItemProperty -Path 'HKLM:\\SOFTWARE\\WOW6432Node\\Microsoft\\EdgeUpdate\\Clients\\{F3017226-FE2A-4295-8BDF-00C3A9A7E4C5}' | ForEach-Object {$_.pv}")
.output()?;
if output.status.success() {
return Ok(Some(
String::from_utf8_lossy(&output.stdout).replace('\n', ""),
));
}
// check 32bit machine-wide installation
let output = Command::new(&powershell_path)
.args(["-NoProfile", "-Command"])
.arg("Get-ItemProperty -Path 'HKLM:\\SOFTWARE\\Microsoft\\EdgeUpdate\\Clients\\{F3017226-FE2A-4295-8BDF-00C3A9A7E4C5}' | ForEach-Object {$_.pv}")
.output()?;
if output.status.success() {
return Ok(Some(
String::from_utf8_lossy(&output.stdout).replace('\n', ""),
));
}
// check user-wide installation
let output = Command::new(&powershell_path)
.args(["-NoProfile", "-Command"])
.arg("Get-ItemProperty -Path 'HKCU:\\SOFTWARE\\Microsoft\\EdgeUpdate\\Clients\\{F3017226-FE2A-4295-8BDF-00C3A9A7E4C5}' | ForEach-Object {$_.pv}")
.output()?;
if output.status.success() {
return Ok(Some(
String::from_utf8_lossy(&output.stdout).replace('\n', ""),
));
}
Ok(None)
}
#[cfg(windows)]
#[derive(Deserialize, Debug)]
#[serde(rename_all = "camelCase")]
struct VsInstanceInfo {
display_name: String,
}
#[cfg(windows)]
const VSWHERE: &[u8] = include_bytes!("../scripts/vswhere.exe");
#[cfg(windows)]
fn build_tools_version() -> crate::Result<Option<Vec<String>>> {
let mut vswhere = std::env::temp_dir();
vswhere.push("vswhere.exe");
if !vswhere.exists() {
if let Ok(mut file) = std::fs::File::create(&vswhere) {
use std::io::Write;
let _ = file.write_all(VSWHERE);
}
}
let output = cross_command(vswhere.to_str().unwrap())
.args([
"-prerelease",
"-products",
"*",
"-requiresAny",
"-requires",
"Microsoft.VisualStudio.Workload.NativeDesktop",
"-requires",
"Microsoft.VisualStudio.Workload.VCTools",
"-format",
"json",
])
.output()?;
Ok(if output.status.success() {
let stdout = String::from_utf8_lossy(&output.stdout);
let instances: Vec<VsInstanceInfo> = serde_json::from_str(&stdout)?;
Some(
instances
.iter()
.map(|i| i.display_name.clone())
.collect::<Vec<String>>(),
)
} else {
None
})
}
fn active_rust_toolchain() -> crate::Result<Option<String>> {
let output = cross_command("rustup")
.args(["show", "active-toolchain"])
.output()?;
let toolchain = if output.status.success() {
Some(
String::from_utf8_lossy(&output.stdout)
.replace(['\n', '\r'], "")
.split('(')
.collect::<Vec<&str>>()[0]
.into(),
)
} else {
None
};
Ok(toolchain)
}
fn crate_version(
tauri_dir: &Path,
manifest: Option<&CargoManifest>,
lock: Option<&CargoLock>,
name: &str,
) -> (String, Option<String>) {
let crate_lock_packages: Vec<CargoLockPackage> = lock
.as_ref()
.map(|lock| {
lock
.package
.iter()
.filter(|p| p.name == name)
.cloned()
.collect()
})
.unwrap_or_default();
let (crate_version_string, found_crate_versions) =
match (&manifest, &lock, crate_lock_packages.len()) {
(Some(_manifest), Some(_lock), 1) => {
let crate_lock_package = crate_lock_packages.first().unwrap();
let version_string = if let Some(s) = &crate_lock_package.source {
if s.starts_with("git") {
format!("{} ({})", s, crate_lock_package.version)
} else {
crate_lock_package.version.clone()
}
} else {
crate_lock_package.version.clone()
};
(version_string, vec![crate_lock_package.version.clone()])
}
(None, Some(_lock), 1) => {
let crate_lock_package = crate_lock_packages.first().unwrap();
let version_string = if let Some(s) = &crate_lock_package.source {
if s.starts_with("git") {
format!("{} ({})", s, crate_lock_package.version)
} else {
crate_lock_package.version.clone()
}
} else {
crate_lock_package.version.clone()
};
(
format!("{version_string} (no manifest)"),
vec![crate_lock_package.version.clone()],
)
}
_ => {
let mut found_crate_versions = Vec::new();
let mut is_git = false;
let manifest_version = match manifest.and_then(|m| m.dependencies.get(name).cloned()) {
Some(tauri) => match tauri {
CargoManifestDependency::Version(v) => {
found_crate_versions.push(v.clone());
v
}
CargoManifestDependency::Package(p) => {
if let Some(v) = p.version {
found_crate_versions.push(v.clone());
v
} else if let Some(p) = p.path {
let manifest_path = tauri_dir.join(&p).join("Cargo.toml");
let v = match read_to_string(manifest_path)
.map_err(|_| ())
.and_then(|m| toml::from_str::<CargoManifest>(&m).map_err(|_| ()))
{
Ok(manifest) => manifest.package.version,
Err(_) => "unknown version".to_string(),
};
format!("path:{p:?} [{v}]")
} else if let Some(g) = p.git {
is_git = true;
let mut v = format!("git:{g}");
if let Some(branch) = p.branch {
let _ = write!(v, "&branch={branch}");
} else if let Some(rev) = p.rev {
let _ = write!(v, "#{rev}");
}
v
} else {
"unknown manifest".to_string()
}
}
},
None => "no manifest".to_string(),
};
let lock_version = match (lock, crate_lock_packages.is_empty()) {
(Some(_lock), false) => crate_lock_packages
.iter()
.map(|p| p.version.clone())
.collect::<Vec<String>>()
.join(", "),
(Some(_lock), true) => "unknown lockfile".to_string(),
_ => "no lockfile".to_string(),
};
(
format!(
"{} {}({})",
manifest_version,
if is_git { "(git manifest)" } else { "" },
lock_version
),
found_crate_versions,
)
}
};
let crate_version = found_crate_versions
.into_iter()
.map(|v| semver::Version::parse(&v).ok())
.max();
let suffix = match (crate_version, crate_latest_version(name)) {
(Some(Some(version)), Some(target_version)) => {
let target_version = semver::Version::parse(&target_version).unwrap();
if version < target_version {
Some(format!(" (outdated, latest: {target_version})"))
} else {
None
}
}
_ => None,
};
(crate_version_string, suffix)
}
fn indent(spaces: usize) {
print!("{}", " ".repeat(spaces));
}
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,
}
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,
}
}
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.version.is_empty() || self.target_version.is_empty()) {
let version = semver::Version::parse(self.version.as_str()).unwrap();
let target_version = semver::Version::parse(self.target_version.as_str()).unwrap();
if version < target_version {
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();
VersionBlock::new(
"OS",
format!(
"{} {} {:?}",
os_info.os_type(),
os_info.version(),
os_info.bitness()
),
)
.display();
#[cfg(windows)]
VersionBlock::new(
"Webview2",
webview2_version().unwrap_or_default().unwrap_or_default(),
)
.display();
#[cfg(windows)]
{
let build_tools = build_tools_version()
.unwrap_or_default()
.unwrap_or_default();
if build_tools.is_empty() {
InfoBlock::new("MSVC", "").display();
} else {
InfoBlock::new("MSVC", "").display();
for i in build_tools {
indent(6);
println!("{} {}", "-".cyan(), i);
}
}
}
let hook = panic::take_hook();
panic::set_hook(Box::new(|_info| {
// do nothing
}));
let app_dir = panic::catch_unwind(crate::helpers::app_paths::app_dir)
.map(Some)
.unwrap_or_default();
panic::set_hook(hook);
let yarn_version = get_version("yarn", &[])
.unwrap_or_default()
.unwrap_or_default();
let metadata = version_metadata()?;
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(">= ", ""))
.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", &yarn_version).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 app_dir_entries = read_dir(app_dir)
.unwrap()
.map(|e| e.unwrap().file_name().to_string_lossy().into_owned())
.collect::<Vec<String>>();
package_manager = get_package_manager(&app_dir_entries)?;
}
if package_manager == PackageManager::Yarn
&& yarn_version
.chars()
.next()
.map(|c| c > '1')
.unwrap_or_default()
{
package_manager = PackageManager::Berry;
}
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(
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(),
)
.display();
}
let hook = panic::take_hook();
panic::set_hook(Box::new(|_info| {
// do nothing
}));
let tauri_dir = panic::catch_unwind(crate::helpers::app_paths::tauri_dir)
.map(Some)
.unwrap_or_default();
panic::set_hook(hook);
if tauri_dir.is_some() || app_dir.is_some() {
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()
} else {
None
};
let lock: Option<CargoLock> = get_workspace_dir()
.ok()
.and_then(|p| read_to_string(p.join("Cargo.lock")).ok())
.and_then(|s| toml::from_str(&s).ok());
for (dep, label) in [
("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);
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",
if config.tauri.bundle.active {
"bundle".to_string()
} else {
"build".to_string()
},
)
.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();
}
}
if let Some(app_dir) = app_dir {
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", framework.to_string()).display();
}
if let Some(bundler) = bundler {
InfoBlock::new("bundler", bundler.to_string()).display();
}
} else {
println!("package.json not found");
}
}
}
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()
);
}
}
#[cfg(target_os = "macos")]
if tauri_dir.is_some() {
let p = tauri_dir.as_ref().unwrap();
if p.join("gen/apple").exists() {
let teams = tauri_mobile::apple::teams::find_development_teams().unwrap_or_default();
Section("iOS").display();
InfoBlock::new(
"Teams",
if teams.is_empty() {
"None".red().to_string()
} else {
teams
.iter()
.map(|t| format!("{} (ID: {})", t.name, t.id))
.collect::<Vec<String>>()
.join(", ")
},
)
.display();
}
}
Ok(())
}
fn get_package_manager<T: AsRef<str>>(app_dir_entries: &[T]) -> crate::Result<PackageManager> {
let mut use_npm = false;
let mut use_pnpm = false;
let mut use_yarn = false;
for name in app_dir_entries {
if name.as_ref() == "package-lock.json" {
use_npm = true;
} else if name.as_ref() == "pnpm-lock.yaml" {
use_pnpm = true;
} else if name.as_ref() == "yarn.lock" {
use_yarn = true;
}
}
if !use_npm && !use_pnpm && !use_yarn {
println!("WARNING: no lock files found, defaulting to npm");
return Ok(PackageManager::Npm);
}
let mut found = Vec::new();
if use_npm {
found.push("npm");
}
if use_pnpm {
found.push("pnpm");
}
if use_yarn {
found.push("yarn");
}
if found.len() > 1 {
return Err(anyhow::anyhow!(
"only one package manager should be used, but found {}\nplease remove unused package manager lock files",
found.join(" and ")
));
}
if use_npm {
Ok(PackageManager::Npm)
} else if use_pnpm {
Ok(PackageManager::Pnpm)
} else {
Ok(PackageManager::Yarn)
}
}

View File

@ -0,0 +1,80 @@
// Copyright 2019-2023 Tauri Programme within The Commons Conservancy
// SPDX-License-Identifier: Apache-2.0
// SPDX-License-Identifier: MIT
use super::{SectionItem, Status};
use crate::helpers::framework;
use std::{
fs::read_to_string,
path::{Path, PathBuf},
};
pub fn items(app_dir: Option<&PathBuf>, tauri_dir: Option<&Path>) -> Vec<SectionItem> {
let mut items = Vec::new();
if tauri_dir.is_some() {
if let Ok(config) = crate::helpers::config::get(None) {
let config_guard = config.lock().unwrap();
let config = config_guard.as_ref().unwrap();
let bundle_or_build = if config.tauri.bundle.active {
"bundle".to_string()
} else {
"build".to_string()
};
items.push(SectionItem::new(
move || Some((format!("build-type: {bundle_or_build}"), Status::Neutral)),
|| None,
false,
));
let csp = config
.tauri
.security
.csp
.clone()
.map(|c| c.to_string())
.unwrap_or_else(|| "unset".to_string());
items.push(SectionItem::new(
move || Some((format!("CSP: {csp}"), Status::Neutral)),
|| None,
false,
));
let dist_dir = config.build.dist_dir.to_string();
items.push(SectionItem::new(
move || Some((format!("distDir: {dist_dir}"), Status::Neutral)),
|| None,
false,
));
let dev_path = config.build.dev_path.to_string();
items.push(SectionItem::new(
move || Some((format!("devPath: {dev_path}"), Status::Neutral)),
|| None,
false,
));
if let Some(app_dir) = app_dir {
if let Ok(package_json) = read_to_string(app_dir.join("package.json")) {
let (framework, bundler) = framework::infer_from_package_json(&package_json);
if let Some(framework) = framework {
items.push(SectionItem::new(
move || Some((format!("framework: {framework}"), Status::Neutral)),
|| None,
false,
));
}
if let Some(bundler) = bundler {
items.push(SectionItem::new(
move || Some((format!("bundler: {bundler}"), Status::Neutral)),
|| None,
false,
));
}
}
}
}
}
items
}

View File

@ -0,0 +1,125 @@
// Copyright 2019-2023 Tauri Programme within The Commons Conservancy
// SPDX-License-Identifier: Apache-2.0
// SPDX-License-Identifier: MIT
use super::{cross_command, VersionMetadata};
use super::{SectionItem, Status};
use colored::Colorize;
pub fn items(metadata: &VersionMetadata) -> (Vec<SectionItem>, Option<String>) {
let yarn_version = cross_command("yarn")
.arg("-v")
.output()
.map(|o| {
if o.status.success() {
let v = String::from_utf8_lossy(o.stdout.as_slice()).to_string();
Some(v.split('\n').next().unwrap().to_string())
} else {
None
}
})
.ok()
.unwrap_or_default();
let yarn_version_c = yarn_version.clone();
let node_target_ver = metadata.js_cli.node.replace(">= ", "");
(
vec![
SectionItem::new(
move || {
cross_command("node")
.arg("-v")
.output()
.map(|o| {
if o.status.success() {
let v = String::from_utf8_lossy(o.stdout.as_slice()).to_string();
let v = v
.split('\n')
.next()
.unwrap()
.strip_prefix('v')
.unwrap_or_default()
.trim();
Some((
format!("node: {}{}", v, {
let version = semver::Version::parse(v).unwrap();
let target_version = semver::Version::parse(node_target_ver.as_str()).unwrap();
if version < target_version {
format!(
" ({}, latest: {})",
"outdated".red(),
target_version.to_string().green()
)
} else {
"".into()
}
}),
Status::Neutral,
))
} else {
None
}
})
.ok()
.unwrap_or_default()
},
|| None,
false,
),
SectionItem::new(
|| {
cross_command("pnpm")
.arg("-v")
.output()
.map(|o| {
if o.status.success() {
let v = String::from_utf8_lossy(o.stdout.as_slice()).to_string();
Some((
format!("pnpm: {}", v.split('\n').next().unwrap()),
Status::Neutral,
))
} else {
None
}
})
.ok()
.unwrap_or_default()
},
|| None,
false,
),
SectionItem::new(
move || {
yarn_version_c
.as_ref()
.map(|v| (format!("yarn: {v}"), Status::Neutral))
},
|| None,
false,
),
SectionItem::new(
|| {
cross_command("npm")
.arg("-v")
.output()
.map(|o| {
if o.status.success() {
let v = String::from_utf8_lossy(o.stdout.as_slice()).to_string();
Some((
format!("npm: {}", v.split('\n').next().unwrap()),
Status::Neutral,
))
} else {
None
}
})
.ok()
.unwrap_or_default()
},
|| None,
false,
),
],
yarn_version,
)
}

View File

@ -0,0 +1,145 @@
// Copyright 2019-2023 Tauri Programme within The Commons Conservancy
// SPDX-License-Identifier: Apache-2.0
// SPDX-License-Identifier: MIT
use super::SectionItem;
use super::Status;
use colored::Colorize;
use std::process::Command;
pub fn items() -> Vec<SectionItem> {
vec![
SectionItem::new(
|| {
Some(
Command::new("rustc")
.arg("-V")
.output()
.map(|o| String::from_utf8_lossy(o.stdout.as_slice()).to_string())
.map(|v| {
format!(
"rustc: {}",
v.split('\n')
.next()
.unwrap()
.strip_prefix("rustc ")
.unwrap_or_default()
)
})
.map(|desc| (desc, Status::Success))
.ok()
.unwrap_or_else(|| {
(
format!(
"rustc: {}\nmaybe you don't have rust installed! Visist {}",
"not installed!".red(),
"https://rustup.rs/".cyan()
),
Status::Error,
)
}),
)
},
|| None,
false,
),
SectionItem::new(
|| {
Some(
Command::new("cargo")
.arg("-V")
.output()
.map(|o| String::from_utf8_lossy(o.stdout.as_slice()).to_string())
.map(|v| {
format!(
"Cargo: {}",
v.split('\n')
.next()
.unwrap()
.strip_prefix("cargo ")
.unwrap_or_default()
)
})
.map(|desc| (desc, Status::Success))
.ok()
.unwrap_or_else(|| {
(
format!(
"Cargo: {}\nmaybe you don't have rust installed! Visit {}",
"not installed!".red(),
"https://rustup.rs/".cyan()
),
Status::Error,
)
}),
)
},
|| None,
false,
),
SectionItem::new(
|| {
Some(
Command::new("rustup")
.arg("-V")
.output()
.map(|o| String::from_utf8_lossy(o.stdout.as_slice()).to_string())
.map(|v| {
format!(
"rustup: {}",
v.split('\n')
.next()
.unwrap()
.strip_prefix("rustup ")
.unwrap_or_default()
)
})
.map(|desc| (desc, Status::Success))
.ok()
.unwrap_or_else(|| {
(
format!(
"rustup: {}\nIf you have rust installed some other way, we recommend uninstalling it\nthen use rustup instead. Visit {}",
"not installed!".red(),
"https://rustup.rs/".cyan()
),
Status::Warning,
)
}),
)
},
|| None,
false,
),
SectionItem::new(
|| {
Some(
Command::new("rustup")
.args(["show", "active-toolchain"])
.output()
.map(|o| String::from_utf8_lossy(o.stdout.as_slice()).to_string())
.map(|v| {
format!(
"Rust toolchain: {}",
v.split('\n')
.next()
.unwrap()
)
})
.map(|desc| (desc, Status::Success))
.ok()
.unwrap_or_else(|| {
(
format!(
"Rust toolchain: couldn't be deteceted!\nmaybe you don't have rustup installed? if so, Visit {}", "https://rustup.rs/".cyan()
),
Status::Warning,
)
}),
)
},
|| None,
false,
),
]
}

View File

@ -0,0 +1,299 @@
// Copyright 2019-2023 Tauri Programme within The Commons Conservancy
// SPDX-License-Identifier: Apache-2.0
// SPDX-License-Identifier: MIT
use super::SectionItem;
use super::Status;
use colored::Colorize;
#[cfg(windows)]
use serde::Deserialize;
use std::process::Command;
#[cfg(windows)]
#[derive(Deserialize, Debug)]
#[serde(rename_all = "camelCase")]
struct VsInstanceInfo {
display_name: String,
}
#[cfg(windows)]
const VSWHERE: &[u8] = include_bytes!("../../scripts/vswhere.exe");
#[cfg(windows)]
fn build_tools_version() -> crate::Result<Option<Vec<String>>> {
let mut vswhere = std::env::temp_dir();
vswhere.push("vswhere.exe");
if !vswhere.exists() {
if let Ok(mut file) = std::fs::File::create(&vswhere) {
use std::io::Write;
let _ = file.write_all(VSWHERE);
}
}
let output = Command::new(vswhere)
.args([
"-prerelease",
"-products",
"*",
"-requiresAny",
"-requires",
"Microsoft.VisualStudio.Workload.NativeDesktop",
"-requires",
"Microsoft.VisualStudio.Workload.VCTools",
"-format",
"json",
])
.output()?;
Ok(if output.status.success() {
let stdout = String::from_utf8_lossy(&output.stdout);
let instances: Vec<VsInstanceInfo> = serde_json::from_str(&stdout)?;
Some(
instances
.iter()
.map(|i| i.display_name.clone())
.collect::<Vec<String>>(),
)
} else {
None
})
}
#[cfg(windows)]
fn webview2_version() -> crate::Result<Option<String>> {
let powershell_path = std::env::var("SYSTEMROOT").map_or_else(
|_| "powershell.exe".to_string(),
|p| format!("{p}\\System32\\WindowsPowerShell\\v1.0\\powershell.exe"),
);
// check 64bit machine-wide installation
let output = Command::new(&powershell_path)
.args(["-NoProfile", "-Command"])
.arg("Get-ItemProperty -Path 'HKLM:\\SOFTWARE\\WOW6432Node\\Microsoft\\EdgeUpdate\\Clients\\{F3017226-FE2A-4295-8BDF-00C3A9A7E4C5}' | ForEach-Object {$_.pv}")
.output()?;
if output.status.success() {
return Ok(Some(
String::from_utf8_lossy(&output.stdout).replace('\n', ""),
));
}
// check 32bit machine-wide installation
let output = Command::new(&powershell_path)
.args(["-NoProfile", "-Command"])
.arg("Get-ItemProperty -Path 'HKLM:\\SOFTWARE\\Microsoft\\EdgeUpdate\\Clients\\{F3017226-FE2A-4295-8BDF-00C3A9A7E4C5}' | ForEach-Object {$_.pv}")
.output()?;
if output.status.success() {
return Ok(Some(
String::from_utf8_lossy(&output.stdout).replace('\n', ""),
));
}
// check user-wide installation
let output = Command::new(&powershell_path)
.args(["-NoProfile", "-Command"])
.arg("Get-ItemProperty -Path 'HKCU:\\SOFTWARE\\Microsoft\\EdgeUpdate\\Clients\\{F3017226-FE2A-4295-8BDF-00C3A9A7E4C5}' | ForEach-Object {$_.pv}")
.output()?;
if output.status.success() {
return Ok(Some(
String::from_utf8_lossy(&output.stdout).replace('\n', ""),
));
}
Ok(None)
}
#[cfg(any(
target_os = "linux",
target_os = "dragonfly",
target_os = "freebsd",
target_os = "openbsd",
target_os = "netbsd"
))]
fn pkg_conf_version(package: &str) -> Option<String> {
Command::new("pkg-config")
.args([package, "--print-provides"])
.output()
.map(|o| {
String::from_utf8_lossy(&o.stdout)
.split('=')
.nth(1)
.map(|s| s.trim().to_string())
})
.unwrap_or(None)
}
#[cfg(any(
target_os = "linux",
target_os = "dragonfly",
target_os = "freebsd",
target_os = "openbsd",
target_os = "netbsd"
))]
fn webkit2gtk_ver() -> Option<String> {
pkg_conf_version("webkit2gtk-4.0")
}
#[cfg(any(
target_os = "linux",
target_os = "dragonfly",
target_os = "freebsd",
target_os = "openbsd",
target_os = "netbsd"
))]
fn rsvg2_ver() -> Option<String> {
pkg_conf_version("librsvg-2.0")
}
#[cfg(target_os = "macos")]
fn is_xcode_command_line_tools_installed() -> bool {
Command::new("xcode-select")
.arg("-p")
.output()
.map(|o| o.status.success())
.unwrap_or(false)
}
pub fn items() -> Vec<SectionItem> {
vec![
SectionItem::new(
|| {
let os_info = os_info::get();
Some((
format!(
"OS: {} {} {:?}",
os_info.os_type(),
os_info.version(),
os_info.bitness()
),
Status::Neutral,
))
},
|| None,
false,
),
#[cfg(windows)]
SectionItem::new(
|| {
let error = || {
format!(
"Webview2: {}\nVisit {}",
"not installed!".red(),
"https://developer.microsoft.com/en-us/microsoft-edge/webview2/".cyan()
)
};
Some(
webview2_version()
.map(|v| {
v.map(|v| (format!("WebView2: {}", v), Status::Success))
.unwrap_or_else(|| (error(), Status::Error))
})
.unwrap_or_else(|_| (error(), Status::Error)),
)
},
|| None,
false,
),
#[cfg(windows)]
SectionItem::new(
|| {
let build_tools = build_tools_version()
.unwrap_or_default()
.unwrap_or_default();
if build_tools.is_empty() {
Some((
format!(
"Couldn't detect Visual Studio or Visual Studio Build Tools. Download from {}",
"https://aka.ms/vs/17/release/vs_BuildTools.exe".cyan()
),
Status::Error,
))
} else {
Some((
format!(
"MSVC: {}{}",
if build_tools.len() > 1 {
format!("\n {} ", "-".cyan())
} else {
"".into()
},
build_tools.join(format!("\n {} ", "-".cyan()).as_str()),
),
Status::Success,
))
}
},
|| None,
false,
),
#[cfg(any(
target_os = "linux",
target_os = "dragonfly",
target_os = "freebsd",
target_os = "openbsd",
target_os = "netbsd"
))]
SectionItem::new(
|| {
Some(
webkit2gtk_ver()
.map(|v| (format!("webkit2gtk-4.0: {v}"), Status::Success))
.unwrap_or_else(|| {
(
format!(
"webkit2gtk-4.0: {}\nVisit {} to learn more about tauri prerequisites",
"not installed".red(),
"https://tauri.app/v1/guides/getting-started/prerequisites".cyan()
),
Status::Error,
)
}),
)
},
|| None,
false,
),
#[cfg(any(
target_os = "linux",
target_os = "dragonfly",
target_os = "freebsd",
target_os = "openbsd",
target_os = "netbsd"
))]
SectionItem::new(
|| {
Some(
rsvg2_ver()
.map(|v| (format!("rsvg2: {v}"), Status::Success))
.unwrap_or_else(|| {
(
format!(
"rsvg2: {}\nVisit {} to learn more about tauri prerequisites",
"not installed".red(),
"https://tauri.app/v1/guides/getting-started/prerequisites".cyan()
),
Status::Error,
)
}),
)
},
|| None,
false,
),
#[cfg(target_os = "macos")]
SectionItem::new(
|| {
Some(if is_xcode_command_line_tools_installed() {
(
"Xcode Command Line Tools: installed".into(),
Status::Success,
)
} else {
(
format!(
"Xcode Command Line Tools: {}\n Run `{}`",
"not installed!".red(),
"xcode-select --install".cyan()
),
Status::Error,
)
})
},
|| None,
false,
),
]
}

View File

@ -0,0 +1,25 @@
use super::{SectionItem, Status};
use colored::Colorize;
pub fn items() -> Vec<SectionItem> {
vec![SectionItem::new(
|| {
let teams = tauri_mobile::apple::teams::find_development_teams().unwrap_or_default();
Some((
if teams.is_empty() {
"None".red().to_string()
} else {
teams
.iter()
.map(|t| format!("{} (ID: {})", t.name, t.id))
.collect::<Vec<String>>()
.join(", ")
},
Status::Neutral,
))
},
|| None,
false,
)]
}

287
tooling/cli/src/info/mod.rs Normal file
View File

@ -0,0 +1,287 @@
// Copyright 2019-2023 Tauri Programme within The Commons Conservancy
// SPDX-License-Identifier: Apache-2.0
// SPDX-License-Identifier: MIT
use crate::Result;
use clap::Parser;
use colored::Colorize;
use dialoguer::{theme::ColorfulTheme, Confirm};
use serde::Deserialize;
use std::{
fmt::{self, Display, Formatter},
panic,
process::Command,
};
mod app;
mod env_nodejs;
mod env_rust;
mod env_system;
#[cfg(target_os = "macos")]
mod ios;
mod packages_nodejs;
mod packages_rust;
#[derive(Deserialize)]
struct JsCliVersionMetadata {
version: String,
node: String,
}
#[derive(Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct VersionMetadata {
#[serde(rename = "cli.js")]
js_cli: JsCliVersionMetadata,
}
fn version_metadata() -> Result<VersionMetadata> {
serde_json::from_str::<VersionMetadata>(include_str!("../../metadata.json")).map_err(Into::into)
}
#[cfg(not(debug_assertions))]
pub(crate) fn cli_current_version() -> Result<String> {
version_metadata().map(|meta| meta.js_cli.version)
}
#[cfg(not(debug_assertions))]
pub(crate) fn cli_upstream_version() -> Result<String> {
let upstream_metadata = match ureq::get(
"https://raw.githubusercontent.com/tauri-apps/tauri/dev/tooling/cli/metadata.json",
)
.timeout(std::time::Duration::from_secs(3))
.call()
{
Ok(r) => r,
Err(ureq::Error::Status(code, _response)) => {
let message = format!("Unable to find updates at the moment. Code: {}", code);
return Err(anyhow::Error::msg(message));
}
Err(ureq::Error::Transport(transport)) => {
let message = format!(
"Unable to find updates at the moment. Error: {:?}",
transport.kind()
);
return Err(anyhow::Error::msg(message));
}
};
upstream_metadata
.into_string()
.and_then(|meta_str| Ok(serde_json::from_str::<VersionMetadata>(&meta_str)))
.and_then(|json| Ok(json.unwrap().js_cli.version))
.map_err(|e| anyhow::Error::new(e))
}
pub fn cross_command(bin: &str) -> Command {
#[cfg(target_os = "windows")]
let cmd = {
let mut cmd = Command::new("cmd");
cmd.arg("/c").arg(bin);
cmd
};
#[cfg(not(target_os = "windows"))]
let cmd = Command::new(bin);
cmd
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Default)]
pub enum Status {
Neutral = 0,
#[default]
Success,
Warning,
Error,
}
impl Display for Status {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
write!(
f,
"{}",
match self {
Status::Neutral => "-".cyan(),
Status::Success => "".green(),
Status::Warning => "".yellow(),
Status::Error => "".red(),
}
)
}
}
pub struct SectionItem {
/// If description is none, the item is skipped
description: Option<String>,
status: Status,
/// This closure return will be assigned to status and description
action: Box<dyn FnMut() -> Option<(String, Status)>>,
/// This closure return will be assigned to status and description
action_if_err: Box<dyn FnMut() -> Option<(String, Status)>>,
has_action_if_err: bool,
}
impl Display for SectionItem {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
let desc = self
.description
.as_ref()
.map(|s| s.replace('\n', "\n "))
.unwrap_or_default();
let (first, second) = desc.split_once(':').unwrap();
write!(f, "{} {}:{}", self.status, first.bold(), second)
}
}
impl SectionItem {
fn new<
F1: FnMut() -> Option<(String, Status)> + 'static,
F2: FnMut() -> Option<(String, Status)> + 'static,
>(
action: F1,
action_if_err: F2,
has_action_if_err: bool,
) -> Self {
Self {
action: Box::new(action),
action_if_err: Box::new(action_if_err),
has_action_if_err,
description: None,
status: Status::Neutral,
}
}
fn run(&mut self, interactive: bool) -> Status {
if let Some(ret) = (self.action)() {
self.description = Some(ret.0);
self.status = ret.1;
}
if self.status == Status::Error && interactive && self.has_action_if_err {
if let Some(description) = &self.description {
let confirmed = Confirm::with_theme(&ColorfulTheme::default())
.with_prompt(format!(
"{}\n Run the automatic fix?",
description.replace('\n', "\n ")
))
.interact()
.unwrap_or(false);
if confirmed {
if let Some(ret) = (self.action_if_err)() {
self.description = Some(ret.0);
self.status = ret.1;
}
}
}
}
self.status
}
}
struct Section<'a> {
label: &'a str,
interactive: bool,
items: Vec<SectionItem>,
}
impl Section<'_> {
fn display(&mut self) {
let mut status = Status::Neutral;
for item in &mut self.items {
let s = item.run(self.interactive);
if s > status {
status = s;
}
}
let status_str = format!("[{status}]");
let status = match status {
Status::Neutral => status_str.normal(),
Status::Success => status_str.green(),
Status::Warning => status_str.yellow(),
Status::Error => status_str.red(),
};
println!();
println!("{} {}", status, self.label.bold().yellow());
for item in &self.items {
if item.description.is_some() {
println!(" {item}");
}
}
}
}
#[derive(Debug, Parser)]
#[clap(about = "Shows information about Tauri dependencies and project configuration")]
pub struct Options {
/// Interactive mode to apply automatic fixes.
#[clap(long)]
pub interactive: bool,
}
pub fn command(options: Options) -> Result<()> {
let Options { interactive } = options;
let hook = panic::take_hook();
panic::set_hook(Box::new(|_info| {
// do nothing
}));
let app_dir = panic::catch_unwind(crate::helpers::app_paths::app_dir)
.map(Some)
.unwrap_or_default();
let tauri_dir = panic::catch_unwind(crate::helpers::app_paths::tauri_dir)
.map(Some)
.unwrap_or_default();
panic::set_hook(hook);
let metadata = version_metadata()?;
let mut environment = Section {
label: "Environment",
interactive,
items: Vec::new(),
};
environment.items.extend(env_system::items());
environment.items.extend(env_rust::items());
let (items, yarn_version) = env_nodejs::items(&metadata);
environment.items.extend(items);
let mut packages = Section {
label: "Packages",
interactive,
items: Vec::new(),
};
packages
.items
.extend(packages_rust::items(app_dir, tauri_dir.as_deref()));
packages
.items
.extend(packages_nodejs::items(app_dir, &metadata, yarn_version));
let mut app = Section {
label: "App",
interactive,
items: Vec::new(),
};
app.items.extend(app::items(app_dir, tauri_dir.as_deref()));
environment.display();
packages.display();
app.display();
// iOS
#[cfg(target_os = "macos")]
{
if let Some(p) = &tauri_dir {
if p.join("gen/apple").exists() {
let mut ios = Section {
label: "iOS",
interactive,
items: Vec::new(),
};
ios.items.extend(ios::items());
ios.display();
}
}
}
Ok(())
}

View File

@ -0,0 +1,287 @@
// Copyright 2019-2023 Tauri Programme within The Commons Conservancy
// SPDX-License-Identifier: Apache-2.0
// SPDX-License-Identifier: MIT
use super::{cross_command, VersionMetadata};
use super::{SectionItem, Status};
use colored::Colorize;
use serde::Deserialize;
use std::fmt::Display;
use std::path::{Path, PathBuf};
#[derive(Deserialize)]
struct YarnVersionInfo {
data: Vec<String>,
}
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
enum PackageManager {
Npm,
Pnpm,
Yarn,
YarnBerry,
}
impl Display for PackageManager {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
"{}",
match self {
PackageManager::Npm => "npm",
PackageManager::Pnpm => "pnpm",
PackageManager::Yarn => "yarn",
PackageManager::YarnBerry => "yarn berry",
}
)
}
}
fn npm_latest_version(pm: &PackageManager, name: &str) -> crate::Result<Option<String>> {
match pm {
PackageManager::Yarn => {
let mut cmd = cross_command("yarn");
let output = cmd
.arg("info")
.arg(name)
.args(["version", "--json"])
.output()?;
if output.status.success() {
let stdout = String::from_utf8_lossy(&output.stdout);
let info: YarnVersionInfo = serde_json::from_str(&stdout)?;
Ok(Some(info.data.last().unwrap().to_string()))
} else {
Ok(None)
}
}
PackageManager::YarnBerry => {
let mut cmd = cross_command("yarn");
let output = cmd
.arg("npm")
.arg("info")
.arg(name)
.args(["--fields", "version", "--json"])
.output()?;
if output.status.success() {
let info: crate::PackageJson =
serde_json::from_reader(std::io::Cursor::new(output.stdout)).unwrap();
Ok(info.version)
} else {
Ok(None)
}
}
PackageManager::Npm => {
let mut cmd = cross_command("npm");
let output = cmd.arg("show").arg(name).arg("version").output()?;
if output.status.success() {
let stdout = String::from_utf8_lossy(&output.stdout);
Ok(Some(stdout.replace('\n', "")))
} else {
Ok(None)
}
}
PackageManager::Pnpm => {
let mut cmd = cross_command("pnpm");
let output = cmd.arg("info").arg(name).arg("version").output()?;
if output.status.success() {
let stdout = String::from_utf8_lossy(&output.stdout);
Ok(Some(stdout.replace('\n', "")))
} else {
Ok(None)
}
}
}
}
fn npm_package_version<P: AsRef<Path>>(
pm: &PackageManager,
name: &str,
app_dir: P,
) -> crate::Result<Option<String>> {
let (output, regex) = match pm {
PackageManager::Yarn => (
cross_command("yarn")
.args(["list", "--pattern"])
.arg(name)
.args(["--depth", "0"])
.current_dir(app_dir)
.output()?,
None,
),
PackageManager::YarnBerry => (
cross_command("yarn")
.arg("info")
.arg(name)
.arg("--json")
.current_dir(app_dir)
.output()?,
Some(regex::Regex::new("\"Version\":\"([\\da-zA-Z\\-\\.]+)\"").unwrap()),
),
PackageManager::Npm => (
cross_command("npm")
.arg("list")
.arg(name)
.args(["version", "--depth", "0"])
.current_dir(app_dir)
.output()?,
None,
),
PackageManager::Pnpm => (
cross_command("pnpm")
.arg("list")
.arg(name)
.args(["--parseable", "--depth", "0"])
.current_dir(app_dir)
.output()?,
None,
),
};
if output.status.success() {
let stdout = String::from_utf8_lossy(&output.stdout);
let regex = regex.unwrap_or_else(|| regex::Regex::new("@(\\d[\\da-zA-Z\\-\\.]+)").unwrap());
Ok(
regex
.captures_iter(&stdout)
.last()
.and_then(|cap| cap.get(1).map(|v| v.as_str().to_string())),
)
} else {
Ok(None)
}
}
fn get_package_manager<T: AsRef<str>>(app_dir_entries: &[T]) -> PackageManager {
let mut use_npm = false;
let mut use_pnpm = false;
let mut use_yarn = false;
for name in app_dir_entries {
if name.as_ref() == "package-lock.json" {
use_npm = true;
} else if name.as_ref() == "pnpm-lock.yaml" {
use_pnpm = true;
} else if name.as_ref() == "yarn.lock" {
use_yarn = true;
}
}
if !use_npm && !use_pnpm && !use_yarn {
println!(
"{}: no lock files found, defaulting to npm",
"WARNING".yellow()
);
return PackageManager::Npm;
}
let mut found = Vec::new();
if use_npm {
found.push(PackageManager::Npm);
}
if use_pnpm {
found.push(PackageManager::Pnpm);
}
if use_yarn {
found.push(PackageManager::Yarn);
}
if found.len() > 1 {
let pkg_manger = found[0];
println!(
"{}: Only one package manager should be used, but found {}.\n Please remove unused package manager lock files, will use {} for now!",
"WARNING".yellow(),
found.iter().map(ToString::to_string).collect::<Vec<_>>().join(" and "),
pkg_manger
);
return pkg_manger;
}
if use_npm {
PackageManager::Npm
} else if use_pnpm {
PackageManager::Pnpm
} else {
PackageManager::Yarn
}
}
pub fn items(
app_dir: Option<&PathBuf>,
metadata: &VersionMetadata,
yarn_version: Option<String>,
) -> Vec<SectionItem> {
let mut package_manager = PackageManager::Npm;
if let Some(app_dir) = &app_dir {
let app_dir_entries = std::fs::read_dir(app_dir)
.unwrap()
.map(|e| e.unwrap().file_name().to_string_lossy().into_owned())
.collect::<Vec<String>>();
package_manager = get_package_manager(&app_dir_entries);
}
if package_manager == PackageManager::Yarn
&& yarn_version
.map(|v| v.chars().next().map(|c| c > '1').unwrap_or_default())
.unwrap_or(false)
{
package_manager = PackageManager::YarnBerry;
}
let mut items = Vec::new();
if let Some(app_dir) = app_dir {
for (package, version) in [
("@tauri-apps/api", None),
("@tauri-apps/cli", Some(metadata.js_cli.version.clone())),
] {
let app_dir = app_dir.clone();
let item = SectionItem::new(
move || {
let version = version.clone().unwrap_or_else(|| {
npm_package_version(&package_manager, package, &app_dir)
.unwrap_or_default()
.unwrap_or_default()
});
let latest_ver = npm_latest_version(&package_manager, package)
.unwrap_or_default()
.unwrap_or_default();
Some((
if version.is_empty() {
format!("{} {}: not installed!", package, "[NPM]".dimmed())
} else {
format!(
"{} {}: {}{}",
package,
"[NPM]".dimmed(),
version,
if !(version.is_empty() || latest_ver.is_empty()) {
let version = semver::Version::parse(version.as_str()).unwrap();
let target_version = semver::Version::parse(latest_ver.as_str()).unwrap();
if version < target_version {
format!(" ({}, latest: {})", "outdated".yellow(), latest_ver.green())
} else {
"".into()
}
} else {
"".into()
}
)
},
Status::Neutral,
))
},
|| None,
false,
);
items.push(item);
}
}
items
}

View File

@ -0,0 +1,240 @@
// Copyright 2019-2023 Tauri Programme within The Commons Conservancy
// SPDX-License-Identifier: Apache-2.0
// SPDX-License-Identifier: MIT
use super::{SectionItem, Status};
use crate::interface::rust::get_workspace_dir;
use colored::Colorize;
use serde::Deserialize;
use std::collections::HashMap;
use std::fmt::Write;
use std::fs::read_to_string;
use std::path::{Path, PathBuf};
#[derive(Clone, Deserialize)]
struct CargoLockPackage {
name: String,
version: String,
source: Option<String>,
}
#[derive(Deserialize)]
struct CargoLock {
package: Vec<CargoLockPackage>,
}
#[derive(Clone, Deserialize)]
struct CargoManifestDependencyPackage {
version: Option<String>,
git: Option<String>,
branch: Option<String>,
rev: Option<String>,
path: Option<PathBuf>,
}
#[derive(Clone, Deserialize)]
#[serde(untagged)]
enum CargoManifestDependency {
Version(String),
Package(CargoManifestDependencyPackage),
}
#[derive(Deserialize)]
struct CargoManifestPackage {
version: String,
}
#[derive(Deserialize)]
struct CargoManifest {
package: CargoManifestPackage,
dependencies: HashMap<String, CargoManifestDependency>,
}
fn crate_latest_version(name: &str) -> Option<String> {
let url = format!("https://docs.rs/crate/{name}/");
match ureq::get(&url).call() {
Ok(response) => match (response.status(), response.header("location")) {
(302, Some(location)) => Some(location.replace(&url, "")),
_ => None,
},
Err(_) => None,
}
}
fn crate_version(
tauri_dir: &Path,
manifest: Option<&CargoManifest>,
lock: Option<&CargoLock>,
name: &str,
) -> (String, Option<String>) {
let crate_lock_packages: Vec<CargoLockPackage> = lock
.as_ref()
.map(|lock| {
lock
.package
.iter()
.filter(|p| p.name == name)
.cloned()
.collect()
})
.unwrap_or_default();
let (crate_version_string, found_crate_versions) =
match (&manifest, &lock, crate_lock_packages.len()) {
(Some(_manifest), Some(_lock), 1) => {
let crate_lock_package = crate_lock_packages.first().unwrap();
let version_string = if let Some(s) = &crate_lock_package.source {
if s.starts_with("git") {
format!("{} ({})", s, crate_lock_package.version)
} else {
crate_lock_package.version.clone()
}
} else {
crate_lock_package.version.clone()
};
(version_string, vec![crate_lock_package.version.clone()])
}
(None, Some(_lock), 1) => {
let crate_lock_package = crate_lock_packages.first().unwrap();
let version_string = if let Some(s) = &crate_lock_package.source {
if s.starts_with("git") {
format!("{} ({})", s, crate_lock_package.version)
} else {
crate_lock_package.version.clone()
}
} else {
crate_lock_package.version.clone()
};
(
format!("{version_string} (no manifest)"),
vec![crate_lock_package.version.clone()],
)
}
_ => {
let mut found_crate_versions = Vec::new();
let mut is_git = false;
let manifest_version = match manifest.and_then(|m| m.dependencies.get(name).cloned()) {
Some(tauri) => match tauri {
CargoManifestDependency::Version(v) => {
found_crate_versions.push(v.clone());
v
}
CargoManifestDependency::Package(p) => {
if let Some(v) = p.version {
found_crate_versions.push(v.clone());
v
} else if let Some(p) = p.path {
let manifest_path = tauri_dir.join(&p).join("Cargo.toml");
let v = match read_to_string(manifest_path)
.map_err(|_| ())
.and_then(|m| toml::from_str::<CargoManifest>(&m).map_err(|_| ()))
{
Ok(manifest) => manifest.package.version,
Err(_) => "unknown version".to_string(),
};
format!("path:{p:?} [{v}]")
} else if let Some(g) = p.git {
is_git = true;
let mut v = format!("git:{g}");
if let Some(branch) = p.branch {
let _ = write!(v, "&branch={branch}");
} else if let Some(rev) = p.rev {
let _ = write!(v, "#{rev}");
}
v
} else {
"unknown manifest".to_string()
}
}
},
None => "no manifest".to_string(),
};
let lock_version = match (lock, crate_lock_packages.is_empty()) {
(Some(_lock), false) => crate_lock_packages
.iter()
.map(|p| p.version.clone())
.collect::<Vec<String>>()
.join(", "),
(Some(_lock), true) => "unknown lockfile".to_string(),
_ => "no lockfile".to_string(),
};
(
format!(
"{} {}({})",
manifest_version,
if is_git { "(git manifest)" } else { "" },
lock_version
),
found_crate_versions,
)
}
};
let crate_version = found_crate_versions
.into_iter()
.map(|v| semver::Version::parse(&v).ok())
.max();
let suffix = match (crate_version, crate_latest_version(name)) {
(Some(Some(version)), Some(target_version)) => {
let target_version = semver::Version::parse(&target_version).unwrap();
if version < target_version {
Some(format!(
" ({}, latest: {})",
"outdated".yellow(),
target_version.to_string().green()
))
} else {
None
}
}
_ => None,
};
(crate_version_string, suffix)
}
pub fn items(app_dir: Option<&PathBuf>, tauri_dir: Option<&Path>) -> Vec<SectionItem> {
let mut items = Vec::new();
if tauri_dir.is_some() || app_dir.is_some() {
if let Some(tauri_dir) = tauri_dir {
let manifest: Option<CargoManifest> =
if let Ok(manifest_contents) = read_to_string(tauri_dir.join("Cargo.toml")) {
toml::from_str(&manifest_contents).ok()
} else {
None
};
let lock: Option<CargoLock> = get_workspace_dir()
.ok()
.and_then(|p| read_to_string(p.join("Cargo.lock")).ok())
.and_then(|s| toml::from_str(&s).ok());
for dep in ["tauri", "tauri-build", "wry", "tao"] {
let (version_string, version_suffix) =
crate_version(tauri_dir, manifest.as_ref(), lock.as_ref(), dep);
let dep = dep.to_string();
let item = SectionItem::new(
move || {
Some((
format!(
"{} {}: {}{}",
dep,
"[RUST]".dimmed(),
version_string,
version_suffix
.clone()
.map(|s| format!(",{s}"))
.unwrap_or_else(|| "".into())
),
Status::Neutral,
))
},
|| None,
false,
);
items.push(item);
}
}
}
items
}

View File

@ -623,6 +623,7 @@ struct BinarySettings {
/// The package settings.
#[derive(Debug, Clone, Deserialize)]
#[serde(rename_all = "kebab-case")]
pub struct CargoPackageSettings {
/// the package's name.
pub name: Option<String>,

View File

@ -31,7 +31,7 @@ pub fn command(mut options: Options) -> Result<()> {
options.ci = options.ci || std::env::var("CI").is_ok();
if options.ci && options.password.is_none() {
println!("Generating new private key without password.");
log::warn!("Generating new private key without password. For security reasons, we recommend setting a password instead.");
options.password.replace("".into());
}
let keypair = generate_key(options.password).expect("Failed to generate key");

View File

@ -21,8 +21,11 @@ jobs:
audit:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v3
- uses: actions-rs/audit-check@v1
with:
token: ${{ secrets.GITHUB_TOKEN }}
permissions:
issues: write
checks: write
{{{{/raw}}}}

View File

@ -17,7 +17,7 @@ jobs:
fail-fast: false
steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v3
- name: install webkit2gtk
run: |
sudo apt-get update

View File

@ -10,7 +10,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v3
with:
fetch-depth: 0
- uses: actions/setup-node@v2

View File

@ -17,7 +17,7 @@ jobs:
fail-fast: false
steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v3
- name: Install rustfmt with stable toolchain
uses: actions-rs/toolchain@v1
with:

View File

@ -1,46 +0,0 @@
{{{{raw}}}}
name: Test
on:
push:
branches:
- main
pull_request:
branches:
- main
- dev
paths-ignore:
- 'webview-src/**'
- 'webview-dist/**'
- 'examples/**'
jobs:
build-and-test:
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest, macos-latest, windows-latest]
steps:
- uses: actions/checkout@v2
- name: Install stable toolchain
uses: actions-rs/toolchain@v1
with:
toolchain: stable
override: true
- name: Install Linux dependencies
run: |
sudo apt-get update
sudo apt-get install -y webkit2gtk-4.1
- uses: Swatinem/rust-cache@v2
- name: Run tests
uses: actions-rs/cargo@v1
with:
command: test
args: --manifest-path=Cargo.toml --release
{{{{/raw}}}}

View File

@ -1,16 +0,0 @@
[package]
name = "tauri-plugin-{{ plugin_name }}"
version = "0.0.0"
authors = [ "{{ author }}" ]
description = ""
edition = "2021"
rust-version = "1.64"
exclude = ["/examples", "/webview-dist", "/webview-src", "node_modules"]
[dependencies]
tauri = {{{ tauri_dep }}}
serde = "1.0"
thiserror = "1.0"
[build-dependencies]
tauri-build = {{{ tauri_build_dep }}}

View File

@ -1,26 +0,0 @@
[package]
name = "tauri-app"
version = "0.0.0"
description = "A Tauri App"
authors = ["you"]
license = ""
repository = ""
edition = "2021"
rust-version = "1.64"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[build-dependencies]
tauri-build = {{{ tauri_build_dep }}}
[dependencies]
serde_json = "1.0"
serde = { version = "1.0", features = ["derive"] }
tauri = {{{ tauri_example_dep }}}
tauri-plugin-{{ plugin_name }} = { path = "../../../" }
[features]
# this feature is used for production builds or when `devPath` points to the filesystem and the built-in dev server is disabled.
# If you use cargo directly instead of tauri's cli you can use this feature flag to switch between tauri's `dev` and `build` modes.
# DO NOT REMOVE!!
custom-protocol = [ "tauri/custom-protocol" ]

View File

@ -1,20 +0,0 @@
[package]
name = "app"
version = "0.1.0"
description = "A Tauri App"
authors = [ "{{ author }}" ]
repository = ""
edition = "2021"
rust-version = "1.64"
[dependencies]
serde_json = "1.0"
serde = { version = "1.0", features = [ "derive" ] }
tauri = {{{ tauri_example_dep }}}
tauri-plugin-{{ plugin_name }} = { path = "../../../" }
[build-dependencies]
tauri-build = {{{ tauri_build_dep }}}
[features]
custom-protocol = [ "tauri/custom-protocol" ]