wasmer_cli/utils/
prompts.rs1use anyhow::Context;
2use colored::Colorize;
3use dialoguer::{Select, theme::ColorfulTheme};
4use wasmer_backend_api::WasmerClient;
5use wasmer_config::package::NamedPackageIdent;
6
7pub fn prompt_for_ident(message: &str, default: Option<&str>) -> Result<String, anyhow::Error> {
8 loop {
9 let theme = ColorfulTheme::default();
10 let diag = dialoguer::Input::with_theme(&theme)
11 .with_prompt(message)
12 .with_initial_text(default.unwrap_or_default());
13
14 let raw: String = diag.interact_text()?;
19 let val = raw.trim();
20 if !val.is_empty() {
21 break Ok(val.to_string());
22 }
23 }
24}
25
26pub fn prompt_for_app_ident(message: &str, default: Option<&str>) -> Result<String, anyhow::Error> {
32 loop {
33 let theme = ColorfulTheme::default();
34 let diag = dialoguer::Input::with_theme(&theme)
35 .with_prompt(message)
36 .with_initial_text(default.unwrap_or_default());
37
38 let raw: String = diag.interact_text()?;
39 let val = raw.trim();
40 if val.is_empty() {
41 continue;
42 }
43 if val.contains('.') || val.contains(' ') {
44 eprintln!("The name must not contain dots or spaces. Please try again.");
45 continue;
46 }
47 return Ok(val.to_string());
48 }
49}
50
51pub fn prompt_for_package_ident(
55 message: &str,
56 default: Option<&str>,
57) -> Result<NamedPackageIdent, anyhow::Error> {
58 loop {
59 let theme = ColorfulTheme::default();
60 let raw: String = dialoguer::Input::with_theme(&theme)
61 .with_prompt(message)
62 .with_initial_text(default.unwrap_or_default())
63 .interact_text()
64 .context("could not read user input")?;
65
66 match raw.parse::<NamedPackageIdent>() {
67 Ok(p) => break Ok(p),
68 Err(err) => {
69 eprintln!("invalid package name: {err}");
70 }
71 }
72 }
73}
74
75pub enum PackageCheckMode {
77 MustExist,
79 #[allow(dead_code)]
81 MustNotExist,
82}
83
84pub fn prompt_for_package_version(
88 message: &str,
89 default: Option<&str>,
90) -> Result<semver::Version, anyhow::Error> {
91 loop {
92 let theme = ColorfulTheme::default();
93 let raw: String = dialoguer::Input::with_theme(&theme)
94 .with_prompt(message)
95 .with_initial_text(default.unwrap_or_default())
96 .interact_text()
97 .context("could not read user input")?;
98
99 match raw.parse::<semver::Version>() {
100 Ok(p) => break Ok(p),
101 Err(err) => {
102 eprintln!("invalid package version: {err}");
103 }
104 }
105 }
106}
107
108pub async fn prompt_for_package(
114 message: &str,
115 default: Option<&str>,
116 check: Option<PackageCheckMode>,
117 client: Option<&WasmerClient>,
118) -> Result<
119 (
120 NamedPackageIdent,
121 Option<wasmer_backend_api::types::Package>,
122 ),
123 anyhow::Error,
124> {
125 loop {
126 let ident = prompt_for_package_ident(message, default)?;
127
128 if let Some(check) = &check {
129 let api = client.expect("Check mode specified, but no API provided");
130
131 let pkg = if let Some(v) = ident.version_opt() {
132 wasmer_backend_api::query::get_package_version(
133 api,
134 ident.full_name(),
135 v.to_string(),
136 )
137 .await
138 .context("could not query backend for package")?
139 .map(|p| p.package)
140 } else {
141 wasmer_backend_api::query::get_package(api, ident.to_string())
142 .await
143 .context("could not query backend for package")?
144 };
145
146 match check {
147 PackageCheckMode::MustExist => {
148 if let Some(pkg) = pkg {
149 let mut ident = ident;
150 if let Some(v) = &pkg.last_version {
151 ident.tag =
152 Some(wasmer_config::package::Tag::VersionReq(v.version.parse()?));
153 }
154 break Ok((ident, Some(pkg)));
155 } else {
156 eprintln!("Package '{ident}' does not exist");
157 }
158 }
159 PackageCheckMode::MustNotExist => {
160 if pkg.is_none() {
161 break Ok((ident, None));
162 } else {
163 eprintln!("Package '{ident}' already exists");
164 }
165 }
166 }
167 } else {
168 break Ok((ident, None));
169 }
170 }
171}
172
173pub fn prompt_for_namespace(
180 message: &str,
181 default: Option<&str>,
182 user: Option<&wasmer_backend_api::types::UserWithNamespaces>,
183) -> Result<String, anyhow::Error> {
184 if let Some(user) = user {
185 let namespaces = user
186 .namespaces
187 .edges
188 .clone()
189 .into_iter()
190 .flatten()
191 .filter_map(|e| e.node)
192 .collect::<Vec<_>>();
193
194 let labels = [user.username.clone()]
195 .into_iter()
196 .chain(namespaces.iter().map(|ns| ns.global_name.clone()))
197 .collect::<Vec<_>>();
198
199 let selection_index = Select::with_theme(&ColorfulTheme::default())
200 .with_prompt(message)
201 .default(0)
202 .items(&labels)
203 .interact()
204 .context("could not read user input")?;
205
206 Ok(labels[selection_index].clone())
207 } else {
208 loop {
209 let theme = ColorfulTheme::default();
210 let value = dialoguer::Input::<String>::with_theme(&theme)
211 .with_prompt(message)
212 .with_initial_text(default.map(|x| x.trim().to_string()).unwrap_or_default())
213 .interact_text()
214 .context("could not read user input")?
215 .trim()
216 .to_string();
217
218 if !value.is_empty() {
219 break Ok(value);
220 }
221 }
222 }
223}
224
225#[allow(dead_code)]
228pub async fn prompt_new_app_name(
229 message: &str,
230 default: Option<&str>,
231 namespace: &str,
232 api: Option<&WasmerClient>,
233) -> Result<String, anyhow::Error> {
234 loop {
235 let ident = prompt_for_ident(message, default)?;
236
237 if ident.len() < 5 {
238 eprintln!(
239 "{}: Name is too short. It must be longer than 5 characters.",
240 "WARN".bold().yellow()
241 )
242 } else if let Some(api) = &api {
243 let app = wasmer_backend_api::query::get_app(api, namespace.to_string(), ident.clone())
244 .await?;
245 eprint!("Checking name availability... ");
246 if app.is_some() {
247 eprintln!(
248 "{}",
249 format!(
250 "app {} already exists in namespace {}",
251 ident.bold(),
252 namespace.bold()
253 )
254 .yellow()
255 );
256 } else {
257 eprintln!("{}", "available!".bold().green());
258 break Ok(ident);
259 }
260 }
261 }
262}
263
264#[allow(dead_code)]
267pub async fn prompt_new_app_alias(
268 message: &str,
269 default: Option<&str>,
270 api: Option<&WasmerClient>,
271) -> Result<String, anyhow::Error> {
272 loop {
273 let ident = prompt_for_ident(message, default)?;
274
275 if let Some(api) = &api {
276 let app = wasmer_backend_api::query::get_app_by_alias(api, ident.clone()).await?;
277 eprintln!("Checking name availability...");
278 if app.is_some() {
279 eprintln!(
280 "{}: alias '{}' already exists - pick a different name",
281 "WARN:".yellow(),
282 ident
283 );
284 } else {
285 break Ok(ident);
286 }
287 }
288 }
289}