mirror of
https://github.com/tauri-apps/tauri.git
synced 2024-12-01 11:13:40 +03:00
feat(cli): plugin add
command (#7023)
This commit is contained in:
parent
9770032860
commit
7e5905ae1d
6
.changes/add-command.md
Normal file
6
.changes/add-command.md
Normal file
@ -0,0 +1,6 @@
|
||||
---
|
||||
"cli.rs": patch
|
||||
"cli.js": patch
|
||||
---
|
||||
|
||||
Added `tauri plugin add` command to add a plugin to the Tauri project.
|
@ -6,6 +6,7 @@ pub mod app_paths;
|
||||
pub mod config;
|
||||
pub mod flock;
|
||||
pub mod framework;
|
||||
pub mod npm;
|
||||
pub mod template;
|
||||
pub mod updater_signature;
|
||||
pub mod web_dev_server;
|
||||
@ -13,6 +14,7 @@ pub mod web_dev_server;
|
||||
use std::{
|
||||
collections::HashMap,
|
||||
path::{Path, PathBuf},
|
||||
process::Command,
|
||||
};
|
||||
|
||||
pub fn command_env(debug: bool) -> HashMap<&'static str, String> {
|
||||
@ -38,3 +40,15 @@ pub fn resolve_tauri_path<P: AsRef<Path>>(path: P, crate_name: &str) -> PathBuf
|
||||
PathBuf::from("..").join(path).join(crate_name)
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
68
tooling/cli/src/helpers/npm.rs
Normal file
68
tooling/cli/src/helpers/npm.rs
Normal file
@ -0,0 +1,68 @@
|
||||
// Copyright 2019-2023 Tauri Programme within The Commons Conservancy
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
use std::{fmt::Display, path::Path};
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
|
||||
pub 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",
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl PackageManager {
|
||||
pub fn from_project<P: AsRef<Path>>(path: P) -> Vec<Self> {
|
||||
let mut use_npm = false;
|
||||
let mut use_pnpm = false;
|
||||
let mut use_yarn = false;
|
||||
|
||||
if let Ok(entries) = std::fs::read_dir(path) {
|
||||
for entry in entries.flatten() {
|
||||
let path = entry.path();
|
||||
let name = path.file_name().unwrap().to_string_lossy();
|
||||
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 {
|
||||
return Vec::new();
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
found
|
||||
}
|
||||
}
|
@ -2,10 +2,12 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
use super::{cross_command, VersionMetadata};
|
||||
use super::VersionMetadata;
|
||||
use super::{SectionItem, Status};
|
||||
use colored::Colorize;
|
||||
|
||||
use crate::helpers::cross_command;
|
||||
|
||||
pub fn items(metadata: &VersionMetadata) -> (Vec<SectionItem>, Option<String>) {
|
||||
let yarn_version = cross_command("yarn")
|
||||
.arg("-v")
|
||||
|
@ -10,7 +10,6 @@ use serde::Deserialize;
|
||||
use std::{
|
||||
fmt::{self, Display, Formatter},
|
||||
panic,
|
||||
process::Command,
|
||||
};
|
||||
|
||||
mod app;
|
||||
@ -73,18 +72,6 @@ pub(crate) fn cli_upstream_version() -> Result<String> {
|
||||
.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,
|
||||
|
@ -2,41 +2,19 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
use super::{cross_command, VersionMetadata};
|
||||
use super::VersionMetadata;
|
||||
use super::{SectionItem, Status};
|
||||
use colored::Colorize;
|
||||
use serde::Deserialize;
|
||||
use std::fmt::Display;
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
use crate::helpers::{cross_command, npm::PackageManager};
|
||||
|
||||
#[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 => {
|
||||
@ -154,74 +132,36 @@ fn npm_package_version<P: AsRef<Path>>(
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
let package_managers = app_dir
|
||||
.map(PackageManager::from_project)
|
||||
.unwrap_or_else(|| {
|
||||
println!(
|
||||
"{}: no lock files found, defaulting to npm",
|
||||
"WARNING".yellow()
|
||||
);
|
||||
vec![PackageManager::Npm]
|
||||
});
|
||||
|
||||
let mut package_manager = if package_managers.len() > 1 {
|
||||
let pkg_manager = package_managers[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(),
|
||||
package_managers.iter().map(ToString::to_string).collect::<Vec<_>>().join(" and "),
|
||||
pkg_manager
|
||||
);
|
||||
pkg_manager
|
||||
} else {
|
||||
package_managers
|
||||
.into_iter()
|
||||
.next()
|
||||
.unwrap_or(PackageManager::Npm)
|
||||
};
|
||||
|
||||
if package_manager == PackageManager::Yarn
|
||||
&& yarn_version
|
||||
|
@ -6,6 +6,7 @@ use clap::{Parser, Subcommand};
|
||||
|
||||
use crate::Result;
|
||||
|
||||
mod add;
|
||||
mod android;
|
||||
mod init;
|
||||
mod ios;
|
||||
@ -26,6 +27,7 @@ pub struct Cli {
|
||||
#[derive(Subcommand)]
|
||||
enum Commands {
|
||||
Init(init::Options),
|
||||
Add(add::Options),
|
||||
Android(android::Cli),
|
||||
Ios(ios::Cli),
|
||||
}
|
||||
@ -33,6 +35,7 @@ enum Commands {
|
||||
pub fn command(cli: Cli) -> Result<()> {
|
||||
match cli.command {
|
||||
Commands::Init(options) => init::command(options)?,
|
||||
Commands::Add(options) => add::command(options)?,
|
||||
Commands::Android(cli) => android::command(cli)?,
|
||||
Commands::Ios(cli) => ios::command(cli)?,
|
||||
}
|
||||
|
193
tooling/cli/src/plugin/add.rs
Normal file
193
tooling/cli/src/plugin/add.rs
Normal file
@ -0,0 +1,193 @@
|
||||
// Copyright 2019-2023 Tauri Programme within The Commons Conservancy
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
use anyhow::Context;
|
||||
use clap::Parser;
|
||||
|
||||
use crate::{
|
||||
helpers::{
|
||||
app_paths::{app_dir, tauri_dir},
|
||||
cross_command,
|
||||
npm::PackageManager,
|
||||
},
|
||||
Result,
|
||||
};
|
||||
|
||||
use std::{collections::HashMap, process::Command};
|
||||
|
||||
#[derive(Debug, Parser)]
|
||||
#[clap(about = "Installs a plugin on the project")]
|
||||
pub struct Options {
|
||||
/// The plugin to add.
|
||||
plugin: String,
|
||||
/// Git tag to use.
|
||||
#[clap(short, long)]
|
||||
tag: Option<String>,
|
||||
/// Git rev to use.
|
||||
#[clap(short, long)]
|
||||
rev: Option<String>,
|
||||
/// Git branch to use.
|
||||
#[clap(short, long)]
|
||||
branch: Option<String>,
|
||||
}
|
||||
|
||||
pub fn command(options: Options) -> Result<()> {
|
||||
let plugin = options.plugin;
|
||||
let crate_name = format!("tauri-plugin-{plugin}");
|
||||
let npm_name = format!("@tauri-apps/plugin-{plugin}");
|
||||
|
||||
let mut plugins = plugins();
|
||||
let metadata = plugins.remove(plugin.as_str()).unwrap_or_default();
|
||||
|
||||
let mut cargo = Command::new("cargo");
|
||||
cargo.current_dir(tauri_dir()).arg("add").arg(&crate_name);
|
||||
|
||||
if options.tag.is_some() || options.rev.is_some() || options.branch.is_some() {
|
||||
cargo
|
||||
.arg("--git")
|
||||
.arg("https://github.com/tauri-apps/plugins-workspace");
|
||||
}
|
||||
|
||||
if metadata.desktop_only {
|
||||
cargo
|
||||
.arg("--target")
|
||||
.arg(r#"cfg(not(any(target_os = "android", target_os = "ios")))"#);
|
||||
}
|
||||
|
||||
let npm_spec = match (options.tag, options.rev, options.branch) {
|
||||
(Some(tag), None, None) => {
|
||||
cargo.args(["--tag", &tag]);
|
||||
format!("tauri-apps/tauri-plugin-{plugin}#{tag}")
|
||||
}
|
||||
(None, Some(rev), None) => {
|
||||
cargo.args(["--rev", &rev]);
|
||||
format!("tauri-apps/tauri-plugin-{plugin}#{rev}")
|
||||
}
|
||||
(None, None, Some(branch)) => {
|
||||
cargo.args(["--branch", &branch]);
|
||||
format!("tauri-apps/tauri-plugin-{plugin}#{branch}")
|
||||
}
|
||||
(None, None, None) => npm_name,
|
||||
_ => anyhow::bail!("Only one of --tag, --rev and --branch can be specified"),
|
||||
};
|
||||
|
||||
log::info!("Installing Cargo dependency {crate_name}...");
|
||||
let status = cargo.status().context("failed to run `cargo add`")?;
|
||||
if !status.success() {
|
||||
anyhow::bail!("Failed to install Cargo dependency");
|
||||
}
|
||||
|
||||
if !metadata.rust_only {
|
||||
if let Some(manager) = std::panic::catch_unwind(app_dir)
|
||||
.map(Some)
|
||||
.unwrap_or_default()
|
||||
.map(PackageManager::from_project)
|
||||
.and_then(|managers| managers.into_iter().next())
|
||||
{
|
||||
let mut cmd = match manager {
|
||||
PackageManager::Npm => cross_command("npm"),
|
||||
PackageManager::Pnpm => cross_command("pnpm"),
|
||||
PackageManager::Yarn => cross_command("yarn"),
|
||||
PackageManager::YarnBerry => cross_command("yarn"),
|
||||
};
|
||||
|
||||
cmd.arg("add").arg(&npm_spec);
|
||||
|
||||
log::info!("Installing NPM dependency {npm_spec}...");
|
||||
let status = cmd
|
||||
.status()
|
||||
.with_context(|| format!("failed to run {manager}"))?;
|
||||
if !status.success() {
|
||||
anyhow::bail!("Failed to install NPM dependency");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let rust_code = if metadata.builder {
|
||||
if metadata.desktop_only {
|
||||
format!(
|
||||
r#"tauri::Builder::default()
|
||||
.setup(|app| {{
|
||||
#[cfg(desktop)]
|
||||
app.handle().plugin(tauri_plugin_{plugin}::Builder::new().build());
|
||||
Ok(())
|
||||
}})
|
||||
"#,
|
||||
)
|
||||
} else {
|
||||
format!(
|
||||
r#"tauri::Builder::default()
|
||||
.setup(|app| {{
|
||||
app.handle().plugin(tauri_plugin_{plugin}::Builder::new().build());
|
||||
Ok(())
|
||||
}})
|
||||
"#,
|
||||
)
|
||||
}
|
||||
} else if metadata.desktop_only {
|
||||
format!(
|
||||
r#"tauri::Builder::default()
|
||||
.setup(|app| {{
|
||||
#[cfg(desktop)]
|
||||
app.handle().plugin(tauri_plugin_{plugin}::init());
|
||||
Ok(())
|
||||
}})
|
||||
"#,
|
||||
)
|
||||
} else {
|
||||
format!(
|
||||
r#"tauri::Builder::default().plugin(tauri_plugin_{plugin}::init())
|
||||
"#,
|
||||
)
|
||||
};
|
||||
|
||||
println!("You must enable the plugin in your Rust code:\n\n{rust_code}");
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
struct PluginMetadata {
|
||||
desktop_only: bool,
|
||||
rust_only: bool,
|
||||
builder: bool,
|
||||
}
|
||||
|
||||
// known plugins with particular cases
|
||||
fn plugins() -> HashMap<&'static str, PluginMetadata> {
|
||||
let mut plugins: HashMap<&'static str, PluginMetadata> = HashMap::new();
|
||||
|
||||
// desktop-only
|
||||
for p in [
|
||||
"authenticator",
|
||||
"cli",
|
||||
"global-shortcut",
|
||||
"updater",
|
||||
"window-state",
|
||||
] {
|
||||
plugins.entry(p).or_default().desktop_only = true;
|
||||
}
|
||||
|
||||
// uses builder pattern
|
||||
for p in [
|
||||
"global-shortcut",
|
||||
"localhost",
|
||||
"log",
|
||||
"sql",
|
||||
"store",
|
||||
"stronghold",
|
||||
"updater",
|
||||
"window-state",
|
||||
] {
|
||||
plugins.entry(p).or_default().builder = true;
|
||||
}
|
||||
|
||||
// rust-only
|
||||
#[allow(clippy::single_element_loop)]
|
||||
for p in ["localhost"] {
|
||||
plugins.entry(p).or_default().rust_only = true;
|
||||
}
|
||||
|
||||
plugins
|
||||
}
|
Loading…
Reference in New Issue
Block a user