use std::path::PathBuf;
use std::sync::Arc;
use deno_cache_dir::file_fetcher::CacheSetting;
use deno_cache_dir::file_fetcher::NullBlobStore;
use deno_config::deno_json::NodeModulesDirMode;
use deno_graph::GraphKind;
use deno_graph::ModuleGraph;
use deno_npm_installer::graph::NpmCachingStrategy;
use deno_npm_installer::lifecycle_scripts::LifecycleScriptsExecutorOptions;
use deno_npm_installer::LogReporter;
use deno_npm_installer::NpmInstallerFactory;
use deno_npm_installer::NpmInstallerFactoryOptions;
use deno_resolver::cjs::IsCjsResolutionMode;
use deno_resolver::deno_json::CompilerOptionsOverrides;
use deno_resolver::factory::ConfigDiscoveryOption;
use deno_resolver::factory::ResolverFactory;
use deno_resolver::factory::ResolverFactoryOptions;
use deno_resolver::factory::WorkspaceFactory;
use deno_resolver::factory::WorkspaceFactoryOptions;
use deno_resolver::file_fetcher::PermissionedFileFetcher;
use deno_resolver::file_fetcher::PermissionedFileFetcherOptions;
use deno_resolver::loader::AllowJsonImports;
use deno_resolver::loader::MemoryFiles;
use node_resolver::analyze::NodeCodeTranslatorMode;
use sys_traits::impls::RealSys;
use crate::graph::GraphResolver;
use crate::http::ReqwestHttpClient;
#[derive(Debug)]
pub struct NpmProcessStateProviderImpl {
kind: NpmProcessStateKind,
local_node_modules_path: Option<String>,
}
#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]
enum NpmProcessStateKind {
Snapshot(deno_npm::resolution::SerializedNpmResolutionSnapshot),
Byonm,
}
#[derive(Debug, serde::Serialize, serde::Deserialize)]
struct NpmProcessState {
kind: NpmProcessStateKind,
local_node_modules_path: Option<String>,
}
impl deno_runtime::deno_process::NpmProcessStateProvider for NpmProcessStateProviderImpl {
fn get_npm_process_state(&self) -> String {
match deno_core::serde_json::to_string(&NpmProcessState {
kind: self.kind.clone(),
local_node_modules_path: self.local_node_modules_path.clone(),
}) {
Ok(json) => json,
Err(e) => {
eprintln!("libdeno: failed to serialize npm process state: {e}");
"{}".to_string()
}
}
}
}
#[derive(Debug)]
pub struct ShellLifecycleScriptsExecutor;
#[async_trait::async_trait(?Send)]
impl deno_npm_installer::lifecycle_scripts::LifecycleScriptsExecutor
for ShellLifecycleScriptsExecutor
{
async fn execute(&self, options: LifecycleScriptsExecutorOptions<'_>) -> anyhow::Result<()> {
let root_bin = options.root_node_modules_dir_path.join(".bin");
for pkg in options.packages_with_scripts {
for event in ["preinstall", "install", "postinstall"] {
let Some(script) = pkg.scripts.get(event) else {
continue;
};
let cwd = pkg
.init_cwds
.first()
.map(|p| p.as_path())
.unwrap_or(pkg.package_folder.as_path());
let pkg_bin = pkg.package_folder.join("node_modules").join(".bin");
let path = format!(
"{}:{}:{}",
pkg_bin.display(),
root_bin.display(),
std::env::var("PATH").unwrap_or_default(),
);
let name = pkg.package.id.nv.name.as_str();
let status = tokio::process::Command::new("sh")
.arg("-c")
.arg(script)
.current_dir(cwd)
.env("PATH", &path)
.env("INIT_CWD", cwd)
.env("npm_lifecycle_event", event)
.env("npm_lifecycle_script", script)
.env("npm_package_name", name)
.env("npm_package_version", pkg.package.id.nv.version.to_string())
.status()
.await?;
if !status.success() {
return Err(anyhow::anyhow!(
"lifecycle script `{event}` for package `{name}` failed with {status}"
));
}
}
(options.on_ran_pkg_scripts)(pkg.package)
.map_err(|e| anyhow::anyhow!(e.to_string()))?;
}
Ok(())
}
}
pub fn create_npm_process_state_provider(
resolver_factory: &Arc<ResolverFactory<RealSys>>,
) -> deno_core::anyhow::Result<deno_runtime::deno_process::NpmProcessStateProviderRc> {
use deno_resolver::npm::NpmResolver;
match resolver_factory.npm_resolver()? {
NpmResolver::Managed(managed) => {
let resolution = managed.resolution();
Ok(deno_fs::sync::MaybeArc::new(NpmProcessStateProviderImpl {
kind: NpmProcessStateKind::Snapshot(
resolution.serialized_valid_snapshot().into_serialized(),
),
local_node_modules_path: managed
.root_node_modules_path()
.map(|p| p.to_string_lossy().to_string()),
}))
}
NpmResolver::Byonm(byonm) => {
Ok(deno_fs::sync::MaybeArc::new(NpmProcessStateProviderImpl {
kind: NpmProcessStateKind::Byonm,
local_node_modules_path: byonm
.root_node_modules_path()
.map(|p| p.to_string_lossy().to_string()),
}))
}
}
}
pub type RealFileFetcher = PermissionedFileFetcher<NullBlobStore, RealSys, ReqwestHttpClient>;
pub type RealGraphLoader =
deno_resolver::file_fetcher::DenoGraphLoader<NullBlobStore, RealSys, ReqwestHttpClient>;
pub type RealNpmInstallerFactory = NpmInstallerFactory<ReqwestHttpClient, LogReporter, RealSys>;
pub struct SharedServices {
pub sys: RealSys,
pub resolver_factory: Arc<ResolverFactory<RealSys>>,
pub file_fetcher: Arc<RealFileFetcher>,
pub graph_loader: Arc<RealGraphLoader>,
pub graph: Arc<tokio::sync::Mutex<ModuleGraph>>,
pub graph_resolver: Arc<GraphResolver>,
pub npm_process_state_provider: deno_runtime::deno_process::NpmProcessStateProviderRc,
}
pub struct RuntimeServices {
pub shared: Arc<SharedServices>,
}
impl RuntimeServices {
pub async fn new(
initial_cwd: PathBuf,
config_start_paths: Vec<PathBuf>,
permissions: deno_runtime::deno_permissions::PermissionsContainer,
) -> deno_core::anyhow::Result<Self> {
let sys = RealSys;
let workspace_factory = Arc::new(WorkspaceFactory::new(
sys.clone(),
initial_cwd,
WorkspaceFactoryOptions {
config_discovery: ConfigDiscoveryOption::Discover {
start_paths: config_start_paths,
},
node_modules_dir: Some(NodeModulesDirMode::Auto),
..Default::default()
},
));
let resolver_factory = Arc::new(ResolverFactory::new(
workspace_factory.clone(),
ResolverFactoryOptions {
compiler_options_overrides: CompilerOptionsOverrides {
no_transpile: false,
force_check_js: false,
source_map_base: None,
preserve_jsx: false,
force_disable_verbatim_module_syntax: true,
},
is_cjs_resolution_mode: IsCjsResolutionMode::Disabled,
node_code_translator_mode: NodeCodeTranslatorMode::ModuleLoader,
node_resolver_options: Default::default(),
npm_system_info: Default::default(),
allow_json_imports: AllowJsonImports::WithAttribute,
require_modules: vec![],
newest_dependency_date: None,
node_analysis_cache: None,
node_resolution_cache: None,
package_json_cache: None,
package_json_dep_resolution: None,
specified_import_map: None,
unstable_sloppy_imports: false,
on_mapped_resolution_diagnostic: None,
},
));
#[allow(clippy::arc_with_non_send_sync)]
let npm_installer_factory = Arc::new(NpmInstallerFactory::new(
resolver_factory.clone(),
Arc::new(ReqwestHttpClient::default()),
Arc::new(ShellLifecycleScriptsExecutor),
LogReporter,
None,
NpmInstallerFactoryOptions {
cache_setting: deno_npm_cache::NpmCacheSetting::Use,
caching_strategy: NpmCachingStrategy::Lazy,
clean_on_install: false,
dedup_lockfile_peer_variants: false,
lifecycle_scripts_config: deno_npm_installer::LifecycleScriptsConfig {
allowed: deno_npm_installer::PackagesAllowedScripts::None,
denied: vec![],
initial_cwd: Default::default(),
root_dir: Default::default(),
explicit_install: false,
},
production: false,
skip_types: true,
resolve_npm_resolution_snapshot: Box::new(|| Ok(None)),
},
));
let memory_files = Arc::new(MemoryFiles::default());
let http_cache = Arc::new(workspace_factory.http_cache()?.clone());
let file_fetcher = Arc::new(PermissionedFileFetcher::new(
NullBlobStore,
http_cache,
ReqwestHttpClient::default(),
memory_files,
sys.clone(),
PermissionedFileFetcherOptions {
allow_remote: true,
cache_setting: CacheSetting::Use,
},
));
let in_npm_pkg_checker = resolver_factory.in_npm_package_checker()?.clone();
let global_http_cache = workspace_factory.global_http_cache()?.clone();
let graph_loader = Arc::new(deno_resolver::file_fetcher::DenoGraphLoader::new(
file_fetcher.clone(),
global_http_cache,
in_npm_pkg_checker,
sys.clone(),
deno_resolver::file_fetcher::DenoGraphLoaderOptions {
file_header_overrides: Default::default(),
include_npm_sources: false,
permissions: Some(permissions.clone()),
file_permission_api_name: None,
reporter: None,
},
));
let graph = Arc::new(tokio::sync::Mutex::new(ModuleGraph::new(
GraphKind::CodeOnly,
)));
npm_installer_factory
.initialize_npm_resolution_if_managed()
.await?;
let graph_resolver = Arc::new(
GraphResolver::new(resolver_factory.clone(), npm_installer_factory.clone()).await?,
);
let npm_process_state_provider = create_npm_process_state_provider(&resolver_factory)?;
Ok(Self {
shared: Arc::new(SharedServices {
sys,
resolver_factory,
file_fetcher,
graph_loader,
graph,
graph_resolver,
npm_process_state_provider,
}),
})
}
}