refactor(cli): enhance plugin subcommand, closes #7749 (#7990)

Co-authored-by: Lucas Fernandes Nogueira <lucas@tauri.app>
This commit is contained in:
Amr Bashir 2023-10-11 18:34:33 +03:00 committed by GitHub
parent 713f84db2b
commit 4caa1cca99
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 189 additions and 46 deletions

View File

@ -0,0 +1,12 @@
---
'tauri-cli': 'patch:breaking'
'@tauri-apps/cli': 'patch:breaking'
---
The `tauri plugin` subcommand is receving a couple of consitency and quality of life improvements:
- Renamed `tauri plugin android/ios add` command to `tauri plugin android/ios init` to match the `tauri plugin init` command.
- Removed the `-n/--name` argument from the `tauri plugin init`, `tauri plugin android/ios init`, and is now parsed from the first positional argument.
- Added `tauri plugin new` to create a plugin in a new directory.
- Changed `tauri plugin init` to initalize a plugin in an existing directory (defaults to current directory) instead of creating a new one.
- Changed `tauri plugin init` to NOT generate mobile projects by default, you can opt-in to generate them using `--android` and `--ios` flags or `--mobile` flag or initalize them later using `tauri plugin android/ios init`.

View File

@ -2,6 +2,8 @@
// SPDX-License-Identifier: Apache-2.0
// SPDX-License-Identifier: MIT
use std::path::Path;
use clap::{Parser, Subcommand};
use crate::Result;
@ -9,6 +11,7 @@ use crate::Result;
mod android;
mod init;
mod ios;
mod new;
#[derive(Parser)]
#[clap(
@ -25,6 +28,7 @@ pub struct Cli {
#[derive(Subcommand)]
enum Commands {
New(new::Options),
Init(init::Options),
Android(android::Cli),
Ios(ios::Cli),
@ -32,6 +36,7 @@ enum Commands {
pub fn command(cli: Cli) -> Result<()> {
match cli.command {
Commands::New(options) => new::command(options)?,
Commands::Init(options) => init::command(options)?,
Commands::Android(cli) => android::command(cli)?,
Commands::Ios(cli) => ios::command(cli)?,
@ -39,3 +44,30 @@ pub fn command(cli: Cli) -> Result<()> {
Ok(())
}
fn infer_plugin_name<P: AsRef<Path>>(directory: P) -> Result<String> {
let dir = directory.as_ref();
let cargo_toml_path = dir.join("Cargo.toml");
let name = if cargo_toml_path.exists() {
let contents = std::fs::read(cargo_toml_path)?;
let cargo_toml: toml::Value = toml::from_slice(&contents)?;
cargo_toml
.get("package")
.and_then(|v| v.get("name"))
.map(|v| v.as_str().unwrap_or_default())
.unwrap_or_default()
.to_string()
} else {
dir
.file_name()
.unwrap_or_default()
.to_string_lossy()
.to_string()
};
Ok(
name
.strip_prefix("tauri-plugin-")
.unwrap_or(&name)
.to_string(),
)
}

View File

@ -28,15 +28,15 @@ pub struct Cli {
#[derive(Subcommand)]
enum Commands {
Add(AddOptions),
Init(InitOptions),
}
#[derive(Debug, Parser)]
#[clap(about = "Adds the Android project to an existing Tauri plugin")]
pub struct AddOptions {
#[clap(about = "Initializes the Android project for an existing Tauri plugin")]
pub struct InitOptions {
/// Name of your Tauri plugin. Must match the current plugin's name.
#[clap(short = 'n', long = "name")]
plugin_name: String,
/// If not specified, it will be infered from the current directory.
plugin_name: Option<String>,
/// The output directory.
#[clap(short, long)]
#[clap(default_value_t = current_dir().expect("failed to read cwd").to_string_lossy().into_owned())]
@ -45,7 +45,12 @@ pub struct AddOptions {
pub fn command(cli: Cli) -> Result<()> {
match cli.command {
Commands::Add(options) => {
Commands::Init(options) => {
let plugin_name = match options.plugin_name {
None => super::infer_plugin_name(std::env::current_dir()?)?,
Some(name) => name,
};
let out_dir = PathBuf::from(options.out_dir);
if out_dir.join("android").exists() {
return Err(anyhow::anyhow!("android folder already exists"));
@ -53,7 +58,7 @@ pub fn command(cli: Cli) -> Result<()> {
let plugin_id = super::init::request_input(
"What should be the Android Package ID for your plugin?",
Some(format!("com.plugin.{}", options.plugin_name)),
Some(format!("com.plugin.{}", plugin_name)),
false,
false,
)?
@ -62,7 +67,7 @@ pub fn command(cli: Cli) -> Result<()> {
let handlebars = Handlebars::new();
let mut data = BTreeMap::new();
super::init::plugin_name_data(&mut data, &options.plugin_name);
super::init::plugin_name_data(&mut data, &plugin_name);
let mut created_dirs = Vec::new();
template::render_with_generator(
@ -114,7 +119,7 @@ pub fn init<R: Runtime>() -> TauriPlugin<R> {{
.build()
}}
"#,
name = options.plugin_name,
name = plugin_name,
identifier = plugin_id
);

View File

@ -11,7 +11,7 @@ use anyhow::Context;
use clap::Parser;
use dialoguer::Input;
use handlebars::{to_json, Handlebars};
use heck::{AsKebabCase, ToKebabCase, ToPascalCase, ToSnakeCase};
use heck::{ToKebabCase, ToPascalCase, ToSnakeCase};
use include_dir::{include_dir, Dir};
use log::warn;
use std::{
@ -29,25 +29,34 @@ pub const TEMPLATE_DIR: Dir<'_> = include_dir!("templates/plugin");
#[derive(Debug, Parser)]
#[clap(about = "Initializes a Tauri plugin project")]
pub struct Options {
/// Name of your Tauri plugin
#[clap(short = 'n', long = "name")]
plugin_name: String,
/// Name of your Tauri plugin.
/// If not specified, it will be infered from the current directory.
pub(crate) plugin_name: Option<String>,
/// Initializes a Tauri plugin without the TypeScript API
#[clap(long)]
no_api: bool,
pub(crate) no_api: bool,
/// Initializes a Tauri core plugin (internal usage)
#[clap(long, hide(true))]
tauri: bool,
pub(crate) tauri: bool,
/// Set target directory for init
#[clap(short, long)]
#[clap(default_value_t = current_dir().expect("failed to read cwd").display().to_string())]
directory: String,
pub(crate) directory: String,
/// Path of the Tauri project to use (relative to the cwd)
#[clap(short, long)]
tauri_path: Option<PathBuf>,
pub(crate) tauri_path: Option<PathBuf>,
/// Author name
#[clap(short, long)]
author: Option<String>,
pub(crate) author: Option<String>,
/// Whether to initialize an Android project for the plugin.
#[clap(long)]
pub(crate) android: bool,
/// Whether to initialize an iOS project for the plugin.
#[clap(long)]
pub(crate) ios: bool,
/// Whether to initialize Android and iOS projects for the plugin.
#[clap(long)]
pub(crate) mobile: bool,
}
impl Options {
@ -64,12 +73,15 @@ impl Options {
pub fn command(mut options: Options) -> Result<()> {
options.load();
let template_target_path = PathBuf::from(options.directory).join(format!(
"tauri-plugin-{}",
AsKebabCase(&options.plugin_name)
));
let plugin_name = match options.plugin_name {
None => super::infer_plugin_name(&options.directory)?,
Some(name) => name,
};
let template_target_path = PathBuf::from(options.directory);
let metadata = crates_metadata()?;
if template_target_path.exists() {
if std::fs::read_dir(&template_target_path)?.count() > 0 {
warn!("Plugin dir ({:?}) not empty.", template_target_path);
} else {
let (tauri_dep, tauri_example_dep, tauri_build_dep) =
@ -101,7 +113,7 @@ pub fn command(mut options: Options) -> Result<()> {
handlebars.register_escape_fn(handlebars::no_escape);
let mut data = BTreeMap::new();
plugin_name_data(&mut data, &options.plugin_name);
plugin_name_data(&mut data, &plugin_name);
data.insert("tauri_dep", to_json(tauri_dep));
data.insert("tauri_example_dep", to_json(tauri_example_dep));
data.insert("tauri_build_dep", to_json(tauri_build_dep));
@ -120,15 +132,20 @@ pub fn command(mut options: Options) -> Result<()> {
);
}
let plugin_id = request_input(
"What should be the Android Package ID for your plugin?",
Some(format!("com.plugin.{}", options.plugin_name)),
false,
false,
)?
.unwrap();
let plugin_id = if options.android || options.mobile {
let plugin_id = request_input(
"What should be the Android Package ID for your plugin?",
Some(format!("com.plugin.{}", plugin_name)),
false,
false,
)?
.unwrap();
data.insert("android_package_id", to_json(&plugin_id));
data.insert("android_package_id", to_json(&plugin_id));
Some(plugin_id)
} else {
None
};
let mut created_dirs = Vec::new();
template::render_with_generator(
@ -157,13 +174,18 @@ pub fn command(mut options: Options) -> Result<()> {
}
}
"android" => {
return generate_android_out_file(
&path,
&template_target_path,
&plugin_id.replace('.', "/"),
&mut created_dirs,
);
if options.android || options.mobile {
return generate_android_out_file(
&path,
&template_target_path,
&plugin_id.as_ref().unwrap().replace('.', "/"),
&mut created_dirs,
);
} else {
return Ok(None);
}
}
"ios" if !(options.ios || options.mobile) => return Ok(None),
"webview-dist" | "webview-src" | "package.json" => {
if options.no_api {
return Ok(None);

View File

@ -29,15 +29,15 @@ pub struct Cli {
#[derive(Subcommand)]
enum Commands {
Add(AddOptions),
Init(InitOptions),
}
#[derive(Debug, Parser)]
#[clap(about = "Adds the iOS project to an existing Tauri plugin")]
pub struct AddOptions {
#[clap(about = "Initializes the iOS project for an existing Tauri plugin")]
pub struct InitOptions {
/// Name of your Tauri plugin. Must match the current plugin's name.
#[clap(short = 'n', long = "name")]
plugin_name: String,
/// If not specified, it will be infered from the current directory.
plugin_name: Option<String>,
/// The output directory.
#[clap(short, long)]
#[clap(default_value_t = current_dir().expect("failed to read cwd").to_string_lossy().into_owned())]
@ -46,7 +46,12 @@ pub struct AddOptions {
pub fn command(cli: Cli) -> Result<()> {
match cli.command {
Commands::Add(options) => {
Commands::Init(options) => {
let plugin_name = match options.plugin_name {
None => super::infer_plugin_name(std::env::current_dir()?)?,
Some(name) => name,
};
let out_dir = PathBuf::from(options.out_dir);
if out_dir.join("ios").exists() {
return Err(anyhow::anyhow!("ios folder already exists"));
@ -55,7 +60,7 @@ pub fn command(cli: Cli) -> Result<()> {
let handlebars = Handlebars::new();
let mut data = BTreeMap::new();
super::init::plugin_name_data(&mut data, &options.plugin_name);
super::init::plugin_name_data(&mut data, &plugin_name);
let mut created_dirs = Vec::new();
template::render_with_generator(
@ -111,7 +116,7 @@ pub fn init<R: Runtime>() -> TauriPlugin<R> {{
.build()
}}
"#,
name = options.plugin_name,
name = plugin_name,
);
log::info!("iOS project added");

View File

@ -0,0 +1,67 @@
// 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 std::path::PathBuf;
#[derive(Debug, Parser)]
#[clap(about = "Initializes a Tauri plugin project")]
pub struct Options {
/// Name of your Tauri plugin
plugin_name: String,
/// Initializes a Tauri plugin without the TypeScript API
#[clap(long)]
no_api: bool,
/// Initializes a Tauri core plugin (internal usage)
#[clap(long, hide(true))]
tauri: bool,
/// Set target directory for init
#[clap(short, long)]
directory: Option<String>,
/// Path of the Tauri project to use (relative to the cwd)
#[clap(short, long)]
tauri_path: Option<PathBuf>,
/// Author name
#[clap(short, long)]
author: Option<String>,
/// Whether to initialize an Android project for the plugin.
#[clap(long)]
android: bool,
/// Whether to initialize an iOS project for the plugin.
#[clap(long)]
ios: bool,
/// Whether to initialize Android and iOS projects for the plugin.
#[clap(long)]
mobile: bool,
}
impl From<Options> for super::init::Options {
fn from(o: Options) -> Self {
Self {
plugin_name: Some(o.plugin_name),
no_api: o.no_api,
tauri: o.tauri,
directory: o.directory.unwrap(),
tauri_path: o.tauri_path,
author: o.author,
android: o.android,
ios: o.ios,
mobile: o.mobile,
}
}
}
pub fn command(mut options: Options) -> Result<()> {
let cwd = std::env::current_dir()?;
if let Some(dir) = &options.directory {
std::fs::create_dir_all(cwd.join(dir))?;
} else {
let target = cwd.join(format!("tauri-plugin-{}", options.plugin_name));
std::fs::create_dir_all(&target)?;
options.directory.replace(target.display().to_string());
}
super::init::command(options.into())
}