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;
13#[cfg(any(feature = "static-artifact-create", feature = "wasmer-artifact-create"))]
14mod create_exe;
15#[cfg(feature = "static-artifact-create")]
16mod create_obj;
17mod cron;
18pub(crate) mod domain;
19#[cfg(feature = "static-artifact-create")]
20mod gen_c_header;
21mod gen_completions;
22mod gen_manpage;
23mod init;
24mod inspect;
25#[cfg(feature = "journal")]
26mod journal;
27pub(crate) mod namespace;
28mod package;
29mod run;
30mod self_update;
31pub mod ssh;
32mod validate;
33#[cfg(feature = "wast")]
34mod wast;
35use itertools::Itertools;
36use std::io::IsTerminal as _;
37use tokio::task::JoinHandle;
38
39#[cfg(target_os = "linux")]
40pub use binfmt::*;
41use clap::{CommandFactory, Parser};
42#[cfg(feature = "compiler")]
43pub use compile::*;
44#[cfg(any(feature = "static-artifact-create", feature = "wasmer-artifact-create"))]
45pub use create_exe::*;
46#[cfg(feature = "wast")]
47pub use wast::*;
48#[cfg(feature = "static-artifact-create")]
49#[allow(unused_imports)]
50pub use {create_obj::*, gen_c_header::*};
51
52#[cfg(feature = "journal")]
53pub use self::journal::*;
54pub use self::{
55 add::*, auth::*, cache::*, config::*, container::*, init::*, inspect::*, package::*,
56 publish::*, run::Run, self_update::*, validate::*,
57};
58use crate::error::PrettyError;
59use git_version::git_version;
60
61pub(crate) trait CliCommand {
63 type Output;
64
65 fn run(self) -> Result<(), anyhow::Error>;
66}
67
68#[async_trait::async_trait]
73pub(crate) trait AsyncCliCommand: Send + Sync {
74 type Output: Send + Sync;
75
76 async fn run_async(self) -> Result<Self::Output, anyhow::Error>;
77
78 fn setup(
79 &self,
80 done: tokio::sync::oneshot::Receiver<()>,
81 ) -> Option<JoinHandle<anyhow::Result<()>>> {
82 if std::io::stdin().is_terminal() {
83 return Some(tokio::task::spawn(async move {
84 tokio::select! {
85 _ = done => {}
86
87 _ = tokio::signal::ctrl_c() => {
88 let term = console::Term::stdout();
89 let _ = term.show_cursor();
90 #[cfg(target_os = "windows")]
92 std::process::exit(3);
93
94 #[cfg(not(target_os = "windows"))]
96 std::process::exit(130);
97 }
98 }
99
100 Ok::<(), anyhow::Error>(())
101 }));
102 }
103
104 None
105 }
106}
107
108impl<O: Send + Sync, C: AsyncCliCommand<Output = O>> CliCommand for C {
109 type Output = O;
110
111 fn run(self) -> Result<(), anyhow::Error> {
112 tokio::runtime::Runtime::new()?.block_on(async {
113 let (snd, rcv) = tokio::sync::oneshot::channel();
114 let handle = self.setup(rcv);
115
116 if let Err(e) = AsyncCliCommand::run_async(self).await {
117 if let Some(handle) = handle {
118 handle.abort();
119 }
120 return Err(e);
121 }
122
123 if let Some(handle) = handle {
124 if snd.send(()).is_err() {
125 tracing::warn!("Failed to send 'done' signal to setup thread!");
126 handle.abort();
127 } else {
128 handle.await??;
129 }
130 }
131
132 Ok::<(), anyhow::Error>(())
133 })?;
134
135 Ok(())
136 }
137}
138
139#[derive(clap::Parser, Debug)]
141#[clap(author, version)]
142#[clap(disable_version_flag = true)] #[cfg_attr(feature = "headless", clap(
144 name = "wasmer-headless",
145 about = concat!("wasmer-headless ", env!("CARGO_PKG_VERSION")),
146))]
147#[cfg_attr(not(feature = "headless"), clap(
148 name = "wasmer",
149 about = concat!("wasmer ", env!("CARGO_PKG_VERSION")),
150))]
151pub struct WasmerCmd {
152 #[clap(short = 'V', long)]
154 version: bool,
155 #[clap(flatten)]
156 output: crate::logging::Output,
157 #[clap(subcommand)]
158 cmd: Option<Cmd>,
159}
160
161impl WasmerCmd {
162 fn execute(self) -> Result<(), anyhow::Error> {
163 let WasmerCmd {
164 cmd,
165 version,
166 output,
167 } = self;
168
169 output.initialize_logging();
170
171 if version {
172 return print_version(output.is_verbose());
173 }
174
175 match cmd {
176 Some(Cmd::GenManPage(cmd)) => cmd.execute(),
177 Some(Cmd::GenCompletions(cmd)) => cmd.execute(),
178 Some(Cmd::Run(options)) => options.execute(output),
179 Some(Cmd::SelfUpdate(options)) => options.execute(),
180 Some(Cmd::Cache(cache)) => cache.execute(),
181 Some(Cmd::Validate(validate)) => validate.execute(),
182 #[cfg(feature = "compiler")]
183 Some(Cmd::Compile(compile)) => compile.execute(),
184 Some(Cmd::Config(config)) => config.run(),
190 Some(Cmd::Inspect(inspect)) => inspect.execute(),
191 Some(Cmd::Init(init)) => init.run(),
192 Some(Cmd::Login(login)) => login.run(),
193 Some(Cmd::Auth(auth)) => auth.run(),
194 Some(Cmd::Publish(publish)) => publish.run().map(|_| ()),
195 Some(Cmd::Package(cmd)) => match cmd {
196 Package::Download(cmd) => cmd.execute(),
197 Package::Build(cmd) => cmd.execute().map(|_| ()),
198 Package::Tag(cmd) => cmd.run(),
199 Package::Push(cmd) => cmd.run(),
200 Package::Publish(cmd) => cmd.run().map(|_| ()),
201 Package::Tree(cmd) => cmd.run(),
202 Package::Unpack(cmd) => cmd.execute(),
203 Package::Search(cmd) => cmd.run(),
204 Package::Get(cmd) => cmd.run(),
205 },
206 Some(Cmd::Container(cmd)) => match cmd {
207 crate::commands::Container::Unpack(cmd) => cmd.execute(),
208 },
209 #[cfg(feature = "static-artifact-create")]
210 Some(Cmd::GenCHeader(gen_header)) => gen_header.execute(),
211 #[cfg(feature = "wast")]
212 Some(Cmd::Wast(wast)) => wast.execute(),
213 #[cfg(target_os = "linux")]
214 Some(Cmd::Binfmt(binfmt)) => binfmt.execute(),
215 Some(Cmd::Whoami(whoami)) => whoami.run(),
216 Some(Cmd::Add(add)) => add.run(),
217
218 Some(Cmd::Deploy(c)) => c.run(),
220 Some(Cmd::App(apps)) => apps.run(),
221 Some(Cmd::Cron(cron)) => cron.run(),
222 #[cfg(feature = "journal")]
223 Some(Cmd::Journal(journal)) => journal.run(),
224 Some(Cmd::Ssh(ssh)) => ssh.run(),
225 Some(Cmd::Namespace(namespace)) => namespace.run(),
226 Some(Cmd::Domain(namespace)) => namespace.run(),
227 None => {
228 WasmerCmd::command().print_long_help()?;
229 std::process::exit(2);
231 }
232 }
233 }
234
235 pub fn run() {
237 #[cfg(windows)]
239 colored::control::set_virtual_terminal(true).unwrap();
240
241 PrettyError::report(Self::run_inner())
242 }
243
244 fn run_inner() -> Result<(), anyhow::Error> {
245 let mut args_os = std::env::args_os();
246
247 let args = args_os.next().into_iter();
248
249 let mut binfmt_args = Vec::new();
250 if is_binfmt_interpreter() {
251 let current_dir = std::env::current_dir().unwrap();
258 let mut mount_paths = ["/home", "/etc", "/tmp", "/var", "/nix", "/opt", "/root"]
259 .into_iter()
260 .map(std::path::PathBuf::from)
261 .filter(|path| {
262 if !path.is_dir() {
263 return false;
265 }
266 if std::fs::read_dir(path).is_err() {
267 return false;
269 }
270 true
271 })
272 .collect_vec();
273 if mount_paths
274 .iter()
275 .all(|path| !current_dir.starts_with(path))
276 {
277 mount_paths.push(current_dir.clone());
279 }
280
281 binfmt_args.push("run".into());
282 binfmt_args.push("--net".into());
283 binfmt_args.push("--forward-host-env".into());
285 for mount_path in mount_paths {
286 if let Some(mount_path_str) = mount_path.to_str() {
287 binfmt_args.push(format!("--volume={mount_path_str}:{mount_path_str}").into());
288 }
289 }
290 if let Some(current_dir_str) = current_dir.to_str() {
291 binfmt_args.push(format!("--cwd={current_dir_str}").into());
292 }
293 binfmt_args.push("--quiet".into());
294 binfmt_args.push("--".into());
295 binfmt_args.push(args_os.next().unwrap());
296 args_os.next().unwrap();
297 };
298 let args_vec = args.chain(binfmt_args).chain(args_os).collect_vec();
299
300 match WasmerCmd::try_parse_from(args_vec.iter()) {
301 Ok(args) => args.execute(),
302 Err(e) => {
303 let first_arg_is_subcommand = if let Some(first_arg) = args_vec.get(1) {
304 let mut ret = false;
305 let cmd = WasmerCmd::command();
306
307 for cmd in cmd.get_subcommands() {
308 if cmd.get_name() == first_arg {
309 ret = true;
310 break;
311 }
312 }
313
314 ret
315 } else {
316 false
317 };
318
319 let might_be_wasmer_run = matches!(
320 e.kind(),
321 clap::error::ErrorKind::InvalidSubcommand
322 | clap::error::ErrorKind::UnknownArgument
323 ) && !first_arg_is_subcommand;
324
325 if might_be_wasmer_run && let Ok(run) = Run::try_parse_from(args_vec.iter()) {
326 let output = crate::logging::Output::default();
331 output.initialize_logging();
332 run.execute(output);
333 }
334
335 e.exit();
336 }
337 }
338 }
339}
340
341#[derive(clap::Parser, Debug)]
342#[allow(clippy::large_enum_variant)]
343enum Cmd {
345 Login(Login),
347
348 #[clap(subcommand)]
349 Auth(CmdAuth),
350
351 #[clap(name = "publish")]
353 Publish(PackagePublish),
354
355 Cache(Cache),
357
358 Validate(Validate),
360
361 #[cfg(feature = "compiler")]
363 Compile(Compile),
364
365 #[cfg(feature = "static-artifact-create")]
433 GenCHeader(GenCHeader),
434
435 Config(Config),
438
439 #[clap(name = "self-update")]
441 SelfUpdate(SelfUpdate),
442
443 Inspect(Inspect),
445
446 #[clap(name = "init")]
448 Init(Init),
449
450 #[cfg(feature = "wast")]
452 Wast(Wast),
453
454 #[cfg(target_os = "linux")]
456 Binfmt(Binfmt),
457
458 Whoami(Whoami),
460
461 Add(CmdAdd),
463
464 #[clap(alias = "run-unstable")]
466 Run(Run),
467
468 #[cfg(feature = "journal")]
470 #[clap(subcommand)]
471 Journal(CmdJournal),
472
473 #[clap(subcommand)]
474 Package(crate::commands::Package),
475
476 #[clap(subcommand)]
477 Container(crate::commands::Container),
478
479 Deploy(crate::commands::app::deploy::CmdAppDeploy),
482
483 #[clap(subcommand, alias = "apps")]
485 App(crate::commands::app::CmdApp),
486
487 #[clap(subcommand)]
489 Cron(crate::commands::cron::CmdCron),
490
491 Ssh(crate::commands::ssh::CmdSsh),
493
494 #[clap(subcommand, alias = "namespaces")]
496 Namespace(crate::commands::namespace::CmdNamespace),
497
498 #[clap(subcommand, alias = "domains")]
500 Domain(crate::commands::domain::CmdDomain),
501
502 #[clap(name = "gen-completions")]
504 GenCompletions(crate::commands::gen_completions::CmdGenCompletions),
505
506 #[clap(name = "gen-man", hide = true)]
508 GenManPage(crate::commands::gen_manpage::CmdGenManPage),
509}
510
511fn is_binfmt_interpreter() -> bool {
512 cfg_if::cfg_if! {
513 if #[cfg(target_os = "linux")] {
514 let binary_path = match std::env::args_os().next() {
516 Some(path) => std::path::PathBuf::from(path),
517 None => return false,
518 };
519 binary_path.file_name().and_then(|f| f.to_str()) == Some(Binfmt::FILENAME)
520 } else {
521 false
522 }
523 }
524}
525
526fn print_version(verbose: bool) -> Result<(), anyhow::Error> {
527 if !verbose {
528 println!("wasmer {}", env!("CARGO_PKG_VERSION"));
529 return Ok(());
530 }
531
532 println!("wasmer {}", env!("CARGO_PKG_VERSION"));
533 println!("binary: {}", env!("CARGO_PKG_NAME"));
534 let git_hash = git_version!(
535 args = [
536 "--abbrev=40",
537 "--always",
538 "--dirty=-modified",
539 "--exclude=*"
540 ],
541 fallback = "",
542 )
543 .to_string();
544 if !git_hash.is_empty() {
545 println!("commit-hash: {git_hash}",);
546 }
547 if !env!("WASMER_REPRODUCIBLE_BUILD")
548 .parse::<bool>()
549 .expect("build-time variable expected")
550 {
551 println!("commit-date: {}", env!("WASMER_BUILD_DATE"));
552 }
553 println!("host: {}", target_lexicon::HOST);
554
555 let cpu_features = wasmer_types::target::CpuFeature::for_host()
556 .iter()
557 .map(|f| f.to_string())
558 .join(" ");
559 println!("CPU flags: {cpu_features}");
560
561 let mut runtimes = Vec::new();
562 if cfg!(feature = "singlepass") {
563 runtimes.push("Singlepass");
564 }
565 if cfg!(feature = "cranelift") {
566 runtimes.push("Cranelift");
567 }
568 if cfg!(feature = "llvm") {
569 runtimes.push("LLVM");
570 }
571 if cfg!(feature = "v8") {
572 runtimes.push("V8");
573 }
574 println!("runtimes: {}", runtimes.join(", "));
575
576 #[allow(clippy::useless_vec)]
577 #[allow(unused_mut)]
578 let mut features = vec!["wasix".to_string()];
579 #[cfg(feature = "napi-v8")]
580 {
581 for napi_version in enum_iterator::all::<wasmer_napi::NapiVersion>() {
582 if !matches!(napi_version, wasmer_napi::NapiVersion::Unknown) {
583 features.push(napi_version.to_string());
584 }
585 }
586 features.push(wasmer_napi::NAPI_EXTENSION_WASMER_MODULE_NAME.to_string());
587 }
588 println!("features: {}", features.join(", "));
589
590 Ok(())
591}