mod graph;
mod http;
mod module_loader;
mod node_loader;
mod permissions;
mod services;
mod subprocess;
mod worker_factory;
use std::path::Path;
use std::path::PathBuf;
use std::rc::Rc;
use std::sync::Arc;
use deno_core::error::AnyError;
use deno_core::ModuleSpecifier;
use deno_runtime::deno_fs::FileSystem;
use deno_runtime::deno_fs::RealFs;
use deno_runtime::deno_inspector_server::MainInspectorSessionChannel;
use deno_runtime::deno_node::NodeExtInitServices;
use deno_runtime::deno_web::BlobStore;
use deno_runtime::deno_web::InMemoryBroadcastChannel;
use deno_runtime::worker::MainWorker;
use deno_runtime::worker::WorkerOptions;
use deno_runtime::worker::WorkerServiceOptions;
use deno_runtime::BootstrapOptions;
pub use subprocess::maybe_handle_child_mode;
pub use subprocess::run_in_subprocess;
use module_loader::GraphModuleLoader;
use node_loader::SimpleNodeRequireLoader;
use permissions::build_permissions;
use services::RuntimeServices;
use sys_traits::impls::RealSys;
use worker_factory::create_web_worker_factory;
struct CwdGuard(Option<PathBuf>);
impl CwdGuard {
fn set(cwd: &Path) -> Self {
let prev = std::env::current_dir().ok();
if std::env::set_current_dir(cwd).is_err() {
eprintln!(
"libdeno: failed to chdir to cwd {}; relative paths resolve against the host cwd",
cwd.display()
);
return Self(None);
}
Self(prev)
}
}
impl Drop for CwdGuard {
fn drop(&mut self) {
if let Some(prev) = &self.0 {
let _ = std::env::set_current_dir(prev);
}
}
}
pub(crate) static CWD_LOCK: std::sync::Mutex<()> = std::sync::Mutex::new(());
include!(concat!(env!("OUT_DIR"), "/EXTENSION_RESIDUAL_SOURCES.rs"));
static STARTUP_SNAPSHOT: &[u8] = include_bytes!(concat!(env!("OUT_DIR"), "/CLI_SNAPSHOT.bin"));
#[derive(Debug, Clone, Default)]
pub struct LibdenoOptions {
pub permissions: Vec<String>,
pub args: Vec<String>,
pub cwd: Option<PathBuf>,
}
#[derive(Debug, thiserror::Error)]
pub enum LibdenoError {
#[error("failed to resolve entry module: {0}")]
Entry(AnyError),
#[error("invalid permission flags: {0}")]
Permission(String),
#[error("{0}")]
Runtime(#[from] AnyError),
#[error("{0}")]
Core(#[from] deno_core::error::CoreError),
#[error("{0}")]
Js(#[from] Box<deno_core::error::JsError>),
#[error("{0}")]
Io(#[from] std::io::Error),
}
pub fn run(entry: impl AsRef<Path>, options: &LibdenoOptions) -> Result<i32, LibdenoError> {
let _lock = CWD_LOCK.lock().unwrap_or_else(|e| e.into_inner());
let runtime = tokio::runtime::Builder::new_current_thread()
.enable_all()
.build()
.map_err(|e| LibdenoError::Runtime(deno_core::anyhow::anyhow!(e)))?;
runtime.block_on(run_inner(entry.as_ref(), options))
}
async fn run_inner(entry: &Path, options: &LibdenoOptions) -> Result<i32, LibdenoError> {
let _ = deno_runtime::deno_tls::rustls::crypto::CryptoProvider::install_default(
deno_runtime::deno_tls::rustls::crypto::aws_lc_rs::default_provider(),
);
let cwd = options.cwd.clone().unwrap_or(std::env::current_dir()?);
let _cwd_guard = CwdGuard::set(&cwd);
let main_module = resolve_entry(entry, &cwd).map_err(LibdenoError::Entry)?;
let fs: Arc<dyn FileSystem> = Arc::new(RealFs);
let config_start_paths = main_module
.to_file_path()
.ok()
.and_then(|p| p.parent().map(|d| vec![d.to_path_buf()]))
.unwrap_or_else(|| vec![cwd.clone()]);
let permission_parser: Arc<
deno_runtime::deno_permissions::RuntimePermissionDescriptorParser<RealSys>,
> = Arc::new(deno_runtime::deno_permissions::RuntimePermissionDescriptorParser::new(RealSys));
let permissions = build_permissions(&options.permissions, permission_parser.clone(), &cwd)?;
let services = Arc::new(
RuntimeServices::new(cwd, config_start_paths, permissions.clone())
.await
.map_err(LibdenoError::Runtime)?,
);
let has_node_modules_dir = {
use deno_resolver::npm::NpmResolver;
match services
.shared
.resolver_factory
.npm_resolver()
.map_err(LibdenoError::Runtime)?
{
NpmResolver::Managed(managed) => managed.root_node_modules_path().is_some(),
NpmResolver::Byonm(byonm) => byonm.root_node_modules_path().is_some(),
}
};
let module_loader: Rc<dyn deno_core::ModuleLoader> = Rc::new(GraphModuleLoader::new(
services.shared.clone(),
permissions.clone(),
));
let node_resolver = services.shared.resolver_factory.node_resolver()?.clone();
let pkg_json_resolver = services.shared.resolver_factory.pkg_json_resolver().clone();
let cjs_tracker = services.shared.resolver_factory.cjs_tracker()?.clone();
let in_npm_pkg_checker = services
.shared
.resolver_factory
.in_npm_package_checker()?
.clone();
let node_require_loader: deno_runtime::deno_node::NodeRequireLoaderRc = Rc::new(
SimpleNodeRequireLoader::new(cjs_tracker, in_npm_pkg_checker),
);
let node_sys = services.shared.sys.clone();
let node_services = Some(NodeExtInitServices {
node_require_loader: node_require_loader.clone(),
node_resolver: node_resolver.clone(),
pkg_json_resolver: pkg_json_resolver.clone(),
sys: node_sys.clone(),
});
let blob_store = BlobStore::default_arc();
let broadcast_channel = InMemoryBroadcastChannel::default();
const ENABLED_FEATURES: &[&str] = &["kv", "cron", "ffi", "webgpu"];
let feature_checker = {
let mut fc = deno_runtime::FeatureChecker::default();
for feature in ENABLED_FEATURES {
fc.enable_feature(feature);
}
Arc::new(fc)
};
let unstable_ids: Vec<i32> = deno_features::UNSTABLE_FEATURES
.iter()
.filter(|f| ENABLED_FEATURES.contains(&f.name))
.map(|f| f.id)
.collect();
let worker_services = WorkerServiceOptions {
blob_store: blob_store.clone(),
broadcast_channel: broadcast_channel.clone(),
deno_rt_native_addon_loader: None,
feature_checker: feature_checker.clone(),
fs: fs.clone(),
module_loader: module_loader.clone(),
node_services: node_services.clone(),
npm_process_state_provider: Some(services.shared.npm_process_state_provider.clone()),
permissions: permissions.clone(),
root_cert_store_provider: None,
fetch_dns_resolver: deno_runtime::deno_fetch::dns::Resolver::default(),
shared_array_buffer_store: None,
compiled_wasm_module_store: None,
v8_code_cache: None,
bundle_provider: None,
};
const DENO_VERSION: &str = "2.9.5";
let main_bootstrap = BootstrapOptions {
deno_version: DENO_VERSION.to_string(),
user_agent: format!(
"libdeno/{}/Deno/{}",
env!("CARGO_PKG_VERSION"),
DENO_VERSION
),
has_node_modules_dir,
location: Some(main_module.clone()),
args: options.args.clone(),
unstable_features: unstable_ids,
node_ipc_init: std::env::var("NODE_CHANNEL_FD")
.ok()
.and_then(|v| v.parse::<i64>().ok())
.map(|fd| {
let serialization =
match std::env::var("NODE_CHANNEL_SERIALIZATION_MODE").as_deref() {
Ok("advanced") => {
deno_runtime::deno_node::ops::ipc::ChildIpcSerialization::Advanced
}
_ => deno_runtime::deno_node::ops::ipc::ChildIpcSerialization::Json,
};
(fd, serialization)
}),
..Default::default()
};
let create_web_worker_cb: Arc<deno_runtime::ops::worker_host::CreateWebWorkerCb> =
create_web_worker_factory(
blob_store,
broadcast_channel,
feature_checker,
fs.clone(),
services.shared.clone(),
MainInspectorSessionChannel::default(),
main_bootstrap.clone(),
)?;
let options = WorkerOptions {
bootstrap: main_bootstrap,
create_web_worker_cb,
extensions: vec![],
startup_snapshot: Some(STARTUP_SNAPSHOT),
residual_lazy_js_sources: RESIDUAL_LAZY_JS,
residual_lazy_esm_sources: RESIDUAL_LAZY_ESM,
..Default::default()
};
let mut worker = MainWorker::bootstrap_from_options(&main_module, worker_services, options);
let isolate_handle = worker.js_runtime.v8_isolate().thread_safe_handle();
worker
.js_runtime
.op_state()
.borrow_mut()
.put(deno_runtime::deno_os::WatcherExitHandle(isolate_handle));
let run_result: Result<(), LibdenoError> = async {
worker.execute_main_module(&main_module).await?;
worker.run_event_loop(false).await?;
worker.dispatch_load_event()?;
worker.run_event_loop(false).await?;
worker.dispatch_beforeunload_event()?;
worker.dispatch_unload_event()?;
worker.dispatch_process_beforeexit_event()?;
worker.dispatch_process_exit_event()?;
Ok(())
}
.await;
match run_result {
Ok(()) => Ok(worker.exit_code()),
Err(_)
if worker
.js_runtime
.op_state()
.borrow()
.try_borrow::<deno_runtime::deno_os::WatcherExited>()
.is_some() =>
{
Ok(worker.exit_code())
}
Err(e) => Err(e),
}
}
fn resolve_entry(path: &Path, cwd: &Path) -> Result<ModuleSpecifier, AnyError> {
let path = if path.is_absolute() {
path.to_path_buf()
} else {
cwd.join(path)
};
let file_path = if path.is_dir() {
let pkg = path.join("package.json");
if pkg.exists() {
package_main(&pkg)?
} else {
path.join("index.js")
}
} else if path.file_name().and_then(|n| n.to_str()) == Some("package.json") {
package_main(&path)?
} else {
path
};
ModuleSpecifier::from_file_path(&file_path)
.map_err(|_| deno_core::anyhow::anyhow!("Invalid entry file path: {}", file_path.display()))
}
fn package_main(pkg_path: &Path) -> Result<PathBuf, AnyError> {
let text = std::fs::read_to_string(pkg_path)?;
let json: deno_core::serde_json::Value = deno_core::serde_json::from_str(&text)?;
let main = json
.get("main")
.and_then(|v| v.as_str())
.unwrap_or("index.js");
Ok(pkg_path.parent().unwrap_or(Path::new(".")).join(main))
}
#[cfg(test)]
mod tests {
use super::*;
fn temp_dir(name: &str) -> PathBuf {
let dir =
std::env::temp_dir().join(format!("libdeno-test-{}-{}", std::process::id(), name));
std::fs::create_dir_all(&dir).unwrap();
dir
}
#[test]
fn resolve_entry_file() {
let dir = temp_dir("entry-file");
let file = dir.join("app.js");
std::fs::write(&file, "").unwrap();
let spec = resolve_entry(&file, &dir).unwrap();
assert_eq!(spec.to_file_path().unwrap(), file);
let _ = std::fs::remove_dir_all(&dir);
}
#[test]
fn resolve_entry_directory_uses_package_main() {
let dir = temp_dir("entry-dir-pkg");
std::fs::write(
dir.join("package.json"),
r#"{"name":"app","main":"src/main.js"}"#,
)
.unwrap();
std::fs::create_dir_all(dir.join("src")).unwrap();
std::fs::write(dir.join("src/main.js"), "").unwrap();
let spec = resolve_entry(&dir, &dir).unwrap();
assert_eq!(spec.to_file_path().unwrap(), dir.join("src/main.js"));
let _ = std::fs::remove_dir_all(&dir);
}
#[test]
fn resolve_entry_directory_defaults_to_index_js() {
let dir = temp_dir("entry-dir-default");
std::fs::write(dir.join("index.js"), "").unwrap();
let spec = resolve_entry(&dir, &dir).unwrap();
assert_eq!(spec.to_file_path().unwrap(), dir.join("index.js"));
let _ = std::fs::remove_dir_all(&dir);
}
#[test]
fn resolve_entry_package_json_directly() {
let dir = temp_dir("entry-pkgjson");
std::fs::write(dir.join("package.json"), r#"{"main":"lib/index.js"}"#).unwrap();
std::fs::create_dir_all(dir.join("lib")).unwrap();
std::fs::write(dir.join("lib/index.js"), "").unwrap();
let spec = resolve_entry(&dir.join("package.json"), &dir).unwrap();
assert_eq!(spec.to_file_path().unwrap(), dir.join("lib/index.js"));
let _ = std::fs::remove_dir_all(&dir);
}
#[test]
fn resolve_entry_relative_path_joins_cwd() {
let dir = temp_dir("entry-relative");
std::fs::write(dir.join("app.ts"), "").unwrap();
let spec = resolve_entry(Path::new("app.ts"), &dir).unwrap();
assert_eq!(spec.to_file_path().unwrap(), dir.join("app.ts"));
let _ = std::fs::remove_dir_all(&dir);
}
}