1mod add;
3mod app;
4mod auth;
5#[cfg(target_os = "linux")]
6mod binfmt;
7mod cache;
8#[cfg(feature = "compiler")]
9mod compile;
10mod config;
11mod connect;
12mod container;
13mod cron;
14pub(crate) mod domain;
15mod gen_completions;
16mod gen_manpage;
17mod init;
18mod inspect;
19#[cfg(feature = "journal")]
20mod journal;
21pub(crate) mod namespace;
22mod package;
23mod run;
24mod self_update;
25pub mod ssh;
26mod validate;
27#[cfg(feature = "wast")]
28mod wast;
29use itertools::Itertools;
30use std::io::IsTerminal as _;
31use tokio::task::JoinHandle;
32
33#[cfg(target_os = "linux")]
34pub use binfmt::*;
35use clap::{CommandFactory, Parser};
36#[cfg(feature = "compiler")]
37pub use compile::*;
38#[cfg(feature = "wast")]
39pub use wast::*;
40
41#[cfg(feature = "journal")]
42pub use self::journal::*;
43pub use self::{
44 add::*, auth::*, cache::*, config::*, container::*, init::*, inspect::*, package::*,
45 publish::*, run::Run, self_update::*, validate::*,
46};
47use crate::error::PrettyError;
48use git_version::git_version;
49
50pub(crate) trait CliCommand {
52 type Output;
53
54 fn run(self) -> Result<(), anyhow::Error>;
55}
56
57#[async_trait::async_trait]
62pub(crate) trait AsyncCliCommand: Send + Sync {
63 type Output: Send + Sync;
64
65 async fn run_async(self) -> Result<Self::Output, anyhow::Error>;
66
67 fn setup(
68 &self,
69 done: tokio::sync::oneshot::Receiver<()>,
70 ) -> Option<JoinHandle<anyhow::Result<()>>> {
71 if std::io::stdin().is_terminal() {
72 return Some(tokio::task::spawn(async move {
73 tokio::select! {
74 _ = done => {}
75
76 _ = tokio::signal::ctrl_c() => {
77 let term = console::Term::stdout();
78 let _ = term.show_cursor();
79 #[cfg(target_os = "windows")]
81 std::process::exit(3);
82
83 #[cfg(not(target_os = "windows"))]
85 std::process::exit(130);
86 }
87 }
88
89 Ok::<(), anyhow::Error>(())
90 }));
91 }
92
93 None
94 }
95}
96
97impl<O: Send + Sync, C: AsyncCliCommand<Output = O>> CliCommand for C {
98 type Output = O;
99
100 fn run(self) -> Result<(), anyhow::Error> {
101 tokio::runtime::Runtime::new()?.block_on(async {
102 let (snd, rcv) = tokio::sync::oneshot::channel();
103 let handle = self.setup(rcv);
104
105 if let Err(e) = AsyncCliCommand::run_async(self).await {
106 if let Some(handle) = handle {
107 handle.abort();
108 }
109 return Err(e);
110 }
111
112 if let Some(handle) = handle {
113 if snd.send(()).is_err() {
114 tracing::warn!("Failed to send 'done' signal to setup thread!");
115 handle.abort();
116 } else {
117 handle.await??;
118 }
119 }
120
121 Ok::<(), anyhow::Error>(())
122 })?;
123
124 Ok(())
125 }
126}
127
128#[derive(clap::Parser, Debug)]
130#[clap(author, version)]
131#[clap(disable_version_flag = true)] #[cfg_attr(feature = "headless", clap(
133 name = "wasmer-headless",
134 about = concat!("wasmer-headless ", env!("CARGO_PKG_VERSION")),
135))]
136#[cfg_attr(not(feature = "headless"), clap(
137 name = "wasmer",
138 about = concat!("wasmer ", env!("CARGO_PKG_VERSION")),
139))]
140pub struct WasmerCmd {
141 #[clap(short = 'V', long)]
143 version: bool,
144 #[clap(flatten)]
145 output: crate::logging::Output,
146 #[clap(subcommand)]
147 cmd: Option<Cmd>,
148}
149
150impl WasmerCmd {
151 fn execute(self) -> Result<(), anyhow::Error> {
152 let WasmerCmd {
153 cmd,
154 version,
155 output,
156 } = self;
157
158 output.initialize_logging();
159
160 if version {
161 return print_version(output.is_verbose());
162 }
163
164 match cmd {
165 Some(Cmd::GenManPage(cmd)) => cmd.execute(),
166 Some(Cmd::GenCompletions(cmd)) => cmd.execute(),
167 Some(Cmd::Run(options)) => options.execute(output),
168 Some(Cmd::SelfUpdate(options)) => options.execute(),
169 Some(Cmd::Cache(cache)) => cache.execute(),
170 Some(Cmd::Validate(validate)) => validate.execute(),
171 #[cfg(feature = "compiler")]
172 Some(Cmd::Compile(compile)) => compile.execute(),
173 Some(Cmd::Config(config)) => config.run(),
174 Some(Cmd::Inspect(inspect)) => inspect.execute(),
175 Some(Cmd::Init(init)) => init.run(),
176 Some(Cmd::Login(login)) => login.run(),
177 Some(Cmd::Auth(auth)) => auth.run(),
178 Some(Cmd::Publish(publish)) => publish.run().map(|_| ()),
179 Some(Cmd::Package(cmd)) => match cmd {
180 Package::Download(cmd) => cmd.execute(),
181 Package::Build(cmd) => cmd.execute().map(|_| ()),
182 Package::Tag(cmd) => cmd.run(),
183 Package::Push(cmd) => cmd.run(),
184 Package::Publish(cmd) => cmd.run().map(|_| ()),
185 Package::Tree(cmd) => cmd.run(),
186 Package::Unpack(cmd) => cmd.execute(),
187 Package::Search(cmd) => cmd.run(),
188 Package::Get(cmd) => cmd.run(),
189 },
190 Some(Cmd::Container(cmd)) => match cmd {
191 crate::commands::Container::Unpack(cmd) => cmd.execute(),
192 },
193 #[cfg(feature = "wast")]
194 Some(Cmd::Wast(wast)) => wast.execute(),
195 #[cfg(target_os = "linux")]
196 Some(Cmd::Binfmt(binfmt)) => binfmt.execute(),
197 Some(Cmd::Whoami(whoami)) => whoami.run(),
198 Some(Cmd::Add(add)) => add.run(),
199
200 Some(Cmd::Deploy(c)) => c.run(),
202 Some(Cmd::App(apps)) => apps.run(),
203 Some(Cmd::Cron(cron)) => cron.run(),
204 #[cfg(feature = "journal")]
205 Some(Cmd::Journal(journal)) => journal.run(),
206 Some(Cmd::Ssh(ssh)) => ssh.run(),
207 Some(Cmd::Namespace(namespace)) => namespace.run(),
208 Some(Cmd::Domain(namespace)) => namespace.run(),
209 None => {
210 WasmerCmd::command().print_long_help()?;
211 std::process::exit(2);
213 }
214 }
215 }
216
217 pub fn run() {
219 #[cfg(windows)]
221 colored::control::set_virtual_terminal(true).unwrap();
222
223 PrettyError::report(Self::run_inner())
224 }
225
226 fn run_inner() -> Result<(), anyhow::Error> {
227 let mut args_os = std::env::args_os();
228
229 let args = args_os.next().into_iter();
230
231 let mut binfmt_args = Vec::new();
232 if is_binfmt_interpreter() {
233 let current_dir = std::env::current_dir().unwrap();
240 let mut mount_paths = ["/home", "/etc", "/tmp", "/var", "/nix", "/opt", "/root"]
241 .into_iter()
242 .map(std::path::PathBuf::from)
243 .filter(|path| {
244 if !path.is_dir() {
245 return false;
247 }
248 if std::fs::read_dir(path).is_err() {
249 return false;
251 }
252 true
253 })
254 .collect_vec();
255 if mount_paths
256 .iter()
257 .all(|path| !current_dir.starts_with(path))
258 {
259 mount_paths.push(current_dir.clone());
261 }
262
263 binfmt_args.push("run".into());
264 binfmt_args.push("--net".into());
265 binfmt_args.push("--forward-host-env".into());
267 for mount_path in mount_paths {
268 if let Some(mount_path_str) = mount_path.to_str() {
269 binfmt_args.push(format!("--volume={mount_path_str}:{mount_path_str}").into());
270 }
271 }
272 if let Some(current_dir_str) = current_dir.to_str() {
273 binfmt_args.push(format!("--cwd={current_dir_str}").into());
274 }
275 binfmt_args.push("--quiet".into());
276 binfmt_args.push("--".into());
277 binfmt_args.push(args_os.next().unwrap());
278 args_os.next().unwrap();
279 };
280 let args_vec = args.chain(binfmt_args).chain(args_os).collect_vec();
281
282 match WasmerCmd::try_parse_from(args_vec.iter()) {
283 Ok(args) => args.execute(),
284 Err(e) => {
285 let first_arg_is_subcommand = if let Some(first_arg) = args_vec.get(1) {
286 let mut ret = false;
287 let cmd = WasmerCmd::command();
288
289 for cmd in cmd.get_subcommands() {
290 if cmd.get_name() == first_arg {
291 ret = true;
292 break;
293 }
294 }
295
296 ret
297 } else {
298 false
299 };
300
301 let might_be_wasmer_run = matches!(
302 e.kind(),
303 clap::error::ErrorKind::InvalidSubcommand
304 | clap::error::ErrorKind::UnknownArgument
305 ) && !first_arg_is_subcommand;
306
307 if might_be_wasmer_run && let Ok(run) = Run::try_parse_from(args_vec.iter()) {
308 let output = crate::logging::Output::default();
313 output.initialize_logging();
314 run.execute(output);
315 }
316
317 e.exit();
318 }
319 }
320 }
321}
322
323#[derive(clap::Parser, Debug)]
324#[allow(clippy::large_enum_variant)]
325enum Cmd {
327 Login(Login),
329
330 #[clap(subcommand)]
331 Auth(CmdAuth),
332
333 #[clap(name = "publish")]
335 Publish(PackagePublish),
336
337 Cache(Cache),
339
340 Validate(Validate),
342
343 #[cfg(feature = "compiler")]
345 Compile(Compile),
346
347 Config(Config),
350
351 #[clap(name = "self-update")]
353 SelfUpdate(SelfUpdate),
354
355 Inspect(Inspect),
357
358 #[clap(name = "init")]
360 Init(Init),
361
362 #[cfg(feature = "wast")]
364 Wast(Wast),
365
366 #[cfg(target_os = "linux")]
368 Binfmt(Binfmt),
369
370 Whoami(Whoami),
372
373 Add(CmdAdd),
375
376 #[clap(alias = "run-unstable")]
378 Run(Run),
379
380 #[cfg(feature = "journal")]
382 #[clap(subcommand)]
383 Journal(CmdJournal),
384
385 #[clap(subcommand)]
386 Package(crate::commands::Package),
387
388 #[clap(subcommand)]
389 Container(crate::commands::Container),
390
391 Deploy(crate::commands::app::deploy::CmdAppDeploy),
394
395 #[clap(subcommand, alias = "apps")]
397 App(crate::commands::app::CmdApp),
398
399 #[clap(subcommand)]
401 Cron(crate::commands::cron::CmdCron),
402
403 Ssh(crate::commands::ssh::CmdSsh),
405
406 #[clap(subcommand, alias = "namespaces")]
408 Namespace(crate::commands::namespace::CmdNamespace),
409
410 #[clap(subcommand, alias = "domains")]
412 Domain(crate::commands::domain::CmdDomain),
413
414 #[clap(name = "gen-completions")]
416 GenCompletions(crate::commands::gen_completions::CmdGenCompletions),
417
418 #[clap(name = "gen-man", hide = true)]
420 GenManPage(crate::commands::gen_manpage::CmdGenManPage),
421}
422
423fn is_binfmt_interpreter() -> bool {
424 cfg_select! {
425 target_os = "linux" => {
426 let binary_path = match std::env::args_os().next() {
428 Some(path) => std::path::PathBuf::from(path),
429 None => return false,
430 };
431 binary_path.file_name().and_then(|f| f.to_str()) == Some(Binfmt::FILENAME)
432 }
433 _ => {
434 false
435 }
436 }
437}
438
439fn print_version(verbose: bool) -> Result<(), anyhow::Error> {
440 if !verbose {
441 println!("wasmer {}", env!("CARGO_PKG_VERSION"));
442 return Ok(());
443 }
444
445 println!("wasmer {}", env!("CARGO_PKG_VERSION"));
446 println!("binary: {}", env!("CARGO_PKG_NAME"));
447 let git_hash = git_version!(
448 args = [
449 "--abbrev=40",
450 "--always",
451 "--dirty=-modified",
452 "--exclude=*"
453 ],
454 fallback = "",
455 )
456 .to_string();
457 if !git_hash.is_empty() {
458 println!("commit-hash: {git_hash}",);
459 }
460 if !env!("WASMER_REPRODUCIBLE_BUILD")
461 .parse::<bool>()
462 .expect("build-time variable expected")
463 {
464 println!("commit-date: {}", env!("WASMER_BUILD_DATE"));
465 }
466 println!("host: {}", target_lexicon::HOST);
467
468 let cpu_features = wasmer_types::target::CpuFeature::for_host()
469 .iter()
470 .map(|f| f.to_string())
471 .join(" ");
472 println!("CPU flags: {cpu_features}");
473
474 let mut runtimes = Vec::new();
475 if cfg!(feature = "singlepass") {
476 runtimes.push("Singlepass");
477 }
478 if cfg!(feature = "cranelift") {
479 runtimes.push("Cranelift");
480 }
481 if cfg!(feature = "llvm") {
482 runtimes.push("LLVM");
483 }
484 if cfg!(feature = "v8") {
485 runtimes.push("V8");
486 }
487 println!("runtimes: {}", runtimes.join(", "));
488
489 #[allow(clippy::useless_vec)]
490 #[allow(unused_mut)]
491 let mut features = vec!["wasix".to_string()];
492 #[cfg(feature = "napi-v8")]
493 {
494 for napi_version in enum_iterator::all::<wasmer_napi::NapiVersion>() {
495 if !matches!(napi_version, wasmer_napi::NapiVersion::Unknown) {
496 features.push(napi_version.to_string());
497 }
498 }
499 features.push(wasmer_napi::NAPI_EXTENSION_WASMER_MODULE_NAME.to_string());
500 }
501 println!("features: {}", features.join(", "));
502
503 Ok(())
504}