use crate::elm; use crate::reporting::{CompilerError, Problem}; use crate::PortableHash; use std::hash::Hasher; use std::io::Write; use std::path::PathBuf; use tracing::info_span; mod astrid_pages; mod css_in_elm; mod fixtures; mod scripting; pub(crate) fn exec( file: PathBuf, debug: bool, function: String, input_source: Option, output: Option, verbosity: u64, sqlite_path: Option, ) -> Result<(), Problem> { // Our first elm make call is where we build the users program. There is a pretty good chance // this won't work. elm::make(&file, debug, verbosity)?; // Step 2, find the elm.json and elm-stuff directory let elm_project_dir = elm::find_project_root("elm.json", "./").map_err(CompilerError::MissingElmJson)?; let elm_cache_dir = elm_project_dir.join("elm-stuff").join("0.19.1"); if !elm_cache_dir.is_dir() { return Err(CompilerError::MissingElmStuff(elm_cache_dir).into()); } // step 2.5 get the module name out of the file. let data = std::fs::read(&file) .map_err(|io_err| CompilerError::ReadInputFailed(io_err, file.clone()))?; let entrypoint = elmi::Global( elmi::ModuleNameCanonical { package: elmi::PackageName::new("author", "project"), module: elm::parse_module_name(&data)?, }, elmi::Name(function.clone()), ); // step 3 find all the filepaths in the elm-stuff/0.19.1/* folder let interfaces = elm::load_interfaces(&elm_cache_dir)?; // Step 5, check for the desired function let span = info_span!("resolved target function"); let timing_guard = span.enter(); let mode = match interfaces.get(&entrypoint.0) { Some(interface) => match interface.values.get(&entrypoint.1) { Some(annotation) => { let elmi::CannonicalAnnotation(_free_vars, tipe) = annotation; if is_astrid_pages_route(tipe) { ExecutionMode::AstridPagesRoute } else if is_css_in_elm_stylesheet(tipe) { ExecutionMode::CssInElm } else { let (input_type, output_type) = scripting::resolve_function_type(tipe)?; ExecutionMode::Scripting(input_type, output_type) } } None => return Err(CompilerError::BadImport(entrypoint).into()), }, None => return Err(CompilerError::MissingModuleTypeInformation(entrypoint.0).into()), }; drop(timing_guard); let mut hasher = PortableHash::new(); hasher.write_all(&data).unwrap(); // also include the function name in the checksum so users can run multiple functions // from the same file but still get caching if the file does not change. hasher.write_all(function.as_bytes()).unwrap(); let source_checksum = hasher.finish(); match mode { ExecutionMode::Scripting(input_type, output_type) => scripting::run( debug, verbosity, sqlite_path, elm_project_dir, source_checksum, entrypoint, input_type, output_type, input_source, output, ), ExecutionMode::AstridPagesRoute => astrid_pages::run( debug, verbosity, sqlite_path, elm_project_dir, source_checksum, entrypoint, output, ), ExecutionMode::CssInElm => css_in_elm::run( debug, verbosity, elm_project_dir, source_checksum, entrypoint, output, ), } } enum ExecutionMode { AstridPagesRoute, CssInElm, Scripting(Option, scripting::OutputType), } fn is_astrid_pages_route(tipe: &elmi::Type) -> bool { match tipe { elmi::Type::TType(module_name, name, _args) => { module_name == "author/project/Astrid.Pages" && name == "Route" } _ => false, } } fn is_css_in_elm_stylesheet(tipe: &elmi::Type) -> bool { match tipe { elmi::Type::TType(module_name, name, args) => { if module_name == "elm/core/List" && name == "List" && args.len() == 1 { match &args[0] { elmi::Type::TTuple(a, b, None) => { match &**a { elmi::Type::TType(module_name, name, _) if module_name == "elm/core/String" && name == "String" => (), _ => return false, } match &**b { elmi::Type::TType(module_name, name, args) if module_name == "elm/core/List" && name == "List" && args.len() == 1 => { match &args[0] { elmi::Type::TAlias(module_name, name, _, _) if module_name == "ThinkAlexandria/css-in-elm/Css" && name == "Stylesheet" => return true, _ => (), } } _ => (), } } _ => (), } } } _ => (), } false } mod runtime { use crate::reporting::InterpreterError; use deno_core::error::{type_error, AnyError}; use deno_core::futures::FutureExt; use deno_core::{resolve_url, Extension, FsModuleLoader, ModuleLoader, ModuleSpecifier}; use deno_runtime::deno_broadcast_channel::InMemoryBroadcastChannel; use deno_runtime::permissions::Permissions; use deno_runtime::worker::MainWorker; use deno_runtime::worker::WorkerOptions; use deno_runtime::BootstrapOptions; use deno_web::BlobStore; use std::pin::Pin; use std::rc::Rc; use std::sync::Arc; use tracing::{info_span, Instrument}; fn get_error_class_name(e: &AnyError) -> &'static str { deno_runtime::errors::get_error_class_name(e).unwrap_or("Error") } pub fn setup_worker( extensions: Vec, path_str: &str, ) -> Result<(MainWorker, ModuleSpecifier), AnyError> { //let module_loader = Rc::new(EmbeddedModuleLoader("void".to_owned())); let module_loader = Rc::new(FsModuleLoader); let create_web_worker_cb = Arc::new(|_| { todo!("Web workers are not supported in the example"); }); let web_worker_preload_module_cb = Arc::new(|_| { todo!("Web workers are not supported in the example"); }); let options = WorkerOptions { bootstrap: BootstrapOptions { args: vec![], apply_source_maps: false, cpu_count: 1, debug_flag: false, enable_testing_features: false, is_tty: false, location: None, no_color: false, runtime_version: "0.50.0".to_string(), ts_version: "2.0.0".to_string(), unstable: false, }, extensions: extensions, unsafely_ignore_certificate_errors: None, root_cert_store: None, user_agent: "hello_runtime".to_string(), seed: None, module_loader, create_web_worker_cb, web_worker_preload_module_cb, js_error_create_fn: None, maybe_inspector_server: None, should_break_on_first_statement: false, get_error_class_fn: Some(&get_error_class_name), origin_storage_dir: None, blob_store: BlobStore::default(), broadcast_channel: InMemoryBroadcastChannel::default(), shared_array_buffer_store: None, compiled_wasm_module_store: None, }; let main_module = deno_core::resolve_path(path_str)?; // note: resolve_url is just calling url::Url::parse and mapping the error type // let main_module = resolve_url(SPECIFIER)?; let permissions = Permissions::allow_all(); let worker = MainWorker::from_options(main_module.clone(), permissions, options); //worker.bootstrap(&options); Ok((worker, main_module)) } pub async fn xyz( mut worker: MainWorker, main_module: ModuleSpecifier, foo: F, ) -> Result<(), InterpreterError> where F: FnOnce(deno_core::v8::HandleScope) -> Result<(), InterpreterError>, { let wait_for_inspector = false; // step 10 load the module into our v8 isolate worker .execute_main_module(&main_module) .instrument(info_span!("execute main module")) .await?; worker .run_event_loop(wait_for_inspector) .instrument(info_span!("run v8 event loop")) .await?; let scope_outer = worker.js_runtime.handle_scope(); foo(scope_outer)?; worker .run_event_loop(wait_for_inspector) .instrument(info_span!("run v8 event loop")) .await?; Ok(()) } const SPECIFIER: &str = "file://$deno$/bundle.js"; struct EmbeddedModuleLoader(String); impl ModuleLoader for EmbeddedModuleLoader { fn resolve( &self, specifier: &str, _referrer: &str, _is_main: bool, ) -> Result { if let Ok(module_specifier) = resolve_url(specifier) { //if get_source_from_data_url(&module_specifier).is_ok() // || specifier == SPECIFIER //{ return Ok(module_specifier); //} } Err(type_error( "Self-contained binaries don't support module loading", )) } fn load( &self, module_specifier: &ModuleSpecifier, _maybe_referrer: Option, _is_dyn_import: bool, ) -> Pin> { let module_specifier = module_specifier.clone(); //let is_data_uri = get_source_from_data_url(&module_specifier).ok(); //let code = if let Some((ref source, _)) = is_data_uri { // source.to_string() //} else { let code = self.0.to_string(); //}; async move { //if is_data_uri.is_none() && module_specifier.to_string() != SPECIFIER { // return Err(type_error( // "Self-contained binaries don't support module loading", // )); //} Ok(deno_core::ModuleSource { code, module_type: deno_core::ModuleType::JavaScript, module_url_specified: module_specifier.to_string(), module_url_found: module_specifier.to_string(), }) } .boxed_local() } } }