use std::path::{Path, PathBuf};
use std::sync::Arc;
use crate::services::SharedServices;
use crate::LibdenoError;
use crate::LibdenoOptions;
use crate::CWD_LOCK;
#[derive(Clone)]
pub struct LibdenoRuntime {
cwd: PathBuf,
state: Arc<std::sync::Mutex<RuntimeState>>,
}
struct RuntimeState {
fingerprint: Vec<(u64, u64)>,
shared: Arc<SharedServices>,
}
impl LibdenoRuntime {
pub async fn new(cwd: impl AsRef<Path>) -> Result<Self, LibdenoError> {
let cwd =
std::fs::canonicalize(cwd.as_ref()).unwrap_or_else(|_| cwd.as_ref().to_path_buf());
let shared = SharedServices::new(cwd.clone(), vec![cwd.clone()])
.await
.map_err(LibdenoError::Runtime)?;
let fingerprint = config_fingerprint(&cwd);
Ok(Self {
cwd,
state: Arc::new(std::sync::Mutex::new(RuntimeState {
fingerprint,
shared,
})),
})
}
}
pub fn run_with(
runtime: &LibdenoRuntime,
entry: impl AsRef<Path>,
options: &LibdenoOptions,
) -> Result<i32, LibdenoError> {
let _lock = CWD_LOCK.lock().unwrap_or_else(|e| e.into_inner());
crate::limits::capture_spawned_ipc_marker();
if tokio::runtime::Handle::try_current().is_ok() {
return Err(LibdenoError::Runtime(deno_core::anyhow::anyhow!(
"libdeno::run_with() cannot be called from inside a tokio runtime; \
call it from a non-async context or use run_in_subprocess"
)));
}
let rt = tokio::runtime::Builder::new_current_thread()
.enable_all()
.build()
.map_err(|e| LibdenoError::Runtime(deno_core::anyhow::anyhow!(e)))?;
rt.block_on(async {
let fp = config_fingerprint(&runtime.cwd);
let shared = {
let stale = {
let state = runtime.state.lock().unwrap_or_else(|e| e.into_inner());
fp != state.fingerprint
};
if stale {
let cwd = runtime.cwd.clone();
let rebuilt = SharedServices::new(cwd.clone(), vec![cwd])
.await
.map_err(LibdenoError::Runtime)?;
let mut state = runtime.state.lock().unwrap_or_else(|e| e.into_inner());
state.fingerprint = fp;
state.shared = rebuilt.clone();
rebuilt
} else {
runtime
.state
.lock()
.unwrap_or_else(|e| e.into_inner())
.shared
.clone()
}
};
crate::run_inner_with(shared, runtime.cwd.clone(), entry.as_ref(), options).await
})
}
fn config_fingerprint(cwd: &Path) -> Vec<(u64, u64)> {
const CONFIG_FILES: [&str; 5] = [
"deno.json",
"deno.jsonc",
"import_map.json",
"package.json",
".npmrc",
];
let mut entries = Vec::new();
let mut dir = Some(cwd.to_path_buf());
while let Some(dir_path) = dir {
for name in CONFIG_FILES {
if let Some(fp) = file_fingerprint(&dir_path.join(name)) {
entries.push(fp);
}
}
if let Some(fp) = lock_fingerprint(&dir_path.join("deno.lock")) {
entries.push(fp);
}
if let Ok(meta) = std::fs::metadata(dir_path.join("node_modules")) {
if meta.is_dir() {
if let Some(fp) = meta_fingerprint(&meta) {
entries.push((fp, 0));
}
}
}
let parent = dir_path.parent().map(|p| p.to_path_buf());
if parent.as_deref() == Some(dir_path.as_path()) {
break; }
dir = parent;
}
entries
}
fn file_fingerprint(path: &Path) -> Option<(u64, u64)> {
crate::npm_cache::content_hash(path).map(|hash| (hash, 0))
}
fn lock_fingerprint(path: &Path) -> Option<(u64, u64)> {
let meta = std::fs::metadata(path).ok()?;
let mtime = meta_fingerprint(&meta)?;
Some((mtime, meta.len()))
}
fn meta_fingerprint(meta: &std::fs::Metadata) -> Option<u64> {
meta.modified()
.ok()?
.duration_since(std::time::UNIX_EPOCH)
.ok()
.map(|d| d.as_nanos() as u64)
}
pub(crate) fn has_watcher_exited(worker: &deno_runtime::worker::MainWorker) -> bool {
worker
.js_runtime
.op_state()
.borrow()
.try_borrow::<deno_runtime::deno_os::WatcherExited>()
.is_some()
}