use crate::host::{with_host, JsObj};
use fusevm::Value;
pub const METHODS: &[&str] = &[
"isBuiltin",
"createRequire",
"wrap",
"syncBuiltinESMExports",
"runMain",
"findPackageJSON",
];
pub const MODULE_STATIC_METHODS: &[&str] = METHODS;
const BUILTIN_MODULES: &[&str] = &[
"assert",
"assert/strict",
"async_hooks",
"buffer",
"child_process",
"cluster",
"console",
"crypto",
"dgram",
"diagnostics_channel",
"dns",
"dns/promises",
"domain",
"events",
"fs",
"fs/promises",
"http",
"http2",
"https",
"inspector",
"module",
"net",
"os",
"path",
"path/posix",
"path/win32",
"perf_hooks",
"process",
"punycode",
"querystring",
"readline",
"repl",
"stream",
"stream/consumers",
"string_decoder",
"timers",
"timers/promises",
"tls",
"trace_events",
"tty",
"url",
"util",
"util/types",
"v8",
"vm",
"wasi",
"worker_threads",
"zlib",
];
const CREATE_REQUIRE_SRC: &str = "(function (p) {\n\
if (typeof p !== 'string') { p = String(p); }\n\
if (p.indexOf('file://') === 0) { p = require('url').fileURLToPath(p); }\n\
var dir = require('path').dirname(p);\n\
var req = function (spec) { return __cjs_require(spec, dir); };\n\
req.resolve = function (spec) { return __cjs_resolve(spec, dir); };\n\
req.cache = {};\n\
req.main = undefined;\n\
req.extensions = {};\n\
return req;\n\
})";
pub fn call(method: &str, args: &[Value]) -> Option<Result<Value, String>> {
Some(match method {
"isBuiltin" => Ok(is_builtin(args)),
"createRequire" => create_require(args),
"wrap" => Ok(wrap(args)),
"syncBuiltinESMExports" => Ok(Value::Undef),
"runMain" => Ok(Value::Undef),
"findPackageJSON" => Ok(find_package_json(args)),
_ => return None,
})
}
pub fn static_call(method: &str, args: &[Value]) -> Option<Result<Value, String>> {
call(method, args)
}
pub fn constant(name: &str) -> Option<Value> {
match name {
"builtinModules" => Some(builtin_modules_array()),
"Module" => Some(with_host(|h| h.alloc(JsObj::Builtin("Module".into())))),
_ => None,
}
}
pub fn static_constant(name: &str) -> Option<Value> {
match name {
"builtinModules" => Some(builtin_modules_array()),
"Module" => Some(with_host(|h| h.alloc(JsObj::Builtin("Module".into())))),
_ => None,
}
}
fn is_builtin(args: &[Value]) -> Value {
let name = super::arg_str(args, 0);
let base = name.strip_prefix("node:").unwrap_or(&name);
Value::Bool(crate::stdlib::resolve(base).is_some() || name.starts_with("node:"))
}
fn create_require(args: &[Value]) -> Result<Value, String> {
let factory = run_completion(CREATE_REQUIRE_SRC)?;
let p = args.first().cloned().unwrap_or(Value::Undef);
crate::host::invoke(&factory, vec![p], None)
}
fn wrap(args: &[Value]) -> Value {
let src = super::arg_str(args, 0);
with_host(|h| {
h.new_str(format!(
"(function (exports, require, module, __filename, __dirname) {{ {src}\n}});"
))
})
}
fn find_package_json(args: &[Value]) -> Value {
use std::path::{Path, PathBuf};
let strip = |s: String| {
s.strip_prefix("file://")
.map(|x| x.to_string())
.unwrap_or(s)
};
let spec = strip(super::arg_str(args, 0));
let base = if args.len() > 1 {
Some(strip(super::arg_str(args, 1)))
} else {
None
};
let start: PathBuf = {
let p = Path::new(&spec);
if p.is_absolute() {
p.to_path_buf()
} else if let Some(b) = base.as_deref() {
let bp = Path::new(b);
let bdir = if bp.is_dir() {
bp
} else {
bp.parent().unwrap_or(bp)
};
bdir.join(&spec)
} else {
std::env::current_dir().unwrap_or_default().join(&spec)
}
};
let mut dir = if start.is_dir() {
Some(start.as_path())
} else {
start.parent()
};
while let Some(d) = dir {
let cand = d.join("package.json");
if cand.is_file() {
return with_host(|h| h.new_str(cand.to_string_lossy().to_string()));
}
dir = d.parent();
}
Value::Undef
}
fn builtin_modules_array() -> Value {
with_host(|h| {
let items: Vec<Value> = BUILTIN_MODULES.iter().map(|s| h.new_str(*s)).collect();
h.new_array(items)
})
}
fn run_completion(src: &str) -> Result<Value, String> {
let prog = crate::compile_completion(src)?;
let chunk = crate::load_merged(prog);
crate::host::run_chunk_on(chunk)
}