deno_lib 2.0.8

Provides the deno library
mod args;
mod auth_tokens;
mod cache;
mod cdp;
mod emit;
mod errors;
mod factory;
mod file_fetcher;
mod graph_container;
mod graph_util;
mod http_util;
mod js;
mod jsr;
mod lsp;
mod module_loader;
mod node;
mod npm;
mod ops;
mod resolver;
mod shared;
mod standalone;
mod task_runner;
mod tools;
mod tsc;
mod util;
mod version;
mod worker;

use crate::args::flags_from_vec;
use crate::args::DenoSubcommand;
use crate::args::Flags;
use crate::util::display;
use crate::util::v8::get_v8_flags_from_env;
use crate::util::v8::init_v8_flags;

use args::TaskFlags;
use deno_resolver::npm::ByonmResolvePkgFolderFromDenoReqError;
use deno_resolver::npm::ResolvePkgFolderFromDenoReqError;
use deno_runtime::WorkerExecutionMode;
pub use deno_runtime::UNSTABLE_GRANULAR_FLAGS;

use deno_core::anyhow::Context;
use deno_core::error::AnyError;
use deno_core::error::JsError;
use deno_core::futures::FutureExt;
use deno_core::unsync::JoinHandle;
use deno_npm::resolution::SnapshotFromLockfileError;
use deno_runtime::fmt_errors::format_js_error;
use deno_runtime::tokio_util::create_and_run_current_thread_with_maybe_metrics;
use deno_terminal::colors;
use factory::CliFactory;
use standalone::MODULE_NOT_FOUND;
use standalone::UNSUPPORTED_SCHEME;
use std::env;
use std::future::Future;
use std::io::IsTerminal;
use std::ops::Deref;
use std::path::PathBuf;
use std::sync::Arc;

use deno_runtime::deno_fs::RealFs;
use deno_runtime::deno_permissions::PermissionsContainer;

pub fn run(cmd: &str) -> String {
  let args: Vec<_> = vec!["deno", "run", cmd]
    .into_iter()
    .map(std::ffi::OsString::from)
    .collect();

  let future = async move {
    // NOTE(lucacasonato): due to new PKU feature introduced in V8 11.6 we need to
    // initialize the V8 platform on a parent thread of all threads that will spawn
    // V8 isolates.
    let flags = resolve_flags_and_init(args)?;

    println!("flags: {:?}", flags);

    run_script(Arc::new(flags)).await
  };

  let result = create_and_run_current_thread_with_maybe_metrics(future);

  #[cfg(feature = "dhat-heap")]
  drop(profiler);

  match result {
    Ok(exit_code) => deno_runtime::exit(exit_code),
    Err(err) => exit_for_error(err),
  }
}

pub async fn run_script(flags: Arc<Flags>) -> Result<i32, AnyError> {
  let handle = match flags.subcommand.clone() {
    DenoSubcommand::Run(run_flags) => spawn_subcommand(async move {
      let result = tools::run::run_script(
        WorkerExecutionMode::Run,
        flags.clone(),
        run_flags.watch,
      )
      .await;

      match result {
        Ok(v) => Ok(v),
        Err(script_err) => {
          if let Some(ResolvePkgFolderFromDenoReqError::Byonm(
            ByonmResolvePkgFolderFromDenoReqError::UnmatchedReq(_),
          )) = script_err.downcast_ref::<ResolvePkgFolderFromDenoReqError>()
          {
            if flags.node_modules_dir.is_none() {
              let mut flags = flags.deref().clone();
              let watch = match &flags.subcommand {
                DenoSubcommand::Run(run_flags) => run_flags.watch.clone(),
                _ => unreachable!(),
              };
              flags.node_modules_dir =
                Some(deno_config::deno_json::NodeModulesDirMode::None);
              // use the current lockfile, but don't write it out
              if flags.frozen_lockfile.is_none() {
                flags.internal.lockfile_skip_write = true;
              }
              return tools::run::run_script(
                WorkerExecutionMode::Run,
                Arc::new(flags),
                watch,
              )
              .await;
            }
          }
          let script_err_msg = script_err.to_string();
          if script_err_msg.starts_with(MODULE_NOT_FOUND)
            || script_err_msg.starts_with(UNSUPPORTED_SCHEME)
          {
            if run_flags.bare {
              let mut cmd = args::clap_root();
              cmd.build();
              let command_names = cmd
                .get_subcommands()
                .map(|command| command.get_name())
                .collect::<Vec<_>>();
              let suggestions =
                args::did_you_mean(&run_flags.script, command_names);
              if !suggestions.is_empty() {
                let mut error =
                  clap::error::Error::<clap::error::DefaultFormatter>::new(
                    clap::error::ErrorKind::InvalidSubcommand,
                  )
                  .with_cmd(&cmd);
                error.insert(
                  clap::error::ContextKind::SuggestedSubcommand,
                  clap::error::ContextValue::Strings(suggestions),
                );

                Err(error.into())
              } else {
                Err(script_err)
              }
            } else {
              let mut new_flags = flags.deref().clone();
              let task_flags = TaskFlags {
                cwd: None,
                task: Some(run_flags.script.clone()),
                is_run: true,
              };
              new_flags.subcommand = DenoSubcommand::Task(task_flags.clone());
              let result = tools::task::execute_script(
                Arc::new(new_flags),
                task_flags.clone(),
              )
              .await;
              match result {
                Ok(v) => Ok(v),
                Err(_) => {
                  // Return script error for backwards compatibility.
                  Err(script_err)
                }
              }
            }
          } else {
            Err(script_err)
          }
        }
      }
    }),
    _ => unreachable!(),
  };

  handle.await?
}

