Skip to content

Commit 4caa1cc

Browse files
refactor(cli): enhance plugin subcommand, closes #7749 (#7990)
Co-authored-by: Lucas Fernandes Nogueira <lucas@tauri.app>
1 parent 713f84d commit 4caa1cc

File tree

6 files changed

+189
-46
lines changed

6 files changed

+189
-46
lines changed

.changes/cli-plugin-init.md

+12
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
---
2+
'tauri-cli': 'patch:breaking'
3+
'@tauri-apps/cli': 'patch:breaking'
4+
---
5+
6+
The `tauri plugin` subcommand is receving a couple of consitency and quality of life improvements:
7+
8+
- Renamed `tauri plugin android/ios add` command to `tauri plugin android/ios init` to match the `tauri plugin init` command.
9+
- Removed the `-n/--name` argument from the `tauri plugin init`, `tauri plugin android/ios init`, and is now parsed from the first positional argument.
10+
- Added `tauri plugin new` to create a plugin in a new directory.
11+
- Changed `tauri plugin init` to initalize a plugin in an existing directory (defaults to current directory) instead of creating a new one.
12+
- 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`.

tooling/cli/src/plugin.rs

+32
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,16 @@
22
// SPDX-License-Identifier: Apache-2.0
33
// SPDX-License-Identifier: MIT
44

5+
use std::path::Path;
6+
57
use clap::{Parser, Subcommand};
68

79
use crate::Result;
810

911
mod android;
1012
mod init;
1113
mod ios;
14+
mod new;
1215

1316
#[derive(Parser)]
1417
#[clap(
@@ -25,17 +28,46 @@ pub struct Cli {
2528

2629
#[derive(Subcommand)]
2730
enum Commands {
31+
New(new::Options),
2832
Init(init::Options),
2933
Android(android::Cli),
3034
Ios(ios::Cli),
3135
}
3236

3337
pub fn command(cli: Cli) -> Result<()> {
3438
match cli.command {
39+
Commands::New(options) => new::command(options)?,
3540
Commands::Init(options) => init::command(options)?,
3641
Commands::Android(cli) => android::command(cli)?,
3742
Commands::Ios(cli) => ios::command(cli)?,
3843
}
3944

4045
Ok(())
4146
}
47+
48+
fn infer_plugin_name<P: AsRef<Path>>(directory: P) -> Result<String> {
49+
let dir = directory.as_ref();
50+
let cargo_toml_path = dir.join("Cargo.toml");
51+
let name = if cargo_toml_path.exists() {
52+
let contents = std::fs::read(cargo_toml_path)?;
53+
let cargo_toml: toml::Value = toml::from_slice(&contents)?;
54+
cargo_toml
55+
.get("package")
56+
.and_then(|v| v.get("name"))
57+
.map(|v| v.as_str().unwrap_or_default())
58+
.unwrap_or_default()
59+
.to_string()
60+
} else {
61+
dir
62+
.file_name()
63+
.unwrap_or_default()
64+
.to_string_lossy()
65+
.to_string()
66+
};
67+
Ok(
68+
name
69+
.strip_prefix("tauri-plugin-")
70+
.unwrap_or(&name)
71+
.to_string(),
72+
)
73+
}

tooling/cli/src/plugin/android.rs

+14-9
Original file line numberDiff line numberDiff line change
@@ -28,15 +28,15 @@ pub struct Cli {
2828

2929
#[derive(Subcommand)]
3030
enum Commands {
31-
Add(AddOptions),
31+
Init(InitOptions),
3232
}
3333

3434
#[derive(Debug, Parser)]
35-
#[clap(about = "Adds the Android project to an existing Tauri plugin")]
36-
pub struct AddOptions {
35+
#[clap(about = "Initializes the Android project for an existing Tauri plugin")]
36+
pub struct InitOptions {
3737
/// Name of your Tauri plugin. Must match the current plugin's name.
38-
#[clap(short = 'n', long = "name")]
39-
plugin_name: String,
38+
/// If not specified, it will be infered from the current directory.
39+
plugin_name: Option<String>,
4040
/// The output directory.
4141
#[clap(short, long)]
4242
#[clap(default_value_t = current_dir().expect("failed to read cwd").to_string_lossy().into_owned())]
@@ -45,15 +45,20 @@ pub struct AddOptions {
4545

4646
pub fn command(cli: Cli) -> Result<()> {
4747
match cli.command {
48-
Commands::Add(options) => {
48+
Commands::Init(options) => {
49+
let plugin_name = match options.plugin_name {
50+
None => super::infer_plugin_name(std::env::current_dir()?)?,
51+
Some(name) => name,
52+
};
53+
4954
let out_dir = PathBuf::from(options.out_dir);
5055
if out_dir.join("android").exists() {
5156
return Err(anyhow::anyhow!("android folder already exists"));
5257
}
5358

5459
let plugin_id = super::init::request_input(
5560
"What should be the Android Package ID for your plugin?",
56-
Some(format!("com.plugin.{}", options.plugin_name)),
61+
Some(format!("com.plugin.{}", plugin_name)),
5762
false,
5863
false,
5964
)?
@@ -62,7 +67,7 @@ pub fn command(cli: Cli) -> Result<()> {
6267
let handlebars = Handlebars::new();
6368

6469
let mut data = BTreeMap::new();
65-
super::init::plugin_name_data(&mut data, &options.plugin_name);
70+
super::init::plugin_name_data(&mut data, &plugin_name);
6671

6772
let mut created_dirs = Vec::new();
6873
template::render_with_generator(
@@ -114,7 +119,7 @@ pub fn init<R: Runtime>() -> TauriPlugin<R> {{
114119
.build()
115120
}}
116121
"#,
117-
name = options.plugin_name,
122+
name = plugin_name,
118123
identifier = plugin_id
119124
);
120125

tooling/cli/src/plugin/init.rs

+51-29
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ use anyhow::Context;
1111
use clap::Parser;
1212
use dialoguer::Input;
1313
use handlebars::{to_json, Handlebars};
14-
use heck::{AsKebabCase, ToKebabCase, ToPascalCase, ToSnakeCase};
14+
use heck::{ToKebabCase, ToPascalCase, ToSnakeCase};
1515
use include_dir::{include_dir, Dir};
1616
use log::warn;
1717
use std::{
@@ -29,25 +29,34 @@ pub const TEMPLATE_DIR: Dir<'_> = include_dir!("templates/plugin");
2929
#[derive(Debug, Parser)]
3030
#[clap(about = "Initializes a Tauri plugin project")]
3131
pub struct Options {
32-
/// Name of your Tauri plugin
33-
#[clap(short = 'n', long = "name")]
34-
plugin_name: String,
32+
/// Name of your Tauri plugin.
33+
/// If not specified, it will be infered from the current directory.
34+
pub(crate) plugin_name: Option<String>,
3535
/// Initializes a Tauri plugin without the TypeScript API
3636
#[clap(long)]
37-
no_api: bool,
37+
pub(crate) no_api: bool,
3838
/// Initializes a Tauri core plugin (internal usage)
3939
#[clap(long, hide(true))]
40-
tauri: bool,
40+
pub(crate) tauri: bool,
4141
/// Set target directory for init
4242
#[clap(short, long)]
4343
#[clap(default_value_t = current_dir().expect("failed to read cwd").display().to_string())]
44-
directory: String,
44+
pub(crate) directory: String,
4545
/// Path of the Tauri project to use (relative to the cwd)
4646
#[clap(short, long)]
47-
tauri_path: Option<PathBuf>,
47+
pub(crate) tauri_path: Option<PathBuf>,
4848
/// Author name
4949
#[clap(short, long)]
50-
author: Option<String>,
50+
pub(crate) author: Option<String>,
51+
/// Whether to initialize an Android project for the plugin.
52+
#[clap(long)]
53+
pub(crate) android: bool,
54+
/// Whether to initialize an iOS project for the plugin.
55+
#[clap(long)]
56+
pub(crate) ios: bool,
57+
/// Whether to initialize Android and iOS projects for the plugin.
58+
#[clap(long)]
59+
pub(crate) mobile: bool,
5160
}
5261

5362
impl Options {
@@ -64,12 +73,15 @@ impl Options {
6473

6574
pub fn command(mut options: Options) -> Result<()> {
6675
options.load();
67-
let template_target_path = PathBuf::from(options.directory).join(format!(
68-
"tauri-plugin-{}",
69-
AsKebabCase(&options.plugin_name)
70-
));
76+
77+
let plugin_name = match options.plugin_name {
78+
None => super::infer_plugin_name(&options.directory)?,
79+
Some(name) => name,
80+
};
81+
82+
let template_target_path = PathBuf::from(options.directory);
7183
let metadata = crates_metadata()?;
72-
if template_target_path.exists() {
84+
if std::fs::read_dir(&template_target_path)?.count() > 0 {
7385
warn!("Plugin dir ({:?}) not empty.", template_target_path);
7486
} else {
7587
let (tauri_dep, tauri_example_dep, tauri_build_dep) =
@@ -101,7 +113,7 @@ pub fn command(mut options: Options) -> Result<()> {
101113
handlebars.register_escape_fn(handlebars::no_escape);
102114

103115
let mut data = BTreeMap::new();
104-
plugin_name_data(&mut data, &options.plugin_name);
116+
plugin_name_data(&mut data, &plugin_name);
105117
data.insert("tauri_dep", to_json(tauri_dep));
106118
data.insert("tauri_example_dep", to_json(tauri_example_dep));
107119
data.insert("tauri_build_dep", to_json(tauri_build_dep));
@@ -120,15 +132,20 @@ pub fn command(mut options: Options) -> Result<()> {
120132
);
121133
}
122134

123-
let plugin_id = request_input(
124-
"What should be the Android Package ID for your plugin?",
125-
Some(format!("com.plugin.{}", options.plugin_name)),
126-
false,
127-
false,
128-
)?
129-
.unwrap();
135+
let plugin_id = if options.android || options.mobile {
136+
let plugin_id = request_input(
137+
"What should be the Android Package ID for your plugin?",
138+
Some(format!("com.plugin.{}", plugin_name)),
139+
false,
140+
false,
141+
)?
142+
.unwrap();
130143

131-
data.insert("android_package_id", to_json(&plugin_id));
144+
data.insert("android_package_id", to_json(&plugin_id));
145+
Some(plugin_id)
146+
} else {
147+
None
148+
};
132149

133150
let mut created_dirs = Vec::new();
134151
template::render_with_generator(
@@ -157,13 +174,18 @@ pub fn command(mut options: Options) -> Result<()> {
157174
}
158175
}
159176
"android" => {
160-
return generate_android_out_file(
161-
&path,
162-
&template_target_path,
163-
&plugin_id.replace('.', "/"),
164-
&mut created_dirs,
165-
);
177+
if options.android || options.mobile {
178+
return generate_android_out_file(
179+
&path,
180+
&template_target_path,
181+
&plugin_id.as_ref().unwrap().replace('.', "/"),
182+
&mut created_dirs,
183+
);
184+
} else {
185+
return Ok(None);
186+
}
166187
}
188+
"ios" if !(options.ios || options.mobile) => return Ok(None),
167189
"webview-dist" | "webview-src" | "package.json" => {
168190
if options.no_api {
169191
return Ok(None);

tooling/cli/src/plugin/ios.rs

+13-8
Original file line numberDiff line numberDiff line change
@@ -29,15 +29,15 @@ pub struct Cli {
2929

3030
#[derive(Subcommand)]
3131
enum Commands {
32-
Add(AddOptions),
32+
Init(InitOptions),
3333
}
3434

3535
#[derive(Debug, Parser)]
36-
#[clap(about = "Adds the iOS project to an existing Tauri plugin")]
37-
pub struct AddOptions {
36+
#[clap(about = "Initializes the iOS project for an existing Tauri plugin")]
37+
pub struct InitOptions {
3838
/// Name of your Tauri plugin. Must match the current plugin's name.
39-
#[clap(short = 'n', long = "name")]
40-
plugin_name: String,
39+
/// If not specified, it will be infered from the current directory.
40+
plugin_name: Option<String>,
4141
/// The output directory.
4242
#[clap(short, long)]
4343
#[clap(default_value_t = current_dir().expect("failed to read cwd").to_string_lossy().into_owned())]
@@ -46,7 +46,12 @@ pub struct AddOptions {
4646

4747
pub fn command(cli: Cli) -> Result<()> {
4848
match cli.command {
49-
Commands::Add(options) => {
49+
Commands::Init(options) => {
50+
let plugin_name = match options.plugin_name {
51+
None => super::infer_plugin_name(std::env::current_dir()?)?,
52+
Some(name) => name,
53+
};
54+
5055
let out_dir = PathBuf::from(options.out_dir);
5156
if out_dir.join("ios").exists() {
5257
return Err(anyhow::anyhow!("ios folder already exists"));
@@ -55,7 +60,7 @@ pub fn command(cli: Cli) -> Result<()> {
5560
let handlebars = Handlebars::new();
5661

5762
let mut data = BTreeMap::new();
58-
super::init::plugin_name_data(&mut data, &options.plugin_name);
63+
super::init::plugin_name_data(&mut data, &plugin_name);
5964

6065
let mut created_dirs = Vec::new();
6166
template::render_with_generator(
@@ -111,7 +116,7 @@ pub fn init<R: Runtime>() -> TauriPlugin<R> {{
111116
.build()
112117
}}
113118
"#,
114-
name = options.plugin_name,
119+
name = plugin_name,
115120
);
116121

117122
log::info!("iOS project added");

tooling/cli/src/plugin/new.rs

+67
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
// Copyright 2019-2023 Tauri Programme within The Commons Conservancy
2+
// SPDX-License-Identifier: Apache-2.0
3+
// SPDX-License-Identifier: MIT
4+
5+
use crate::Result;
6+
use clap::Parser;
7+
use std::path::PathBuf;
8+
9+
#[derive(Debug, Parser)]
10+
#[clap(about = "Initializes a Tauri plugin project")]
11+
pub struct Options {
12+
/// Name of your Tauri plugin
13+
plugin_name: String,
14+
/// Initializes a Tauri plugin without the TypeScript API
15+
#[clap(long)]
16+
no_api: bool,
17+
/// Initializes a Tauri core plugin (internal usage)
18+
#[clap(long, hide(true))]
19+
tauri: bool,
20+
/// Set target directory for init
21+
#[clap(short, long)]
22+
directory: Option<String>,
23+
/// Path of the Tauri project to use (relative to the cwd)
24+
#[clap(short, long)]
25+
tauri_path: Option<PathBuf>,
26+
/// Author name
27+
#[clap(short, long)]
28+
author: Option<String>,
29+
/// Whether to initialize an Android project for the plugin.
30+
#[clap(long)]
31+
android: bool,
32+
/// Whether to initialize an iOS project for the plugin.
33+
#[clap(long)]
34+
ios: bool,
35+
/// Whether to initialize Android and iOS projects for the plugin.
36+
#[clap(long)]
37+
mobile: bool,
38+
}
39+
40+
impl From<Options> for super::init::Options {
41+
fn from(o: Options) -> Self {
42+
Self {
43+
plugin_name: Some(o.plugin_name),
44+
no_api: o.no_api,
45+
tauri: o.tauri,
46+
directory: o.directory.unwrap(),
47+
tauri_path: o.tauri_path,
48+
author: o.author,
49+
android: o.android,
50+
ios: o.ios,
51+
mobile: o.mobile,
52+
}
53+
}
54+
}
55+
56+
pub fn command(mut options: Options) -> Result<()> {
57+
let cwd = std::env::current_dir()?;
58+
if let Some(dir) = &options.directory {
59+
std::fs::create_dir_all(cwd.join(dir))?;
60+
} else {
61+
let target = cwd.join(format!("tauri-plugin-{}", options.plugin_name));
62+
std::fs::create_dir_all(&target)?;
63+
options.directory.replace(target.display().to_string());
64+
}
65+
66+
super::init::command(options.into())
67+
}

0 commit comments

Comments
 (0)