amberjs 1.17.0

JavaScript/TypeScript runtime built with Rust and V8
Documentation

use anyhow::{Result, anyhow};
use std::collections::{BTreeMap, HashMap};
use std::path::{Path, PathBuf};
use std::fs::File;
use rusty_v8 as v8;
use std::task::Context;

/// Node.js compatibility module for V8
/// Provides fs, path, process and other Node.js core modules
/// Set up all Node.js compatibility globals
pub fn setup_nodejs_apis(scope: &mut v8::ContextScope<v8::HandleScope>) -> Result<()> {
    setup_process(scope)?;
    setup_path(scope)?;
    setup_fs(scope)?;
    setup_module_system(scope)?;
    Ok(())
}
/// Process module implementation
fn setup_process(scope: &mut v8::ContextScope<v8::HandleScope>) -> Result<()> {
    let process: _ = v8::Object::new(scope);
    // process.argv - use Array instead of Object
    let argv: _ = v8::Array::new_with_length(scope, 2);
    // In a real implementation, these would come from actual CLI args
    let val_0: _ = v8::String::new(scope, "amber").unwrap().into();
    argv.set_index(scope, 0, val_0);
    let val_1: _ = v8::String::new(scope, "<eval>").unwrap().into();
    argv.set_index(scope, 1, val_1);
    let process_key: _ = v8::String::new(scope, "process").unwrap();
    let global: _ = scope.global();
    global.set(scope, process_key.clone(), process.clone().into())?;
    process.set(scope, "argv", argv.into())?;
    // process.version
    process.set(scope, "version", v8::String::new(scope, "1.0.0-amber").unwrap().into())?;
    // process.cwd()
    let cwd_func: _ = v8::FunctionTemplate::new(scope, |scope: &mut v8::PinScope, _args: v8::FunctionCallbackArguments, mut retval: v8::ReturnValue| {
        let result: _ = match env::current_dir() {
            Ok(path) => path.to_string_lossy().to_string(),
            Err(_) => ".".to_string(),
        };
        retval.set(v8::String::new(scope, &result).unwrap().into());
    });
    let cwd_func_instance: _ = cwd_func.get_function(scope).unwrap();
    process.set(scope, "cwd", cwd_func_instance.into())?;
    // process.nextTick()
    let next_tick_func: _ = v8::FunctionTemplate::new(scope, |_scope: &mut v8::PinScope, _args: v8::FunctionCallbackArguments, mut retval: v8::ReturnValue| {
        // Simple implementation - execute callback immediately
        // In a real implementation, this would use a task queue
        retval.set_undefined();
    });
    let next_tick_func_instance: _ = next_tick_func.get_function(scope).unwrap();
    process.set(scope, "nextTick", next_tick_func_instance.into())?;
    // process.env
    let env: _ = v8::Object::new(scope);
    for (key, value) in env::vars() {
        env.set(scope, v8::String::new(scope, &key).unwrap().into(), v8::String::new(scope, &value).unwrap().into())?;
    }
    process.set(scope, "env", env.into())?;
    Ok(())
}
/// Path module implementation
fn setup_path(scope: &mut v8::ContextScope<v8::HandleScope>) -> Result<()> {
    let path: _ = v8::Object::new(scope);
    // path.join() - accept multiple string arguments
    let join_func: _ = v8::FunctionTemplate::new(scope, |scope: &mut v8::PinScope, args: v8::FunctionCallbackArguments, mut retval: v8::ReturnValue| {
        let mut paths = Vec::new();
        for i in 0..args.length() {
            let arg: _ = args.get(i);
            let arg_str: _ = arg.to_string(scope)
                .unwrap_or_else(|| v8::String::new(scope, "<error>").unwrap())
                .to_rust_string_lossy(scope);
            if !arg_str.is_empty() {
                paths.push(arg_str);
            }
        }
        let result: _ = paths.join("/");
        retval.set(v8::String::new(scope, &result).unwrap().into());
    });
    let join_func_instance: _ = join_func.get_function(scope).unwrap();
    path.set(scope, "join", join_func_instance.into())?;
    // path.resolve()
    let resolve_func: _ = v8::FunctionTemplate::new(scope, |scope: &mut v8::PinScope, args: v8::FunctionCallbackArguments, mut retval: v8::ReturnValue| {
        let mut paths = Vec::new();
        for i in 0..args.length() {
            let arg: _ = args.get(i);
            let arg_str: _ = arg.to_string(scope)
                .unwrap_or_else(|| v8::String::new(scope, "<error>").unwrap())
                .to_rust_string_lossy(scope);
            if !arg_str.is_empty() {
                paths.push(arg_str);
            }
        }
        let mut result = PathBuf::new();
        for p in paths {
            let path: _ = Path::new(&p);
            if path.is_absolute() {
                result = path.to_path_buf();
            } else {
                result = result.join(path);
            }
        }
        // If no paths provided, return current directory
        if result.as_os_str().is_empty() {
            result = env::current_dir().unwrap_or_else(|_| PathBuf::from("."));
        }
        let result_str: _ = result.to_string_lossy().to_string();
        retval.set(v8::String::new(scope, &result_str).unwrap().into());
    });
    let resolve_func_instance: _ = resolve_func.get_function(scope).unwrap();
    path.set(scope, "resolve", resolve_func_instance.into())?;
    // path.dirname()
    let dirname_func: _ = v8::FunctionTemplate::new(scope, |scope: &mut v8::PinScope, args: v8::FunctionCallbackArguments, mut retval: v8::ReturnValue| {
        let arg: _ = args.get(0);
        let arg_str: _ = arg.to_string(scope)
            .unwrap_or_else(|| v8::String::new(scope, "<error>").unwrap())
            .to_rust_string_lossy(scope);
        let path: _ = Path::new(&arg_str);
        let result: _ = if let Some(parent) = path.parent() {
            parent.to_string_lossy().to_string()
        } else {
            ".".to_string()
        };
        retval.set(v8::String::new(scope, &result).unwrap().into());
    });
    let dirname_func_instance: _ = dirname_func.get_function(scope).unwrap();
    path.set(scope, "dirname", dirname_func_instance.into())?;
    // path.basename()
    let basename_func: _ = v8::FunctionTemplate::new(scope, |scope: &mut v8::PinScope, args: v8::FunctionCallbackArguments, mut retval: v8::ReturnValue| {
        let arg: _ = args.get(0);
        let arg_str: _ = arg.to_string(scope)
            .unwrap_or_else(|| v8::String::new(scope, "<error>").unwrap())
            .to_rust_string_lossy(scope);
        let path: _ = Path::new(&arg_str);
        let result: _ = path.file_name()
            .and_then(|s| s.to_str())
            .unwrap_or(&arg_str)
            .to_string();
        retval.set(v8::String::new(scope, &result).unwrap().into());
    });
    let basename_func_instance: _ = basename_func.get_function(scope).unwrap();
    path.set(scope, "basename", basename_func_instance.into())?;
    // path.extname()
    let extname_func: _ = v8::FunctionTemplate::new(scope, |scope: &mut v8::PinScope, args: v8::FunctionCallbackArguments, mut retval: v8::ReturnValue| {
        let arg: _ = args.get(0);
        let arg_str: _ = arg.to_string(scope)
            .unwrap_or_else(|| v8::String::new(scope, "<error>").unwrap())
            .to_rust_string_lossy(scope);
        let path: _ = Path::new(&arg_str);
        let result: _ = path.extension()
            .and_then(|s| {
                let ext: _ = s.to_str()?;
                Some(format!(".{}", ext))
            })
            .unwrap_or_else(|| "".to_string());
        retval.set(v8::String::new(scope, &result).unwrap().into());
    });
    let extname_func_instance: _ = extname_func.get_function(scope).unwrap();
    path.set(scope, "extname", extname_func_instance.into())?;
    let global: _ = scope.global();
    let global_key: _ = v8::String::new(scope, "path").unwrap();
    let global_val: _ = path.into(;
    global.set(scope, global_key.into(), global_val);)?;
    Ok(())
}
/// File System module implementation
fn setup_fs(scope: &mut v8::ContextScope<v8::HandleScope>) -> Result<()> {
    let fs_obj: _ = v8::Object::new(scope);
    // fs.readFileSync()
    let read_file_sync: _ = v8::FunctionTemplate::new(scope, |scope: &mut v8::PinScope, args: v8::FunctionCallbackArguments, mut retval: v8::ReturnValue| {
        let path: _ = args.get(0);
        let _encoding: _ = args.get(1); // Not used in simple implementation
        let path_str: _ = path.to_string(scope)
            .unwrap_or_else(|| v8::String::new(scope, "<error>").unwrap())
            .to_rust_string_lossy(scope);
        let content: _ = match fs::read_to_string(&path_str) {
            Ok(content) => content,
            Err(_) => "".to_string(),
        };
        retval.set(v8::String::new(scope, &content).unwrap().into());
    });
    let read_file_sync_instance: _ = read_file_sync.get_function(scope).unwrap();
    fs_obj.set(scope, "readFileSync", read_file_sync_instance.into())?;
    // fs.writeFileSync()
    let write_file_sync: _ = v8::FunctionTemplate::new(scope, |scope: &mut v8::PinScope, args: v8::FunctionCallbackArguments, mut retval: v8::ReturnValue| {
        let path: _ = args.get(0);
        let data: _ = args.get(1);
        let _encoding: _ = args.get(2); // Not used in simple implementation
        let path_str: _ = path.to_string(scope)
            .unwrap_or_else(|| v8::String::new(scope, "<error>").unwrap())
            .to_rust_string_lossy(scope);
        let data_str: _ = data.to_string(scope)
            .unwrap_or_else(|| v8::String::new(scope, "<error>").unwrap())
            .to_rust_string_lossy(scope);
        let _: _ = fs::write(&path_str, data_str);
        retval.set_undefined();
    });
    let write_file_sync_instance: _ = write_file_sync.get_function(scope).unwrap();
    fs_obj.set(scope, "writeFileSync", write_file_sync_instance.into())?;
    // fs.existsSync()
    let exists_sync: _ = v8::FunctionTemplate::new(scope, |scope: &mut v8::PinScope, args: v8::FunctionCallbackArguments, mut retval: v8::ReturnValue| {
        let path: _ = args.get(0);
        let path_str: _ = path.to_string(scope)
            .unwrap_or_else(|| v8::String::new(scope, "<error>").unwrap())
            .to_rust_string_lossy(scope);
        let result: _ = Path::new(&path_str).exists();
        retval.set(v8::Boolean::new(scope, result).into());
    });
    let exists_sync_instance: _ = exists_sync.get_function(scope).unwrap();
    fs_obj.set(scope, "existsSync", exists_sync_instance.into())?;
    // fs.mkdirSync()
    let mkdir_sync: _ = v8::FunctionTemplate::new(scope, |_scope: &mut v8::PinScope, args: v8::FunctionCallbackArguments, mut retval: v8::ReturnValue| {
        let path: _ = args.get(0);
        let path_str: _ = path.to_string(_scope)
            .unwrap_or_else(|| v8::String::new(_scope, "<error>").unwrap())
            .to_rust_string_lossy(_scope);
        let _: _ = fs::create_dir_all(&path_str);
        retval.set_undefined();
    });
    let mkdir_sync_instance: _ = mkdir_sync.get_function(_scope).unwrap();
    fs_obj.set(scope, "mkdirSync", mkdir_sync_instance.into())?;
    // fs.readdirSync()
    let readdir_sync: _ = v8::FunctionTemplate::new(scope, |scope: &mut v8::PinScope, args: v8::FunctionCallbackArguments, mut retval: v8::ReturnValue| {
        let path: _ = args.get(0);
        let path_str: _ = path.to_string(scope)
            .unwrap_or_else(|| v8::String::new(scope, "<error>").unwrap())
            .to_rust_string_lossy(scope);
        let mut result_vec = Vec::new();
        if let Ok(entries) = fs::read_dir(&path_str) {
            for entry in entries {
                if let Ok(entry) = entry {
                    if let Some(name) = entry.file_name().to_str() {
                        result_vec.push(name.to_string());
                    }
                }
            }
        }
        // Create V8 array
        let array: _ = v8::Array::new_with_length(scope, result_vec.len());
        for (i, name) in result_vec.iter().enumerate() {
            array.set_index(scope, i, v8::String::new(scope, name).unwrap().into());
        }
        retval.set(array.into());
    });
    let readdir_sync_instance: _ = readdir_sync.get_function(scope).unwrap();
    fs_obj.set(scope, "readdirSync", readdir_sync_instance.into())?;
    // fs.statSync()
    let stat_sync: _ = v8::FunctionTemplate::new(scope, |scope: &mut v8::PinScope, args: v8::FunctionCallbackArguments, mut retval: v8::ReturnValue| {
        let path: _ = args.get(0);
        let path_str: _ = path.to_string(scope)
            .unwrap_or_else(|| v8::String::new(scope, "<error>").unwrap())
            .to_rust_string_lossy(scope);
        let result: _ = Path::new(&path_str).is_file();
        retval.set(v8::Boolean::new(scope, result).into());
    });
    let stat_sync_instance: _ = stat_sync.get_function(scope).unwrap();
    fs_obj.set(scope, "statSync", stat_sync_instance.into())?;
    let global: _ = scope.global();
    let global_key: _ = v8::String::new(scope, "fs").unwrap();
    let global_val: _ = fs_obj.into(;
    global.set(scope, global_key.into(), global_val);)?;
    Ok(())
}
/// Module system implementation
fn setup_module_system(scope: &mut v8::ContextScope<v8::HandleScope>) -> Result<()> {
    // Global require function - simplified implementation
    let require_func: _ = v8::FunctionTemplate::new(scope, |scope: &mut v8::PinScope, args: v8::FunctionCallbackArguments, mut retval: v8::ReturnValue| {
        let module_name: _ = args.get(0);
        let module_name_str: _ = module_name.to_string(scope)
            .unwrap_or_else(|| v8::String::new(scope, "<error>").unwrap())
            .to_rust_string_lossy(scope);
        // Simple require implementation - return a mock module object
        // NOTE: This is a partial implementation for specific test scenarios.
        // The main code uses the complete implementation in src/nodejs.rs which includes:
        // - Full ModuleLoader with npm package support
        // - Filesystem module resolution
        // - Package.json parsing
        // - Module caching
        // This partial version is used for basic API testing only.
        let result: _ = format!("[Module: {}]", module_name_str));
        retval.set(v8::String::new(scope, &result).unwrap().into());
    });
    let require_func_instance: _ = require_func.get_function(scope).unwrap();
    let global: _ = scope.global();
    let global_key: _ = v8::String::new(scope, "require").unwrap();
    let global_val: _ = require_func_instance.into(;
    global.set(scope, global_key.into(), global_val);)?;
    // Module object
    let module: _ = v8::Object::new(scope);
    let exports: _ = v8::Object::new(scope);
    module.set(scope, "exports", exports.into())?;
    let global_key: _ = v8::String::new(scope, "module").unwrap();
    let global_val: _ = module.into(;
    global.set(scope, global_key.into(), global_val);)?;
    Ok(())
}
#[cfg(test)]
mod tests {
    #[test]
    fn test_setup_nodejs_apis() {
        // Initialize V8
        let platform: _ = v8::new_default_platform(0, false).unwrap().make_shared();
        v8::V8::initialize_platform(platform);
        v8::V8::initialize();
        let mut isolate = v8::Isolate::new(v8::CreateParams::default()).unwrap();
        let scope: _ = &mut v8::HandleScope::new(&mut isolate);
        let context: _ = v8::Context::new(scope, Default::default());
        let scope: _ = &mut v8::ContextScope::new(scope, context);
        let result: _ = setup_nodejs_apis(scope);
        assert!(result.is_ok());
        // Verify process is available
        let global: _ = scope.global();
        let process_key: _ = v8::String::new(scope, "process").unwrap();
        let process: _ = global.get(scope, process_key.into()).unwrap();
        assert!(process.is_object());
        // Verify path is available
        let path_key: _ = v8::String::new(scope, "path").unwrap();
        let path: _ = global.get(scope, path_key.into()).unwrap();
        assert!(path.is_object());
        // Verify fs is available
        let fs_key: _ = v8::String::new(scope, "fs").unwrap();
        let fs: _ = global.get(scope, fs_key.into()).unwrap();
        assert!(fs.is_object());
    }
}