mirror of
https://github.com/denoland/deno.git
synced 2024-12-20 14:24:48 -05:00
6f506208f6
Currently deno eagerly caches all npm packages in the workspace's npm resolution. So, for instance, running a file `foo.ts` that imports `npm:chalk` will also install all dependencies listed in `package.json` and all `npm` dependencies listed in the lockfile. This PR refactors things to give more control over when and what npm packages are automatically cached while building the module graph. After this PR, by default the current behavior is unchanged _except_ for `deno install --entrypoint`, which will only cache npm packages used by the given entrypoint. For the other subcommands, this behavior can be enabled with `--unstable-npm-lazy-caching` Fixes #25782. --------- Signed-off-by: Nathan Whitaker <17734409+nathanwhit@users.noreply.github.com> Co-authored-by: Luca Casonato <hello@lcas.dev>
221 lines
6.3 KiB
Rust
221 lines
6.3 KiB
Rust
// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license.
|
|
|
|
use std::io::Read;
|
|
use std::sync::Arc;
|
|
|
|
use deno_config::deno_json::NodeModulesDirMode;
|
|
use deno_core::error::AnyError;
|
|
use deno_runtime::WorkerExecutionMode;
|
|
|
|
use crate::args::EvalFlags;
|
|
use crate::args::Flags;
|
|
use crate::args::WatchFlagsWithPaths;
|
|
use crate::factory::CliFactory;
|
|
use crate::file_fetcher::File;
|
|
use crate::util;
|
|
use crate::util::file_watcher::WatcherRestartMode;
|
|
|
|
pub mod hmr;
|
|
|
|
pub fn check_permission_before_script(flags: &Flags) {
|
|
if !flags.has_permission() && flags.has_permission_in_argv() {
|
|
log::warn!(
|
|
"{}",
|
|
crate::colors::yellow(
|
|
r#"Permission flags have likely been incorrectly set after the script argument.
|
|
To grant permissions, set them before the script argument. For example:
|
|
deno run --allow-read=. main.js"#
|
|
)
|
|
);
|
|
}
|
|
}
|
|
|
|
fn set_npm_user_agent() {
|
|
static ONCE: std::sync::Once = std::sync::Once::new();
|
|
ONCE.call_once(|| {
|
|
std::env::set_var(
|
|
crate::npm::NPM_CONFIG_USER_AGENT_ENV_VAR,
|
|
crate::npm::get_npm_config_user_agent(),
|
|
);
|
|
});
|
|
}
|
|
|
|
pub async fn run_script(
|
|
mode: WorkerExecutionMode,
|
|
flags: Arc<Flags>,
|
|
watch: Option<WatchFlagsWithPaths>,
|
|
) -> Result<i32, AnyError> {
|
|
check_permission_before_script(&flags);
|
|
|
|
if let Some(watch_flags) = watch {
|
|
return run_with_watch(mode, flags, watch_flags).await;
|
|
}
|
|
|
|
// TODO(bartlomieju): actually I think it will also fail if there's an import
|
|
// map specified and bare specifier is used on the command line
|
|
let factory = CliFactory::from_flags(flags);
|
|
let cli_options = factory.cli_options()?;
|
|
let deno_dir = factory.deno_dir()?;
|
|
let http_client = factory.http_client_provider();
|
|
|
|
// Run a background task that checks for available upgrades or output
|
|
// if an earlier run of this background task found a new version of Deno.
|
|
#[cfg(feature = "upgrade")]
|
|
super::upgrade::check_for_upgrades(
|
|
http_client.clone(),
|
|
deno_dir.upgrade_check_file_path(),
|
|
);
|
|
|
|
let main_module = cli_options.resolve_main_module()?;
|
|
|
|
if main_module.scheme() == "npm" {
|
|
set_npm_user_agent();
|
|
}
|
|
|
|
maybe_npm_install(&factory).await?;
|
|
|
|
let worker_factory = factory.create_cli_main_worker_factory().await?;
|
|
let mut worker = worker_factory
|
|
.create_main_worker(mode, main_module.clone())
|
|
.await?;
|
|
|
|
let exit_code = worker.run().await?;
|
|
Ok(exit_code)
|
|
}
|
|
|
|
pub async fn run_from_stdin(flags: Arc<Flags>) -> Result<i32, AnyError> {
|
|
let factory = CliFactory::from_flags(flags);
|
|
let cli_options = factory.cli_options()?;
|
|
let main_module = cli_options.resolve_main_module()?;
|
|
|
|
maybe_npm_install(&factory).await?;
|
|
|
|
let file_fetcher = factory.file_fetcher()?;
|
|
let worker_factory = factory.create_cli_main_worker_factory().await?;
|
|
let mut source = Vec::new();
|
|
std::io::stdin().read_to_end(&mut source)?;
|
|
// Save a fake file into file fetcher cache
|
|
// to allow module access by TS compiler
|
|
file_fetcher.insert_memory_files(File {
|
|
specifier: main_module.clone(),
|
|
maybe_headers: None,
|
|
source: source.into(),
|
|
});
|
|
|
|
let mut worker = worker_factory
|
|
.create_main_worker(WorkerExecutionMode::Run, main_module.clone())
|
|
.await?;
|
|
let exit_code = worker.run().await?;
|
|
Ok(exit_code)
|
|
}
|
|
|
|
// TODO(bartlomieju): this function is not handling `exit_code` set by the runtime
|
|
// code properly.
|
|
async fn run_with_watch(
|
|
mode: WorkerExecutionMode,
|
|
flags: Arc<Flags>,
|
|
watch_flags: WatchFlagsWithPaths,
|
|
) -> Result<i32, AnyError> {
|
|
util::file_watcher::watch_recv(
|
|
flags,
|
|
util::file_watcher::PrintConfig::new_with_banner(
|
|
if watch_flags.hmr { "HMR" } else { "Watcher" },
|
|
"Process",
|
|
!watch_flags.no_clear_screen,
|
|
),
|
|
WatcherRestartMode::Automatic,
|
|
move |flags, watcher_communicator, changed_paths| {
|
|
watcher_communicator.show_path_changed(changed_paths.clone());
|
|
Ok(async move {
|
|
let factory = CliFactory::from_flags_for_watcher(
|
|
flags,
|
|
watcher_communicator.clone(),
|
|
);
|
|
let cli_options = factory.cli_options()?;
|
|
let main_module = cli_options.resolve_main_module()?;
|
|
|
|
if main_module.scheme() == "npm" {
|
|
set_npm_user_agent();
|
|
}
|
|
|
|
maybe_npm_install(&factory).await?;
|
|
|
|
let _ = watcher_communicator.watch_paths(cli_options.watch_paths());
|
|
|
|
let mut worker = factory
|
|
.create_cli_main_worker_factory()
|
|
.await?
|
|
.create_main_worker(mode, main_module.clone())
|
|
.await?;
|
|
|
|
if watch_flags.hmr {
|
|
worker.run().await?;
|
|
} else {
|
|
worker.run_for_watcher().await?;
|
|
}
|
|
|
|
Ok(())
|
|
})
|
|
},
|
|
)
|
|
.await?;
|
|
|
|
Ok(0)
|
|
}
|
|
|
|
pub async fn eval_command(
|
|
flags: Arc<Flags>,
|
|
eval_flags: EvalFlags,
|
|
) -> Result<i32, AnyError> {
|
|
let factory = CliFactory::from_flags(flags);
|
|
let cli_options = factory.cli_options()?;
|
|
let file_fetcher = factory.file_fetcher()?;
|
|
let main_module = cli_options.resolve_main_module()?;
|
|
|
|
maybe_npm_install(&factory).await?;
|
|
|
|
// Create a dummy source file.
|
|
let source_code = if eval_flags.print {
|
|
format!("console.log({})", eval_flags.code)
|
|
} else {
|
|
eval_flags.code
|
|
};
|
|
|
|
// Save a fake file into file fetcher cache
|
|
// to allow module access by TS compiler.
|
|
file_fetcher.insert_memory_files(File {
|
|
specifier: main_module.clone(),
|
|
maybe_headers: None,
|
|
source: source_code.into_bytes().into(),
|
|
});
|
|
|
|
let worker_factory = factory.create_cli_main_worker_factory().await?;
|
|
let mut worker = worker_factory
|
|
.create_main_worker(WorkerExecutionMode::Eval, main_module.clone())
|
|
.await?;
|
|
let exit_code = worker.run().await?;
|
|
Ok(exit_code)
|
|
}
|
|
|
|
pub async fn maybe_npm_install(factory: &CliFactory) -> Result<(), AnyError> {
|
|
let cli_options = factory.cli_options()?;
|
|
// ensure an "npm install" is done if the user has explicitly
|
|
// opted into using a managed node_modules directory
|
|
if cli_options.node_modules_dir()? == Some(NodeModulesDirMode::Auto) {
|
|
if let Some(npm_resolver) = factory.npm_resolver().await?.as_managed() {
|
|
let already_done =
|
|
npm_resolver.ensure_top_level_package_json_install().await?;
|
|
if !already_done
|
|
&& matches!(
|
|
cli_options.default_npm_caching_strategy(),
|
|
crate::graph_util::NpmCachingStrategy::Eager
|
|
)
|
|
{
|
|
npm_resolver
|
|
.cache_packages(crate::npm::PackageCaching::All)
|
|
.await?;
|
|
}
|
|
}
|
|
}
|
|
Ok(())
|
|
}
|