fn resolve_flags_and_init(
  args: Vec<std::ffi::OsString>,
) -> Result<Flags, AnyError> {
  let flags = match flags_from_vec(args) {
    Ok(flags) => flags,
    Err(err @ clap::Error { .. })
      if err.kind() == clap::error::ErrorKind::DisplayVersion =>
    {
      // Ignore results to avoid BrokenPipe errors.
      util::logger::init(None);
      let _ = err.print();
      deno_runtime::exit(0);
    }
    Err(err) => {
      util::logger::init(None);
      exit_for_error(AnyError::from(err))
    }
  };

  if let Some(otel_config) = flags.otel_config() {
    deno_runtime::ops::otel::init(otel_config)?;
  }
  util::logger::init(flags.log_level);

  // TODO(bartlomieju): remove in Deno v2.5 and hard error then.
  if flags.unstable_config.legacy_flag_enabled {
    log::warn!(
      "⚠️  {}",
      colors::yellow(
        "The `--unstable` flag has been removed in Deno 2.0. Use granular `--unstable-*` flags instead.\nLearn more at: https://docs.deno.com/runtime/manual/tools/unstable_flags"
      )
    );
  }

  let default_v8_flags = match flags.subcommand {
    // Using same default as VSCode:
    // https://github.com/microsoft/vscode/blob/48d4ba271686e8072fc6674137415bc80d936bc7/extensions/typescript-language-features/src/configuration/configuration.ts#L213-L214
    DenoSubcommand::Lsp => vec!["--max-old-space-size=3072".to_string()],
    _ => {
      // TODO(bartlomieju): I think this can be removed as it's handled by `deno_core`
      // and its settings.
      // deno_ast removes TypeScript `assert` keywords, so this flag only affects JavaScript
      // TODO(petamoriken): Need to check TypeScript `assert` keywords in deno_ast
      vec!["--no-harmony-import-assertions".to_string()]
    }
  };

  init_v8_flags(&default_v8_flags, &flags.v8_flags, get_v8_flags_from_env());
  // TODO(bartlomieju): remove last argument once Deploy no longer needs it
  deno_core::JsRuntime::init_platform(
    None, /* import assertions enabled */ false,
  );

  Ok(flags)
}

fn exit_for_error(error: AnyError) -> ! {
  let mut error_string = format!("{error:?}");
  let mut error_code = 1;

  if let Some(e) = error.downcast_ref::<JsError>() {
    error_string = format_js_error(e);
  } else if let Some(SnapshotFromLockfileError::IntegrityCheckFailed(e)) =
    error.downcast_ref::<SnapshotFromLockfileError>()
  {
    error_string = e.to_string();
    error_code = 10;
  }

  exit_with_message(&error_string, error_code);
}

/// Ensure that the subcommand runs in a task, rather than being directly executed. Since some of these
/// futures are very large, this prevents the stack from getting blown out from passing them by value up
/// the callchain (especially in debug mode when Rust doesn't have a chance to elide copies!).
#[inline(always)]
fn spawn_subcommand<F: Future<Output = T> + 'static, T: SubcommandOutput>(
  f: F,
) -> JoinHandle<Result<i32, AnyError>> {
  // the boxed_local() is important in order to get windows to not blow the stack in debug
  deno_core::unsync::spawn(
    async move { f.map(|r| r.output()).await }.boxed_local(),
  )
}

fn exit_with_message(message: &str, code: i32) -> ! {
  log::error!(
    "{}: {}",
    colors::red_bold("error"),
    message.trim_start_matches("error: ")
  );
  deno_runtime::exit(code);
}

/// Ensures that all subcommands return an i32 exit code and an [`AnyError`] error type.
trait SubcommandOutput {
  fn output(self) -> Result<i32, AnyError>;
}

impl SubcommandOutput for Result<i32, AnyError> {
  fn output(self) -> Result<i32, AnyError> {
    self
  }
}

impl SubcommandOutput for Result<(), AnyError> {
  fn output(self) -> Result<i32, AnyError> {
    self.map(|_| 0)
  }
}

impl SubcommandOutput for Result<(), std::io::Error> {
  fn output(self) -> Result<i32, AnyError> {
    self.map(|_| 0).map_err(|e| e.into())
  }
}

pub(crate) fn unstable_exit_cb(feature: &str, api_name: &str) {
  log::error!(
    "Unstable API '{api_name}'. The `--unstable-{}` flag must be provided.",
    feature
  );
  deno_runtime::exit(70);
}