// Copyright 2018-2026 the Deno authors. MIT license.

use std::borrow::Cow;
use std::path::Path;
use std::path::PathBuf;
use std::rc::Rc;
use std::sync::Arc;
use std::sync::OnceLock;

use deno_cache_dir::npm::NpmCacheDir;
use deno_config::workspace::ResolverWorkspaceJsrPackage;
use deno_core::FastString;
use deno_core::ModuleLoadOptions;
use deno_core::ModuleLoadReferrer;
use deno_core::ModuleLoader;
use deno_core::ModuleSourceCode;
use deno_core::ModuleSpecifier;
use deno_core::ModuleType;
use deno_core::RequestedModuleType;
use deno_core::ResolutionKind;
use deno_core::SourceCodeCacheInfo;
use deno_core::error::AnyError;
use deno_core::error::ModuleLoaderError;
use deno_core::futures::FutureExt;
use deno_core::futures::future::LocalBoxFuture;
use deno_core::url::Url;
use deno_core::v8_set_flags;
use deno_error::JsErrorBox;
use deno_lib::args::CaData;
use deno_lib::args::RootCertStoreLoadError;
use deno_lib::args::get_root_cert_store;
use deno_lib::args::npm_pkg_req_ref_to_binary_command;
use deno_lib::loader::as_deno_resolver_requested_module_type;
use deno_lib::loader::loaded_module_source_to_module_source_code;
use deno_lib::loader::module_type_from_media_and_requested_type;
use deno_lib::npm::NpmRegistryReadPermissionChecker;
use deno_lib::npm::NpmRegistryReadPermissionCheckerMode;
use deno_lib::npm::create_npm_process_state_provider;
use deno_lib::standalone::binary::NodeModules;
use deno_lib::util::hash::FastInsecureHasher;
use deno_lib::util::text_encoding::from_utf8_lossy_cow;
use deno_lib::util::text_encoding::from_utf8_lossy_owned;
use deno_lib::util::v8::construct_v8_flags;
use deno_lib::worker::CreateModuleLoaderResult;
use deno_lib::worker::LibMainWorkerFactory;
use deno_lib::worker::LibMainWorkerOptions;
use deno_lib::worker::ModuleLoaderFactory;
use deno_lib::worker::StorageKeyResolver;
use deno_media_type::MediaType;
use deno_npm::resolution::NpmResolutionSnapshot;
use deno_npmrc::ResolvedNpmRc;
use deno_package_json::PackageJsonDepValue;
use deno_resolver::DenoResolveErrorKind;
use deno_resolver::cjs::CjsTracker;
use deno_resolver::cjs::IsCjsResolutionMode;
use deno_resolver::loader::NpmModuleLoader;
use deno_resolver::npm::ByonmNpmResolverCreateOptions;
use deno_resolver::npm::CreateInNpmPkgCheckerOptions;
use deno_resolver::npm::DenoInNpmPackageChecker;
use deno_resolver::npm::NpmReqResolver;
use deno_resolver::npm::NpmReqResolverOptions;
use deno_resolver::npm::NpmResolver;
use deno_resolver::npm::NpmResolverCreateOptions;
use deno_resolver::npm::managed::ManagedInNpmPkgCheckerCreateOptions;
use deno_resolver::npm::managed::ManagedNpmResolverCreateOptions;
use deno_resolver::npm::managed::NpmResolutionCell;
use deno_resolver::workspace::MappedResolution;
use deno_resolver::workspace::SloppyImportsOptions;
use deno_resolver::workspace::WorkspaceResolver;
use deno_runtime::FeatureChecker;
use deno_runtime::WorkerExecutionMode;
use deno_runtime::WorkerLogLevel;
use deno_runtime::code_cache::CodeCache;
use deno_runtime::deno_fs::FileSystem;
use deno_runtime::deno_node::NodeRequireLoader;
use deno_runtime::deno_node::create_host_defined_options;
use deno_runtime::deno_node::ops::module_hooks::LoaderHookRegistry;
use deno_runtime::deno_permissions::Permissions;
use deno_runtime::deno_permissions::PermissionsContainer;
use deno_runtime::deno_tls::RootCertStoreProvider;
use deno_runtime::deno_tls::rustls::RootCertStore;
use deno_runtime::deno_web::Blob;
use deno_runtime::deno_web::BlobStore;
use deno_runtime::permissions::RuntimePermissionDescriptorParser;
use deno_semver::npm::NpmPackageReqReference;
use node_resolver::DenoIsBuiltInNodeModuleChecker;
use node_resolver::NodeResolutionKind;
use node_resolver::NodeResolver;
use node_resolver::PackageJsonResolver;
use node_resolver::PackageJsonThreadLocalCache;
use node_resolver::ResolutionMode;
use node_resolver::analyze::CjsAnalysisSourceProvider;
use node_resolver::analyze::CjsModuleExportAnalyzer;
use node_resolver::analyze::NodeCodeTranslator;
use node_resolver::cache::NodeResolutionSys;
use node_resolver::errors::PackageJsonLoadError;
use sys_traits::EnvCurrentDir;

use crate::binary::DenoCompileModuleSource;
use crate::binary::StandaloneData;
use crate::binary::StandaloneModules;
use crate::binary::transpile_runtime_module;
use crate::code_cache::DenoCompileCodeCache;
use crate::file_system::DenoRtSys;
use crate::file_system::FileBackedVfs;
use crate::node::CjsCodeAnalyzer;
use crate::node::DenoRtCjsTracker;
use crate::node::DenoRtNodeCodeTranslator;
use crate::node::DenoRtNodeResolver;
use crate::node::DenoRtNpmModuleLoader;
use crate::node::DenoRtNpmReqResolver;

struct SharedModuleLoaderState {
  blob_store: Arc<BlobStore>,
  cjs_tracker: Arc<DenoRtCjsTracker>,
  code_cache: Option<Arc<DenoCompileCodeCache>>,
  modules: Arc<StandaloneModules>,
  node_code_translator: Arc<DenoRtNodeCodeTranslator>,
  node_resolver: Arc<DenoRtNodeResolver>,
  npm_module_loader: Arc<DenoRtNpmModuleLoader>,
  npm_registry_permission_checker: NpmRegistryReadPermissionChecker<DenoRtSys>,
  npm_req_resolver: Arc<DenoRtNpmReqResolver>,
  vfs: Arc<FileBackedVfs>,
  workspace_resolver: WorkspaceResolver<DenoRtSys>,
}

impl SharedModuleLoaderState {
  fn get_code_cache(
    &self,
    specifier: &Url,
    source: &[u8],
  ) -> Option<SourceCodeCacheInfo> {
    let Some(code_cache) = &self.code_cache else {
      return None;
    };
    if !code_cache.enabled() {
      return None;
    }
    // deno version is already included in the root cache key
    let hash = FastInsecureHasher::new_without_deno_version()
      .write_hashable(source)
      .finish();
    let data = code_cache.get_sync(
      specifier,
      deno_runtime::code_cache::CodeCacheType::EsModule,
      hash,
    );
    Some(SourceCodeCacheInfo {
      hash,
      data: data.map(Cow::Owned),
    })
  }
}

#[derive(Clone)]
struct EmbeddedModuleLoader {
  shared: Arc<SharedModuleLoaderState>,
  hook_registry: LoaderHookRegistry,
  permissions: PermissionsContainer,
  sys: DenoRtSys,
  /// For blob/object-URL module workers, the captured root blob and its
  /// specifier. Used so the worker's root module load resolves from the
  /// synchronously-captured blob even if `URL.revokeObjectURL` has since
  /// removed it from the blob store.
  maybe_main_module_blob: Option<(ModuleSpecifier, Arc<Blob>)>,
}

impl std::fmt::Debug for EmbeddedModuleLoader {
  fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
    f.debug_struct("EmbeddedModuleLoader").finish()
  }
}

struct StandaloneCjsAnalysisSourceProvider<'a> {
  loader: &'a EmbeddedModuleLoader,
  permissions: PermissionsContainer,
}

impl<'a> StandaloneCjsAnalysisSourceProvider<'a> {
  fn new(loader: &'a EmbeddedModuleLoader) -> Self {
    Self {
      loader,
      permissions: loader.permissions.deep_clone_without_prompt(),
    }
  }
}

impl CjsAnalysisSourceProvider for StandaloneCjsAnalysisSourceProvider<'_> {
  fn load_source<'a>(&'a self, specifier: &Url) -> Option<Cow<'a, str>> {
    let path = deno_path_util::url_to_file_path(specifier).ok()?;
    let mut permissions = self.permissions.clone();
    let path = self
      .loader
      .ensure_read_permission(&mut permissions, Cow::Owned(path))
      .ok()?;
    self
      .loader
      .load_text_file_lossy(&path)
      .ok()
      .map(|source| Cow::Owned(source.to_string()))
  }
}

impl EmbeddedModuleLoader {
  fn resolve_inner(
    &self,
    raw_specifier: &str,
    referrer: &str,
    _kind: ResolutionKind,
  ) -> Result<Url, ModuleLoaderError> {
    let referrer = if referrer == "." {
      #[allow(
        clippy::disallowed_methods,
        reason = "ok to use current_dir here"
      )]
      let current_dir =
        self.sys.env_current_dir().map_err(JsErrorBox::from_err)?;
      deno_core::resolve_path(".", &current_dir)
        .map_err(JsErrorBox::from_err)?
    } else {
      Url::parse(referrer).map_err(|err| {
        JsErrorBox::type_error(format!(
          "Referrer uses invalid specifier: {}",
          err
        ))
      })?
    };
    let resolution_mode = if self
      .shared
      .cjs_tracker
      .is_maybe_cjs(&referrer, MediaType::from_specifier(&referrer))
      .map_err(JsErrorBox::from_err)?
    {
      ResolutionMode::Require
    } else {
      ResolutionMode::Import
    };

    if self.shared.node_resolver.in_npm_package(&referrer) {
      return self
        .shared
        .node_resolver
        .resolve(
          raw_specifier,
          &referrer,
          resolution_mode,
          NodeResolutionKind::Execution,
        )
        .and_then(|res| res.into_url())
        .map_err(JsErrorBox::from_err);
    }

    let mapped_resolution = self.shared.workspace_resolver.resolve(
      raw_specifier,
      &referrer,
      deno_resolver::workspace::ResolutionKind::Execution,
    );

    match mapped_resolution {
      Ok(MappedResolution::WorkspaceJsrPackage { specifier, .. }) => {
        Ok(specifier)
      }
      Ok(MappedResolution::WorkspaceNpmPackage {
        target_pkg_json: pkg_json,
        sub_path,
        ..
      }) => Ok(
        self
          .shared
          .node_resolver
          .resolve_package_subpath_from_deno_module(
            pkg_json.dir_path(),
            sub_path.as_deref(),
            Some(&referrer),
            resolution_mode,
            NodeResolutionKind::Execution,
          )
          .map_err(JsErrorBox::from_err)
          .and_then(|url_or_path| {
            url_or_path.into_url().map_err(JsErrorBox::from_err)
          })?,
      ),
      Ok(MappedResolution::PackageJson {
        dep_result,
        sub_path,
        alias,
        ..
      }) => match dep_result
        .as_ref()
        .map_err(|e| JsErrorBox::from_err(e.clone()))?
      {
        PackageJsonDepValue::File(_) => Err(JsErrorBox::from_err(
          DenoResolveErrorKind::UnsupportedPackageJsonFileSpecifier.into_box(),
        )),
        PackageJsonDepValue::Req(req) => Ok(
          self
            .shared
            .npm_req_resolver
            .resolve_req_with_sub_path(
              req,
              sub_path.as_deref(),
              &referrer,
              resolution_mode,
              NodeResolutionKind::Execution,
            )
            .map_err(JsErrorBox::from_err)
            .and_then(|url_or_path| {
              url_or_path.into_url().map_err(JsErrorBox::from_err)
            })?,
        ),
        PackageJsonDepValue::Workspace { name, version_req } => {
          let pkg_folder = self
            .shared
            .workspace_resolver
            .resolve_workspace_pkg_json_folder_for_pkg_json_dep(
              name.as_deref().unwrap_or(alias),
              version_req,
            )
            .map_err(JsErrorBox::from_err)?;
          Ok(
            self
              .shared
              .node_resolver
              .resolve_package_subpath_from_deno_module(
                pkg_folder,
                sub_path.as_deref(),
                Some(&referrer),
                resolution_mode,
                NodeResolutionKind::Execution,
              )
              .map_err(JsErrorBox::from_err)
              .and_then(|url_or_path| {
                url_or_path.into_url().map_err(JsErrorBox::from_err)
              })?,
          )
        }
        PackageJsonDepValue::Catalog(catalog_name) => {
          match self
            .shared
            .workspace_resolver
            .resolve_catalog_dep(alias, catalog_name)
          {
            Some(req) => Ok(
              self
                .shared
                .npm_req_resolver
                .resolve_req_with_sub_path(
                  &req,
                  sub_path.as_deref(),
                  &referrer,
                  resolution_mode,
                  NodeResolutionKind::Execution,
                )
                .map_err(JsErrorBox::from_err)
                .and_then(|url_or_path| {
                  url_or_path.into_url().map_err(JsErrorBox::from_err)
                })?,
            ),
            None => Err(JsErrorBox::generic(format!(
              "Package '{}' not found in catalog",
              alias
            ))),
          }
        }
      },
      Ok(MappedResolution::PackageJsonImport { pkg_json }) => self
        .shared
        .node_resolver
        .resolve_package_import(
          raw_specifier,
          Some(&node_resolver::UrlOrPathRef::from_url(&referrer)),
          Some(pkg_json),
          resolution_mode,
          NodeResolutionKind::Execution,
        )
        .map_err(JsErrorBox::from_err)
        .and_then(|url_or_path| {
          url_or_path.into_url().map_err(JsErrorBox::from_err)
        }),
      Ok(MappedResolution::Normal { specifier, .. }) => {
        if let Ok(reference) =
          NpmPackageReqReference::from_specifier(&specifier)
        {
          return self
            .shared
            .npm_req_resolver
            .resolve_req_reference(
              &reference,
              &referrer,
              resolution_mode,
              NodeResolutionKind::Execution,
            )
            .map_err(JsErrorBox::from_err)
            .and_then(|url_or_path| {
              url_or_path.into_url().map_err(JsErrorBox::from_err)
            });
        }

        if specifier.scheme() == "jsr"
          && let Some(specifier) = self
            .shared
            .modules
            .resolve_specifier(&specifier)
            .map_err(JsErrorBox::from_err)?
        {
          return Ok(specifier.clone());
        }

        Ok(
          self
            .shared
            .node_resolver
            .handle_if_in_node_modules(&specifier)
            .unwrap_or(specifier),
        )
      }
      Err(err)
        if err.is_unmapped_bare_specifier() && referrer.scheme() == "file" =>
      {
        let maybe_res = self
          .shared
          .npm_req_resolver
          .resolve_if_for_npm_pkg(
            raw_specifier,
            &referrer,
            resolution_mode,
            NodeResolutionKind::Execution,
          )
          .map_err(JsErrorBox::from_err)?;
        if let Some(res) = maybe_res {
          return res.into_url().map_err(JsErrorBox::from_err);
        }
        Err(JsErrorBox::from_err(err))
      }
      Err(err) => Err(JsErrorBox::from_err(err)),
    }
  }

  /// Load a module using the default embedded/filesystem logic, including
  /// the async npm-package path. Used both by `ModuleLoader::load` after
  /// data-URL/hook routing and by the hook-fallthrough branch when a load
  /// hook calls `nextLoad()` without short-circuiting.
  fn load_default(
    &self,
    original_specifier: &Url,
    maybe_referrer: Option<&ModuleLoadReferrer>,
    options: ModuleLoadOptions,
  ) -> deno_core::ModuleLoadResponse {
    if self.shared.node_resolver.in_npm_package(original_specifier) {
      let loader = self.clone();
      let shared = self.shared.clone();
      let original_specifier = original_specifier.clone();
      let maybe_referrer = maybe_referrer.map(|r| r.specifier.clone());
      return deno_core::ModuleLoadResponse::Async(
        async move {
          let source_provider =
            StandaloneCjsAnalysisSourceProvider::new(&loader);
          let code_source = shared
            .npm_module_loader
            .load(
              Cow::Borrowed(&original_specifier),
              maybe_referrer.as_ref(),
              &as_deno_resolver_requested_module_type(
                &options.requested_module_type,
              ),
              Some(&source_provider),
            )
            .await
            .map_err(JsErrorBox::from_err)?;
          let module_type = module_type_from_media_and_requested_type(
            code_source.media_type,
            &options.requested_module_type,
          );
          // Only JavaScript modules produce a V8 code cache. Requesting one
          // for other module types (JSON, Wasm, etc.) bumps the `FirstRun`
          // strategy's pending counter via `get_sync` without a matching
          // `set_sync`, preventing the cache from ever being serialized.
          // See https://github.com/denoland/deno/issues/31766
          let code_cache_entry = if module_type == ModuleType::JavaScript {
            shared.get_code_cache(
              &code_source.specifier,
              code_source.source.as_bytes(),
            )
          } else {
            None
          };
          Ok(deno_core::ModuleSource::new_with_redirect(
            module_type,
            loaded_module_source_to_module_source_code(code_source.source),
            &original_specifier,
            &code_source.specifier,
            code_cache_entry,
          ))
        }
        .boxed_local(),
      );
    }

    match self.shared.modules.read(original_specifier) {
      Ok(Some(module)) => {
        match options.requested_module_type {
          RequestedModuleType::Text | RequestedModuleType::Bytes => {
            let module_source = DenoCompileModuleSource::Bytes(module.data);
            return deno_core::ModuleLoadResponse::Sync(Ok(
              deno_core::ModuleSource::new_with_redirect(
                match options.requested_module_type {
                  RequestedModuleType::Text => ModuleType::Text,
                  RequestedModuleType::Bytes => ModuleType::Bytes,
                  _ => unreachable!(),
                },
                match options.requested_module_type {
                  RequestedModuleType::Text => module_source.into_for_v8(),
                  RequestedModuleType::Bytes => {
                    ModuleSourceCode::Bytes(module_source.into_bytes_for_v8())
                  }
                  _ => unreachable!(),
                },
                original_specifier,
                module.specifier,
                None,
              ),
            ));
          }
          RequestedModuleType::Other(_)
          | RequestedModuleType::None
          | RequestedModuleType::Json => {
            // ignore
          }
        }

        let media_type = module.media_type;
        let (module_specifier, module_type, module_source) =
          module.into_parts();
        let is_maybe_cjs = match self
          .shared
          .cjs_tracker
          .is_maybe_cjs(original_specifier, media_type)
        {
          Ok(is_maybe_cjs) => is_maybe_cjs,
          Err(err) => {
            return deno_core::ModuleLoadResponse::Sync(Err(
              JsErrorBox::type_error(format!("{:?}", err)),
            ));
          }
        };
        if is_maybe_cjs {
          let original_specifier = original_specifier.clone();
          let module_specifier = module_specifier.clone();
          let loader = self.clone();
          let shared = self.shared.clone();
          deno_core::ModuleLoadResponse::Async(
            async move {
              let source = match module_source {
                DenoCompileModuleSource::String(string) => {
                  Cow::Borrowed(string)
                }
                DenoCompileModuleSource::Bytes(module_code_bytes) => {
                  match module_code_bytes {
                    Cow::Owned(bytes) => {
                      Cow::Owned(from_utf8_lossy_owned(bytes))
                    }
                    Cow::Borrowed(bytes) => String::from_utf8_lossy(bytes),
                  }
                }
              };
              let source_provider =
                StandaloneCjsAnalysisSourceProvider::new(&loader);
              let source = shared
                .node_code_translator
                .translate_cjs_to_esm_with_source_provider(
                  &module_specifier,
                  Some(source),
                  Some(&source_provider),
                )
                .await
                .map_err(JsErrorBox::from_err)?
                .into_owned();
              let module_source = ModuleSourceCode::String(source.into());
              // CJS modules are always JavaScript, but gate on the module
              // type anyway to keep the code cache contract uniform across all
              // load paths: only JavaScript produces a V8 code cache.
              // See https://github.com/denoland/deno/issues/31766
              let code_cache_entry = if module_type == ModuleType::JavaScript {
                shared
                  .get_code_cache(&module_specifier, module_source.as_bytes())
              } else {
                None
              };
              Ok(deno_core::ModuleSource::new_with_redirect(
                module_type,
                module_source,
                &original_specifier,
                &module_specifier,
                code_cache_entry,
              ))
            }
            .boxed_local(),
          )
        } else {
          let module_source = module_source.into_for_v8();
          // Only JavaScript modules produce a V8 code cache. Requesting the
          // code cache for other module types (JSON, Wasm, etc.) would still
          // bump the `FirstRun` strategy's pending counter via `get_sync`
          // without a matching `set_sync`, preventing the cache from ever
          // being serialized. See https://github.com/denoland/deno/issues/31766
          let code_cache_entry = if module_type == ModuleType::JavaScript {
            self
              .shared
              .get_code_cache(module_specifier, module_source.as_bytes())
          } else {
            None
          };
          deno_core::ModuleLoadResponse::Sync(Ok(
            deno_core::ModuleSource::new_with_redirect(
              module_type,
              module_source,
              original_specifier,
              module_specifier,
              code_cache_entry,
            ),
          ))
        }
      }
      Ok(None) => {
        // Fall back to reading from disk (used by dev entrypoint in HMR mode).
        if original_specifier.scheme() == "file"
          && let Ok(path) = original_specifier.to_file_path()
          && let Ok(source) = std::fs::read_to_string(&path)
        {
          let media_type = MediaType::from_specifier(original_specifier);
          let (module_type, should_transpile) = match media_type {
            MediaType::JavaScript | MediaType::Mjs | MediaType::Cjs => {
              (ModuleType::JavaScript, false)
            }
            MediaType::TypeScript
            | MediaType::Mts
            | MediaType::Cts
            | MediaType::Jsx
            | MediaType::Tsx => (ModuleType::JavaScript, true),
            MediaType::Json => (ModuleType::Json, false),
            _ => (ModuleType::JavaScript, false),
          };
          let code = if should_transpile {
            match deno_ast::parse_module(deno_ast::ParseParams {
              specifier: original_specifier.clone(),
              text: source.into(),
              media_type,
              capture_tokens: false,
              scope_analysis: false,
              maybe_syntax: None,
            }) {
              Ok(parsed) => match parsed.transpile(
                &deno_ast::TranspileOptions::default(),
                &deno_ast::TranspileModuleOptions::default(),
                &deno_ast::EmitOptions::default(),
              ) {
                Ok(transpiled) => {
                  ModuleSourceCode::String(transpiled.into_source().text.into())
                }
                Err(e) => {
                  return deno_core::ModuleLoadResponse::Sync(Err(
                    JsErrorBox::type_error(format!("Transpile error: {e}")),
                  ));
                }
              },
              Err(e) => {
                return deno_core::ModuleLoadResponse::Sync(Err(
                  JsErrorBox::type_error(format!("Parse error: {e}")),
                ));
              }
            }
          } else {
            ModuleSourceCode::String(source.into())
          };
          return deno_core::ModuleLoadResponse::Sync(Ok(
            deno_core::ModuleSource::new(
              module_type,
              code,
              original_specifier,
              None,
            ),
          ));
        }
        deno_core::ModuleLoadResponse::Sync(Err(JsErrorBox::type_error(
          format!("Module not found: {}", original_specifier),
        )))
      }
      Err(err) => deno_core::ModuleLoadResponse::Sync(Err(
        JsErrorBox::type_error(format!("{:?}", err)),
      )),
    }
  }
}

impl ModuleLoader for EmbeddedModuleLoader {
  fn resolve(
    &self,
    raw_specifier: &str,
    referrer: &str,
    kind: ResolutionKind,
  ) -> Result<ModuleSpecifier, JsErrorBox> {
    self.resolve_inner(raw_specifier, referrer, kind)
  }

  fn resolve_with_scope(
    &self,
    scope: &mut deno_core::v8::PinScope,
    raw_specifier: &str,
    referrer: &str,
    kind: ResolutionKind,
    import_attributes: &std::collections::HashMap<String, String>,
  ) -> Result<ModuleSpecifier, JsErrorBox> {
    // CJS modules generated by Deno import `node:module` internally. Node does
    // not expose that implementation detail to resolve hooks, so only bypass
    // hooks for CJS referrers. User ESM imports of `node:module` still go
    // through hooks.
    if raw_specifier == "node:module"
      && let Ok(referrer) = Url::parse(referrer)
      && self.shared.cjs_tracker.get_referrer_kind(&referrer)
        == ResolutionMode::Require
    {
      return self.resolve_inner(raw_specifier, referrer.as_str(), kind);
    }
    if raw_specifier == "node:module"
      && let Ok(referrer) = Url::parse(referrer)
      && self.is_maybe_cjs(&referrer).unwrap_or(false)
    {
      return self.resolve_inner(raw_specifier, referrer.as_str(), kind);
    }
    let resolved = if let Some(url) = self.hook_registry.resolve(
      scope,
      raw_specifier,
      referrer,
      import_attributes,
    )? {
      ModuleSpecifier::parse(&url).map_err(JsErrorBox::from_err)
    } else {
      self.resolve_inner(raw_specifier, referrer, kind)
    };
    // Stash the full import attributes against the resolved URL so the load
    // hook (which V8 only hands the `type` attribute) can recover them.
    if let Ok(resolved) = &resolved {
      self
        .hook_registry
        .record_resolved_attributes(resolved.as_str(), import_attributes);
    }
    resolved
  }

  fn pump_event_loop_during_load(&self) -> bool {
    // Load hooks respond through the async bridge, so the event loop must be
    // pumped during the recursive load or the load deadlocks.
    self.hook_registry.load_active.get()
  }

  fn get_host_defined_options<'s>(
    &self,
    scope: &mut deno_core::v8::PinScope<'s, '_>,
    name: &str,
  ) -> Option<deno_core::v8::Local<'s, deno_core::v8::Data>> {
    let name = Url::parse(name).ok()?;
    if self.shared.node_resolver.in_npm_package(&name) {
      Some(create_host_defined_options(scope))
    } else {
      None
    }
  }

  fn load(
    &self,
    original_specifier: &Url,
    maybe_referrer: Option<&ModuleLoadReferrer>,
    options: ModuleLoadOptions,
  ) -> deno_core::ModuleLoadResponse {
    if original_specifier.scheme() == "data" {
      let raw_data_url = match deno_media_type::data_url::RawDataUrl::parse(
        original_specifier,
      ) {
        Ok(raw) => raw,
        Err(err) => {
          return deno_core::ModuleLoadResponse::Sync(Err(
            JsErrorBox::type_error(format!("{:#}", err)),
          ));
        }
      };
      let media_type = raw_data_url.media_type();
      let data_url_text = match raw_data_url.decode() {
        Ok(text) => text,
        Err(err) => {
          return deno_core::ModuleLoadResponse::Sync(Err(
            JsErrorBox::type_error(format!("{:#}", err)),
          ));
        }
      };
      // Transpile when the data URL carries TypeScript/JSX, so compiled
      // programs can dynamically import TypeScript built at runtime.
      let code = match transpile_runtime_module(
        original_specifier,
        media_type,
        data_url_text,
      ) {
        Ok(code) => code,
        Err(err) => {
          return deno_core::ModuleLoadResponse::Sync(Err(err));
        }
      };
      return deno_core::ModuleLoadResponse::Sync(Ok(
        deno_core::ModuleSource::new(
          deno_core::ModuleType::JavaScript,
          ModuleSourceCode::String(code.into()),
          original_specifier,
          None,
        ),
      ));
    }

    if original_specifier.scheme() == "blob" {
      let specifier = original_specifier.clone();
      // Prefer the synchronously-captured root blob (if this is the worker's
      // main module) so a racing `URL.revokeObjectURL` can't make the load
      // fail; otherwise look the object URL up in the blob store.
      let maybe_blob = self
        .maybe_main_module_blob
        .as_ref()
        .filter(|(blob_specifier, _)| *blob_specifier == specifier)
        .map(|(_, blob)| blob.clone())
        .or_else(|| self.shared.blob_store.get_object_url(specifier.clone()));
      let Some(blob) = maybe_blob else {
        return deno_core::ModuleLoadResponse::Sync(Err(
          JsErrorBox::type_error(format!("Blob URL not found: {specifier}")),
        ));
      };
      let requested_module_type = options.requested_module_type.clone();
      return deno_core::ModuleLoadResponse::Async(
        async move {
          let bytes = blob.read_all().await;
          let (media_type, maybe_charset) =
            deno_media_type::resolve_media_type_and_charset_from_content_type(
              &specifier,
              Some(&blob.media_type),
            );
          let module_type = module_type_from_media_and_requested_type(
            media_type,
            &requested_module_type,
          );
          let source = match module_type {
            ModuleType::Bytes | ModuleType::Wasm => {
              ModuleSourceCode::Bytes(bytes.into_boxed_slice().into())
            }
            _ => {
              // Mirror `deno run`'s charset-aware decoding (see
              // `TextDecodedFile::decode` in cli/file_fetcher.rs) so blob
              // sources honor the content-type charset and have a leading BOM
              // stripped, instead of blindly assuming UTF-8.
              let charset = maybe_charset.unwrap_or_else(|| {
                deno_media_type::encoding::detect_charset(&specifier, &bytes)
              });
              let text =
                deno_media_type::encoding::decode_owned_source(charset, bytes)
                  .map_err(|err| {
                    JsErrorBox::type_error(format!(
                      "Failed decoding \"{specifier}\": {err}"
                    ))
                  })?;
              // Transpile when the blob carries TypeScript/JSX, so compiled
              // programs can dynamically import TypeScript built at runtime.
              let text =
                transpile_runtime_module(&specifier, media_type, text)?;
              ModuleSourceCode::String(text.into())
            }
          };
          Ok(deno_core::ModuleSource::new(
            module_type,
            source,
            &specifier,
            None,
          ))
        }
        .boxed_local(),
      );
    }

    // When load hooks are active, delegate to JS hooks first.
    // Only route through hooks for files that are NOT embedded in the binary
    // (i.e., external files that may need transformation like TS stripping).
    // Embedded files load directly from VFS without needing hooks.
    let is_embedded = if original_specifier.scheme() == "file" {
      deno_path_util::url_to_file_path(original_specifier)
        .map(|path| self.shared.vfs.file_entry(&path).is_ok())
        .unwrap_or(false)
    } else {
      // Remote modules in the binary are always embedded
      self
        .shared
        .modules
        .resolve_specifier(original_specifier)
        .ok()
        .flatten()
        .is_some()
    };
    if self.hook_registry.load_active.get()
      && !options.is_synchronous
      && !is_embedded
    {
      // The hook function itself is synchronous, but this load path has no V8
      // scope. The async bridge lets the JS event loop run the hook; blocking
      // here waiting for JS would deadlock the same runtime.
      let import_attributes = hook_load_import_attributes(
        &self.hook_registry,
        original_specifier.as_str(),
        &options.requested_module_type,
      );
      let receiver = self
        .hook_registry
        .push_load(original_specifier.to_string(), import_attributes);
      let this = self.clone();
      let specifier = original_specifier.clone();
      let maybe_referrer = maybe_referrer.cloned();
      let is_dynamic_import = options.is_dynamic_import;
      let is_synchronous = options.is_synchronous;
      let requested_module_type = options.requested_module_type.clone();
      return deno_core::ModuleLoadResponse::Async(
        async move {
          let hook_result = match receiver.await {
            Ok(r) => r,
            Err(_) => {
              return Err(JsErrorBox::generic("module load hook cancelled"));
            }
          };
          match hook_result {
            Ok((Some(source), format, _effective_url)) => {
              // Hook provided transformed source. Honor a hook-supplied
              // `format` (Node's hook contract) first, then fall back to
              // the importer's `with { type: "..." }` attribute, so e.g.
              // a passthrough hook on a JSON import still parses as JSON.
              let module_type = pick_hook_module_type(
                format.as_deref(),
                &requested_module_type,
              );
              Ok(deno_core::ModuleSource::new(
                module_type,
                ModuleSourceCode::String(source.into()),
                &specifier,
                None,
              ))
            }
            Ok((None, _, effective_url)) => {
              // Fallthrough: hooks didn't intercept; route through the same
              // default loader that an un-hooked load would use, so npm
              // packages and embedded modules resolve correctly. If the
              // user's load hook chain delegated via `nextLoad(newUrl)`,
              // fetch source from that URL while keeping the module's
              // identity at the original specifier (matches Node).
              let fetch_specifier = effective_url
                .as_deref()
                .and_then(|u| deno_core::ModuleSpecifier::parse(u).ok())
                .filter(|u| u != &specifier);
              let load_url = fetch_specifier.as_ref().unwrap_or(&specifier);
              let response = this.load_default(
                load_url,
                maybe_referrer.as_ref(),
                ModuleLoadOptions {
                  is_dynamic_import,
                  is_synchronous,
                  requested_module_type,
                },
              );
              let source = match response {
                deno_core::ModuleLoadResponse::Sync(r) => r?,
                deno_core::ModuleLoadResponse::Async(fut) => fut.await?,
              };
              if fetch_specifier.is_some() {
                Ok(deno_core::ModuleSource::new(
                  source.module_type,
                  source.code,
                  &specifier,
                  source.code_cache,
                ))
              } else {
                Ok(source)
              }
            }
            Err(err) => Err(JsErrorBox::generic(err)),
          }
        }
        .boxed_local(),
      );
    }

    self.load_default(original_specifier, maybe_referrer, options)
  }

  fn get_code_cache(
    &self,
    specifier: &ModuleSpecifier,
    source: &deno_core::v8::String,
  ) -> Option<SourceCodeCacheInfo> {
    let code_cache = self.shared.code_cache.as_ref()?;
    if !code_cache.enabled() {
      return None;
    }
    // Residual ext-scripts (the node polyfill closure, etc.) don't go through
    // `load()`, so they can't ride their cache on a `ModuleSource`. Read the
    // shared on-disk cache directly here. The deno version is already part of
    // the root cache key. Hash the `v8::String` the same way `CliModuleLoader`
    // does so this read side lines up with the `code_cache_ready` write side.
    let hash = FastInsecureHasher::new_without_deno_version()
      .write_hashable(source)
      .finish();
    let data = code_cache.get_sync(
      specifier,
      deno_runtime::code_cache::CodeCacheType::EsModule,
      hash,
    );
    Some(SourceCodeCacheInfo {
      hash,
      data: data.map(Cow::Owned),
    })
  }

  fn code_cache_ready(
    &self,
    specifier: Url,
    source_hash: u64,
    code_cache_data: &[u8],
  ) -> LocalBoxFuture<'static, ()> {
    if let Some(code_cache) = &self.shared.code_cache {
      code_cache.set_sync(
        specifier,
        deno_runtime::code_cache::CodeCacheType::EsModule,
        source_hash,
        code_cache_data,
      );
    }
    std::future::ready(()).boxed_local()
  }

  fn get_source_map(&self, file_name: &str) -> Option<Cow<'_, [u8]>> {
    let url = Url::parse(file_name).ok()?;
    let data = self.shared.modules.read(&url).ok()??;
    data.source_map
  }

  fn load_external_source_map(
    &self,
    source_map_url: &str,
  ) -> Option<Cow<'_, [u8]>> {
    let url = Url::parse(source_map_url).ok()?;
    let data = self.shared.modules.read(&url).ok()??;
    Some(Cow::Owned(data.data.to_vec()))
  }

  fn source_map_source_exists(&self, source_url: &str) -> Option<bool> {
    use sys_traits::FsMetadata;
    let specifier = Url::parse(source_url).ok()?;
    // only bother checking this for npm packages that might depend on this
    if self.shared.node_resolver.in_npm_package(&specifier)
      && let Ok(path) = deno_path_util::url_to_file_path(&specifier)
    {
      return self.sys.fs_is_file(path).ok();
    }

    Some(true)
  }

  fn get_source_mapped_source_line(
    &self,
    file_name: &str,
    line_number: usize,
  ) -> Option<String> {
    let specifier = Url::parse(file_name).ok()?;
    let data = self.shared.modules.read(&specifier).ok()??;

    let source = String::from_utf8_lossy(&data.data);
    // Do NOT use .lines(): it skips the terminating empty line.
    // (due to internally using_terminator() instead of .split())
    let lines: Vec<&str> = source.split('\n').collect();
    if line_number >= lines.len() {
      Some(format!(
        "{} Couldn't format source line: Line {} is out of bounds (source may have changed at runtime)",
        crate::colors::yellow("Warning"),
        line_number + 1,
      ))
    } else {
      Some(lines[line_number].to_string())
    }
  }
}

impl NodeRequireLoader for EmbeddedModuleLoader {
  fn ensure_read_permission<'a>(
    &self,
    permissions: &mut PermissionsContainer,
    path: Cow<'a, Path>,
  ) -> Result<Cow<'a, Path>, JsErrorBox> {
    if self.shared.modules.path_in_root(&path) {
      // allow reading if the file is in the root directory
      return Ok(path);
    }

    self
      .shared
      .npm_registry_permission_checker
      .ensure_read_permission(permissions, path)
      .map_err(JsErrorBox::from_err)
  }

  fn load_text_file_lossy(
    &self,
    path: &std::path::Path,
  ) -> Result<FastString, JsErrorBox> {
    match self.shared.vfs.file_entry(path) {
      Ok(file_entry) => {
        let file_bytes = self
          .shared
          .vfs
          .read_file_offset_with_len(
            file_entry.transpiled_offset.unwrap_or(file_entry.offset),
          )
          .map_err(JsErrorBox::from_err)?;
        Ok(match from_utf8_lossy_cow(file_bytes) {
          Cow::Borrowed(s) => FastString::from_static(s),
          Cow::Owned(s) => s.into(),
        })
      }
      Err(err) if err.kind() == std::io::ErrorKind::NotFound => {
        // Fall back to the host filesystem. Mirrors the fallback
        // `StandaloneModules::read` does for the ESM loader path.
        // Without it, a CJS `require()` that resolves to a file
        // outside the embedded VFS (HMR / dev mode, or a dynamic
        // import that landed on a host-FS path) would fail with
        // "path not found" even though the file exists on disk and
        // the permission layer already allowed reading it.
        use sys_traits::FsRead;
        #[allow(
          clippy::disallowed_types,
          reason = "use real file system because not in vfs"
        )]
        let bytes = sys_traits::impls::RealSys
          .fs_read(path)
          .map_err(JsErrorBox::from_err)?;
        let text = String::from_utf8_lossy(&bytes).into_owned();
        // Mirror the ESM loader path: TypeScript/JSX read from disk at
        // runtime hasn't been transpiled at compile time, so transpile
        // here. Non-TS media types are returned verbatim.
        let specifier = deno_path_util::url_from_file_path(path)
          .map_err(JsErrorBox::from_err)?;
        let media_type = MediaType::from_specifier(&specifier);
        let text = transpile_runtime_module(&specifier, media_type, text)?;
        Ok(text.into())
      }
      Err(err) => Err(JsErrorBox::from_err(err)),
    }
  }

  fn is_maybe_cjs(
    &self,
    specifier: &Url,
  ) -> Result<bool, PackageJsonLoadError> {
    let media_type = MediaType::from_specifier(specifier);
    self.shared.cjs_tracker.is_maybe_cjs(specifier, media_type)
  }

  fn is_maybe_cjs_from_require(
    &self,
    specifier: &Url,
  ) -> Result<bool, PackageJsonLoadError> {
    let media_type = MediaType::from_specifier(specifier);
    self
      .shared
      .cjs_tracker
      .is_maybe_cjs_from_require(specifier, media_type)
  }
}

struct StandaloneModuleLoaderFactory {
  shared: Arc<SharedModuleLoaderState>,
  sys: DenoRtSys,
}

impl StandaloneModuleLoaderFactory {
  pub fn create_result(
    &self,
    permissions: PermissionsContainer,
    maybe_main_module_blob: Option<(ModuleSpecifier, Arc<Blob>)>,
  ) -> CreateModuleLoaderResult {
    let hook_registry = LoaderHookRegistry::default();
    let loader = Rc::new(EmbeddedModuleLoader {
      shared: self.shared.clone(),
      hook_registry: hook_registry.clone(),
      permissions,
      sys: self.sys.clone(),
      maybe_main_module_blob,
    });
    {
      let loader = loader.clone();
      hook_registry.set_default_resolve(Rc::new(
        move |specifier: &str, referrer: &str| {
          loader
            .resolve_inner(specifier, referrer, ResolutionKind::Import)
            .map(|s| s.to_string())
            .map_err(|e| JsErrorBox::generic(e.to_string()))
        },
      ));
    }
    CreateModuleLoaderResult {
      module_loader: loader.clone(),
      node_require_loader: loader,
      hook_registry: Some(hook_registry),
    }
  }
}

impl ModuleLoaderFactory for StandaloneModuleLoaderFactory {
  fn create_for_main(
    &self,
    root_permissions: PermissionsContainer,
  ) -> CreateModuleLoaderResult {
    self.create_result(root_permissions, None)
  }

  fn create_for_worker(
    &self,
    _parent_permissions: PermissionsContainer,
    permissions: PermissionsContainer,
    maybe_main_module_blob: Option<(ModuleSpecifier, Arc<Blob>)>,
  ) -> CreateModuleLoaderResult {
    self.create_result(permissions, maybe_main_module_blob)
  }
}

struct StandaloneRootCertStoreProvider {
  sys: DenoRtSys,
  ca_stores: Option<Vec<String>>,
  ca_data: Option<CaData>,
  cell: OnceLock<Result<RootCertStore, RootCertStoreLoadError>>,
}

impl RootCertStoreProvider for StandaloneRootCertStoreProvider {
  fn get_or_try_init(&self) -> Result<&RootCertStore, JsErrorBox> {
    self
      .cell
      // get_or_try_init was not stable yet when this was written
      .get_or_init(|| {
        get_root_cert_store(
          &self.sys,
          None,
          self.ca_stores.clone(),
          self.ca_data.clone(),
        )
      })
      .as_ref()
      .map_err(|err| JsErrorBox::from_err(err.clone()))
  }
}

/// Callback to initialize additional `OpState` during worker creation.
pub type OpStateInitFn = Box<dyn FnOnce(&mut deno_core::OpState) + Send>;

/// Error raised while a compiled desktop app's main module is still loading or
/// first evaluating (e.g. a failed import, a link error, or a top-level
/// throw/await rejection during evaluation).
///
/// Such failures happen before the app's own `error` / `unhandledrejection`
/// listeners can observe them (the event loop hasn't started yet), so the
/// desktop shell can't rely on the app to report them. It carries a
/// pre-formatted, user-facing message so the shell can surface it in a native
/// dialog: a GUI-launched app (Finder/Dock) has no visible stderr, so an
/// unsurfaced startup error otherwise presents as a window that blinks open and
/// immediately closes (deno#35544).
///
/// The original error is retained as the [`std::error::Error::source`] so the
/// `{:?}` debug logs in the desktop shell still print the full cause chain.
#[derive(Debug)]
pub struct DesktopStartupError {
  /// Pre-formatted, user-facing error message.
  pub message: String,
  /// The original error, kept so the cause chain survives in debug logs.
  source: AnyError,
}

impl std::fmt::Display for DesktopStartupError {
  fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
    f.write_str(&self.message)
  }
}

impl std::error::Error for DesktopStartupError {
  fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
    Some(self.source.as_ref())
  }
}

/// Wrap a desktop main-module load/evaluation failure as a
/// [`DesktopStartupError`], formatting JS errors the same way the runtime would
/// print them to stderr while retaining the original error as the source.
fn desktop_startup_error<E: Into<AnyError>>(err: E) -> AnyError {
  let err: AnyError = err.into();
  let message = match deno_lib::util::result::js_error_downcast_ref(&err) {
    Some(js_error) => deno_runtime::fmt_errors::format_js_error(js_error, None),
    None => format!("{err:?}"),
  };
  AnyError::new(DesktopStartupError {
    message,
    source: err,
  })
}

/// Options to override default standalone runtime behavior.
/// Used by the desktop runtime to enable auto_serve and set the port.
#[derive(Default)]
pub struct RunOptions {
  pub auto_serve: bool,
  pub serve_port: Option<u16>,
  pub serve_host: Option<String>,
  /// Enable HMR file watching from this directory.
  pub hmr_watch_dir: Option<PathBuf>,
  /// Callback invoked after each successful HMR replacement.
  pub hmr_on_reload: Option<crate::hmr::HmrReloadCallback>,
  /// Callback to initialize additional OpState (e.g. desktop APIs).
  /// Called during worker creation to inject state without adding extensions.
  pub op_state_init: Option<OpStateInitFn>,
  /// Override the main module to execute instead of the embedded entrypoint.
  /// Used by desktop runtime for forked worker processes (child_process.fork)
  /// where the child should run a specific script, not the embedded entrypoint.
  pub override_main_module: Option<deno_core::url::Url>,
  /// Version and rollback state for desktop auto-update JS initialization.
  pub auto_update_version: Option<String>,
  pub auto_update_rolled_back: bool,
  /// Error reporting URL from deno.json `desktop.errorReporting.url`.
  pub error_reporting_url: Option<String>,
  /// Auto-update release base URL from deno.json `desktop.release.baseUrl`.
  /// Used as the default `url` for `Deno.autoUpdate` when none is passed.
  pub release_base_url: Option<String>,
  /// Stop on the first line of user code. Mirrors `--inspect-brk`.
  pub inspect_brk: bool,
  /// Wait for a DevTools session to attach before running user code.
  /// Mirrors `--inspect-wait`.
  pub inspect_wait: bool,
  /// Enables inspector-related setup in the worker (registers the runtime
  /// with the global inspector server). Mirrors `--inspect`/`-brk`/`-wait`.
  pub is_inspecting: bool,
}

/// Read NODE_CHANNEL_FD from the environment to initialize IPC for
/// forked child processes (e.g. child_process.fork()).
pub async fn run(
  fs: Arc<dyn FileSystem>,
  sys: DenoRtSys,
  data: StandaloneData,
) -> Result<i32, AnyError> {
  run_with_options(fs, sys, data, RunOptions::default()).await
}

pub async fn run_with_options(
  fs: Arc<dyn FileSystem>,
  sys: DenoRtSys,
  data: StandaloneData,
  options: RunOptions,
) -> Result<i32, AnyError> {
  let hmr_watch_dir = options.hmr_watch_dir;
  let hmr_on_reload = options.hmr_on_reload;
  let op_state_init = options.op_state_init;
  let override_main_module = options.override_main_module;
  let StandaloneData {
    metadata,
    modules,
    npm_snapshot,
    root_path,
    vfs,
  } = data;

  let root_cert_store_provider = Arc::new(StandaloneRootCertStoreProvider {
    sys: sys.clone(),
    ca_stores: metadata.ca_stores,
    ca_data: metadata.ca_data.map(CaData::Bytes),
    cell: Default::default(),
  });
  // use a dummy npm registry url
  let npm_registry_url = Url::parse("https://localhost/").unwrap();
  let root_dir_url = Arc::new(Url::from_directory_path(&root_path).unwrap());
  let workspace_root_path =
    hmr_watch_dir.clone().unwrap_or_else(|| root_path.clone());
  let workspace_root_dir_url =
    Arc::new(Url::from_directory_path(&workspace_root_path).unwrap());
  let entrypoint = root_dir_url.join(&metadata.entrypoint_key).unwrap();
  // When this process was spawned by node:child_process.fork() from a compiled
  // binary, the parent asks us to run a specific embedded module instead of the
  // baked-in entrypoint (see ext/node/polyfills/child_process.ts). Otherwise a
  // fork() would just re-run the parent's entrypoint (issue #26304).
  let child_entrypoint = std::env::var(INTERNAL_CHILD_ENTRYPOINT_ENV_VAR)
    .ok()
    .filter(|module_path| !module_path.is_empty());
  if child_entrypoint.is_some() {
    // Always strip the internal var so it does not leak into any grandchild
    // processes that inherit this environment, even if we end up ignoring it.
    // SAFETY: single-threaded during startup, before the runtime is created.
    unsafe { std::env::remove_var(INTERNAL_CHILD_ENTRYPOINT_ENV_VAR) };
  }
  let main_module = if let Some(url) = override_main_module {
    url
  } else {
    match child_entrypoint {
      // Only honor the override for a genuine fork() child, which always wires
      // up an IPC channel (NODE_CHANNEL_FD). This is defense in depth against an
      // accidental collision with a stray DENO_INTERNAL_CHILD_ENTRYPOINT, not a
      // security boundary: an attacker who can set one env var can set both, and
      // resolve_child_entrypoint's cwd-relative fallback will then run an
      // on-disk module with the binary's baked-in permissions. That on-disk
      // fallback is intentional (it matches fork() semantics outside a compiled
      // binary), so a hostile environment is out of scope here. NODE_CHANNEL_FD
      // is still present because node_ipc_init() consumes it later.
      Some(module_path) if std::env::var("NODE_CHANNEL_FD").is_ok() => {
        resolve_child_entrypoint(&module_path, &entrypoint, &vfs, &sys)
      }
      _ => entrypoint,
    }
  };
  let npm_global_cache_dir = root_path.join(".deno_compile_node_modules");
  let pkg_json_resolver = Arc::new(PackageJsonResolver::new(
    sys.clone(),
    Some(Arc::new(PackageJsonThreadLocalCache)),
  ));
  let npm_registry_permission_checker = {
    let mode = match &metadata.node_modules {
      Some(NodeModules::Managed {
        node_modules_dir: Some(path),
      }) => NpmRegistryReadPermissionCheckerMode::Local(PathBuf::from(path)),
      Some(NodeModules::Byonm { .. }) => {
        NpmRegistryReadPermissionCheckerMode::Byonm
      }
      Some(NodeModules::Managed {
        node_modules_dir: None,
      })
      | None => NpmRegistryReadPermissionCheckerMode::Global(
        npm_global_cache_dir.clone(),
      ),
    };
    NpmRegistryReadPermissionChecker::new(sys.clone(), mode)
  };
  let node_resolution_sys = NodeResolutionSys::new(sys.clone(), None);
  let (in_npm_pkg_checker, npm_resolver) = match metadata.node_modules {
    Some(NodeModules::Managed { node_modules_dir }) => {
      // create an npmrc that uses the fake npm_registry_url to resolve packages
      let npmrc = Arc::new(ResolvedNpmRc {
        default_config: deno_npmrc::RegistryConfigWithUrl {
          registry_url: npm_registry_url.clone(),
          config: Default::default(),
        },
        scopes: Default::default(),
        registry_configs: Default::default(),
        replace_registry_host: Default::default(),
        min_release_age_days: None,
        trust_policy: Default::default(),
        trust_policy_ignore_after_minutes: None,
        trust_policy_exclude: Vec::new(),
      });
      let npm_cache_dir = Arc::new(NpmCacheDir::new(
        &sys,
        npm_global_cache_dir,
        npmrc.get_all_known_registries_urls(),
      ));
      let snapshot = npm_snapshot.unwrap();
      let maybe_node_modules_path = node_modules_dir
        .map(|node_modules_dir| workspace_root_path.join(node_modules_dir));
      let in_npm_pkg_checker =
        DenoInNpmPackageChecker::new(CreateInNpmPkgCheckerOptions::Managed(
          ManagedInNpmPkgCheckerCreateOptions {
            root_cache_dir_url: npm_cache_dir.root_dir_url(),
            maybe_node_modules_path: maybe_node_modules_path.as_deref(),
          },
        ));
      let npm_resolution =
        Arc::new(NpmResolutionCell::new(NpmResolutionSnapshot::new(snapshot)));
      let npm_resolver = NpmResolver::<DenoRtSys>::new::<DenoRtSys>(
        NpmResolverCreateOptions::Managed(ManagedNpmResolverCreateOptions {
          npm_resolution,
          npm_cache_dir,
          sys: node_resolution_sys.clone(),
          maybe_node_modules_path,
          npm_system_info: Default::default(),
          npmrc,
          linker_mode: deno_config::deno_json::NodeModulesLinkerMode::default(),
        }),
      );
      (in_npm_pkg_checker, npm_resolver)
    }
    Some(NodeModules::Byonm {
      root_node_modules_dir,
    }) => {
      let root_node_modules_dir =
        root_node_modules_dir.map(|p| workspace_root_path.join(p));
      let in_npm_pkg_checker =
        DenoInNpmPackageChecker::new(CreateInNpmPkgCheckerOptions::Byonm);
      let npm_resolver = NpmResolver::<DenoRtSys>::new::<DenoRtSys>(
        NpmResolverCreateOptions::Byonm(ByonmNpmResolverCreateOptions {
          sys: node_resolution_sys.clone(),
          pkg_json_resolver: pkg_json_resolver.clone(),
          root_node_modules_dir,
          search_stop_dir: Some(workspace_root_path.clone()),
        }),
      );
      (in_npm_pkg_checker, npm_resolver)
    }
    None => {
      // Packages from different registries are already inlined in the binary,
      // so no need to create actual `.npmrc` configuration.
      let npmrc = create_default_npmrc();
      let npm_cache_dir = Arc::new(NpmCacheDir::new(
        &sys,
        npm_global_cache_dir,
        npmrc.get_all_known_registries_urls(),
      ));
      let in_npm_pkg_checker =
        DenoInNpmPackageChecker::new(CreateInNpmPkgCheckerOptions::Managed(
          ManagedInNpmPkgCheckerCreateOptions {
            root_cache_dir_url: npm_cache_dir.root_dir_url(),
            maybe_node_modules_path: None,
          },
        ));
      let npm_resolution = Arc::new(NpmResolutionCell::default());
      let npm_resolver = NpmResolver::<DenoRtSys>::new::<DenoRtSys>(
        NpmResolverCreateOptions::Managed(ManagedNpmResolverCreateOptions {
          npm_resolution,
          sys: node_resolution_sys.clone(),
          npm_cache_dir,
          maybe_node_modules_path: None,
          npm_system_info: Default::default(),
          npmrc: create_default_npmrc(),
          linker_mode: deno_config::deno_json::NodeModulesLinkerMode::default(),
        }),
      );
      (in_npm_pkg_checker, npm_resolver)
    }
  };

  let has_node_modules_dir = npm_resolver.root_node_modules_path().is_some();
  let node_resolver = Arc::new(NodeResolver::new(
    in_npm_pkg_checker.clone(),
    DenoIsBuiltInNodeModuleChecker,
    npm_resolver.clone(),
    pkg_json_resolver.clone(),
    node_resolution_sys,
    node_resolver::NodeResolverOptions::default(),
  ));
  let require_modules = metadata
    .require_modules
    .iter()
    .map(|key| root_dir_url.join(key).unwrap())
    .collect::<Vec<_>>();
  let cjs_tracker = Arc::new(CjsTracker::new(
    in_npm_pkg_checker.clone(),
    pkg_json_resolver.clone(),
    if metadata.unstable_config.detect_cjs {
      IsCjsResolutionMode::ImplicitTypeCommonJs
    } else if metadata.workspace_resolver.package_jsons.is_empty() {
      IsCjsResolutionMode::Disabled
    } else {
      IsCjsResolutionMode::ExplicitTypeCommonJs
    },
    require_modules.clone(),
  ));
  let npm_req_resolver = Arc::new(NpmReqResolver::new(NpmReqResolverOptions {
    sys: sys.clone(),
    in_npm_pkg_checker: in_npm_pkg_checker.clone(),
    node_resolver: node_resolver.clone(),
    npm_resolver: npm_resolver.clone(),
  }));
  let cjs_esm_code_analyzer =
    CjsCodeAnalyzer::new(cjs_tracker.clone(), modules.clone(), sys.clone());
  let cjs_module_export_analyzer = Arc::new(CjsModuleExportAnalyzer::new(
    cjs_esm_code_analyzer,
    in_npm_pkg_checker.clone(),
    node_resolver.clone(),
    npm_resolver.clone(),
    pkg_json_resolver.clone(),
    sys.clone(),
  ));
  let node_code_translator = Arc::new(NodeCodeTranslator::new(
    cjs_module_export_analyzer,
    node_resolver::analyze::NodeCodeTranslatorMode::ModuleLoader,
  ));
  let workspace_resolver = {
    let import_map = match metadata.workspace_resolver.import_map {
      Some(import_map) => Some(
        import_map::parse_from_json_with_options(
          workspace_root_dir_url.join(&import_map.specifier).unwrap(),
          &import_map.json,
          import_map::ImportMapOptions {
            address_hook: None,
            expand_imports: true,
          },
        )?
        .import_map,
      ),
      None => None,
    };
    let pkg_jsons = metadata
      .workspace_resolver
      .package_jsons
      .into_iter()
      .map(|(relative_path, json)| {
        let path = workspace_root_dir_url
          .join(&relative_path)
          .unwrap()
          .to_file_path()
          .unwrap();
        let pkg_json =
          deno_package_json::PackageJson::load_from_value(path, json)?;
        Ok(Arc::new(pkg_json))
      })
      .collect::<Result<Vec<_>, AnyError>>()?;
    WorkspaceResolver::new_raw(
      workspace_root_dir_url.clone(),
      import_map,
      metadata
        .workspace_resolver
        .jsr_pkgs
        .iter()
        .map(|pkg| ResolverWorkspaceJsrPackage {
          is_link: false, // only used for enhancing the diagnostic, which isn't shown in deno compile
          base: root_dir_url.join(&pkg.relative_base).unwrap(),
          name: pkg.name.clone(),
          version: pkg.version.clone(),
          exports: pkg.exports.clone(),
        })
        .collect(),
      pkg_jsons,
      metadata.workspace_resolver.pkg_json_resolution,
      if metadata.unstable_config.sloppy_imports {
        SloppyImportsOptions::Enabled
      } else {
        SloppyImportsOptions::Unspecified
      },
      Default::default(),
      sys.clone(),
      metadata.workspace_resolver.catalogs,
    )
  };
  let code_cache = match metadata.code_cache_key {
    Some(code_cache_key) => Some(Arc::new(DenoCompileCodeCache::new(
      root_path.with_file_name(format!(
        "{}.cache",
        root_path.file_name().unwrap().to_string_lossy()
      )),
      code_cache_key,
    ))),
    None => {
      log::debug!("Code cache disabled.");
      None
    }
  };
  let blob_store = Arc::new(BlobStore::default());
  let module_loader_factory = StandaloneModuleLoaderFactory {
    shared: Arc::new(SharedModuleLoaderState {
      blob_store: blob_store.clone(),
      cjs_tracker: cjs_tracker.clone(),
      code_cache: code_cache.clone(),
      modules,
      node_code_translator: node_code_translator.clone(),
      node_resolver: node_resolver.clone(),
      npm_module_loader: Arc::new(NpmModuleLoader::new(
        cjs_tracker.clone(),
        in_npm_pkg_checker.clone(),
        node_code_translator,
        sys.clone(),
      )),
      npm_registry_permission_checker,
      npm_req_resolver,
      vfs: vfs.clone(),
      workspace_resolver,
    }),
    sys: sys.clone(),
  };

  // Desktop runtimes always need a baseline set of permissions to function:
  // the renderer is served over loopback HTTP, so net access to 127.0.0.1
  // is mandatory; the desktop init JS reads `DENO_DESKTOP_*` env vars to
  // discover the inspector mux, HMR watch dir, etc. Without these, the
  // very first `Deno.serve(...)` or `Deno.env.get(...)` throws NotCapable
  // and aborts script execution before the event-loop IIFE registers —
  // which manifests as a blank window where nothing works (no input,
  // no auto-tests, no bindings). Granting them up front avoids forcing
  // every `deno desktop` user to remember `-A`.
  let has_desktop = op_state_init.is_some();
  let permissions = {
    let mut permissions = metadata.permissions;
    // grant read access to the vfs
    grant_vfs_read_access(&mut permissions, &root_path);

    if has_desktop {
      apply_desktop_permission_defaults(&mut permissions);
    }

    let desc_parser =
      Arc::new(RuntimePermissionDescriptorParser::new(sys.clone()));
    let permissions =
      Permissions::from_options(desc_parser.as_ref(), &permissions)?;
    PermissionsContainer::new(desc_parser, permissions)
  };
  let feature_checker = Arc::new({
    let mut checker = FeatureChecker::default();
    checker.set_exit_cb(Box::new(crate::unstable_exit_cb));
    for feature in metadata.unstable_config.features {
      // `metadata` is valid for the whole lifetime of the program, so we
      // can leak the string here.
      checker.enable_feature(feature.leak());
    }
    // Mirror cli/factory.rs: force-enable the unstable KV feature when
    // DENO_KV_REQUIRES_DISTRIBUTED_DATABASE=error so Deno.openKv() is exposed
    // and surfaces a clear "no database attached" error.
    if std::env::var("DENO_KV_REQUIRES_DISTRIBUTED_DATABASE").as_deref()
      == Ok("error")
      && !checker.check("kv")
    {
      checker.enable_feature("kv");
    }
    checker
  });
  // Resolve a persistent, per-app data directory so origin-bound storage such
  // as the default `Deno.openKv()`, `localStorage` and `caches` persists to
  // disk instead of silently falling back to an in-memory database. A compiled
  // binary is a standalone app, so we store under the platform's app data
  // directory (`%LOCALAPPDATA%`, `~/Library/Application Support`,
  // `$XDG_DATA_HOME`) keyed by the app name, rather than polluting `DENO_DIR`.
  //
  // The app name is baked in at compile time (`--app-name`, otherwise the
  // output file name), so it is stable across runs and even if the binary is
  // later renamed. Recompiling with a different `--output`/`--app-name` starts
  // a fresh store. If we can't resolve a data directory we leave both pieces
  // unset and keep the in-memory fallback.
  //
  // `metadata.app_name` is always set by binaries this version produces; the
  // fallback to the executable file stem only matters for binaries compiled
  // before the field existed.
  let app_name = metadata.app_name.unwrap_or_else(|| {
    std::env::current_exe()
      .ok()
      .and_then(|p| p.file_stem().map(|s| s.to_string_lossy().into_owned()))
      .unwrap_or_else(|| "deno-compile".to_string())
  });
  // `--app-name` is validated at compile time (see `validate_app_name`), so it
  // is safe to use directly as a single directory component here.
  let origin_data_folder_path =
    crate::binary::get_data_local_dir().map(|dir| dir.join(&app_name));
  // Only enable persistent storage when we resolved a data directory. The
  // storage key resolver must stay empty otherwise, because the worker unwraps
  // `origin_data_folder_path` whenever the key resolves to a value.
  let storage_key_resolver = if origin_data_folder_path.is_some() {
    StorageKeyResolver::from_compile_app_name(&app_name)
  } else {
    StorageKeyResolver::empty()
  };
  let lib_main_worker_options = LibMainWorkerOptions {
    argv: metadata.argv,
    log_level: WorkerLogLevel::Info,
    enable_testing_features: false,
    has_node_modules_dir,
    inspect_brk: options.inspect_brk,
    inspect_wait: options.inspect_wait,
    trace_ops: None,
    is_inspecting: options.is_inspecting,
    is_standalone: true,
    auto_serve: options.auto_serve,
    skip_op_registration: true,
    location: metadata.location,
    argv0: NpmPackageReqReference::from_specifier(&main_module)
      .ok()
      .map(|req_ref| npm_pkg_req_ref_to_binary_command(&req_ref).to_string())
      .or(std::env::args().next()),
    node_debug: std::env::var("NODE_DEBUG").ok(),
    node_cluster_unique_id: std::env::var("NODE_UNIQUE_ID").ok(),
    node_cluster_sched_policy: std::env::var("NODE_CLUSTER_SCHED_POLICY").ok(),
    origin_data_folder_path,
    seed: metadata.seed,
    unsafely_ignore_certificate_errors: metadata
      .unsafely_ignore_certificate_errors,
    node_ipc_init: deno_lib::args::node_ipc_init(&sys)?,
    serve_port: options.serve_port,
    serve_host: options.serve_host,
    otel_config: metadata.otel_config,
    no_legacy_abort: false,
    startup_snapshot: deno_snapshots::CLI_SNAPSHOT,
    residual_lazy_js_sources: deno_snapshots::RESIDUAL_LAZY_JS,
    residual_lazy_esm_sources: deno_snapshots::RESIDUAL_LAZY_ESM,
    enable_raw_imports: metadata.unstable_config.raw_imports,
    close_on_idle: !options.auto_serve,
    maybe_initial_cwd: None,
    disable_offscreen_canvas: matches!(
      std::env::var("DENO_DISABLE_OFFSCREEN_CANVAS").as_deref(),
      Ok("1") | Ok("true")
    ),
  };
  let worker_factory = LibMainWorkerFactory::new(
    blob_store,
    code_cache.map(|c| c.for_deno_core()),
    sys.maybe_native_addon_loader(),
    feature_checker,
    fs,
    None, // maybe_coverage_dir
    None, // maybe_cpu_prof_config
    Box::new(module_loader_factory),
    node_resolver.clone(),
    create_npm_process_state_provider(&npm_resolver),
    pkg_json_resolver,
    root_cert_store_provider,
    storage_key_resolver,
    sys.clone(),
    lib_main_worker_options,
    Default::default(),
    None,
  );

  // Initialize v8 once from the main thread.
  v8_set_flags(construct_v8_flags(&[], &metadata.v8_flags, vec![]));
  let is_single_threaded = metadata
    .v8_flags
    .contains(&String::from("--single-threaded"));
  let v8_platform = if is_single_threaded {
    Some(::deno_core::v8::Platform::new_single_threaded(true).make_shared())
  } else {
    None
  };
  deno_core::JsRuntime::init_platform(v8_platform);

  let main_module = match NpmPackageReqReference::from_specifier(&main_module) {
    Ok(package_ref) => {
      let pkg_folder = npm_resolver.resolve_pkg_folder_from_deno_module_req(
        package_ref.req(),
        &deno_path_util::url_from_file_path(&vfs.root().join("package.json"))?,
      )?;
      worker_factory
        .resolve_npm_binary_entrypoint(&pkg_folder, package_ref.sub_path())?
    }
    Err(_) => main_module,
  };

  let preload_modules = metadata
    .preload_modules
    .iter()
    .map(|key| root_dir_url.join(key).unwrap())
    .collect::<Vec<_>>();

  let mut worker = worker_factory.create_custom_worker(
    WorkerExecutionMode::Run,
    main_module,
    preload_modules,
    require_modules,
    permissions,
    vec![],
    Default::default(),
    None,
  )?;

  // Inject desktop API state into OpState after worker creation.
  if let Some(init_fn) = op_state_init {
    let op_state = worker.js_runtime().op_state();
    init_fn(&mut op_state.borrow_mut());
  }

  // Initialize desktop APIs (Deno.desktop.*).
  if has_desktop {
    worker
      .js_runtime()
      .execute_script("ext:deno_desktop/init", crate::desktop::DESKTOP_JS)?;

    // Initialize auto-update JS (DesktopUpdater cppgc object is already
    // registered via the desktop extension's objects).
    let js = crate::desktop::desktop_auto_update_js(
      options.auto_update_version.as_deref(),
      options.auto_update_rolled_back,
      options.release_base_url.as_deref(),
    );
    worker
      .js_runtime()
      .execute_script("ext:deno_desktop/auto_update", js)?;

    // Make the operator-configured reporting URL available to the native
    // op (and the panic hook) via `ERROR_REPORT_CONFIG`. The op reads the
    // destination from here rather than trusting a JS-supplied URL, so an
    // untrusted caller can't retarget it. `OnceLock::set` is a no-op if a
    // path that already knows the URL (e.g. `run_desktop`) set it first.
    if let Some(url) = options.error_reporting_url.as_deref() {
      deno_runtime::ops::desktop::set_error_report_config(
        url.to_string(),
        options.auto_update_version.clone(),
      );
    }

    let js = crate::desktop::desktop_error_reporting_js(
      options.error_reporting_url.as_deref(),
      options.auto_update_version.as_deref(),
    );
    worker
      .js_runtime()
      .execute_script("ext:deno_desktop/error_reporting", js)?;
  }

  if let Some(watch_dir) = hmr_watch_dir {
    let mut hmr_runner =
      crate::hmr::setup_desktop_hmr(&mut worker, watch_dir, root_path.clone())?;
    if let Some(cb) = hmr_on_reload {
      hmr_runner.set_on_reload(cb);
    }

    // Run one event loop tick so the inspector processes the
    // Debugger.enable / Runtime.enable messages before we load modules.
    // This ensures scriptParsed notifications are emitted during module load.
    worker.run_event_loop(false).await?;

    worker.execute_load_phase().await?;

    loop {
      let hmr_fut = hmr_runner.run();
      let event_loop_fut = worker.run_event_loop(false);

      tokio::select! {
        hmr_result = hmr_fut => {
          if let Err(e) = hmr_result {
            log::error!("HMR error: {:?}", e);
          }
        }
        event_loop_result = event_loop_fut => {
          event_loop_result?;
          let web_continue = worker.dispatch_beforeunload_event()?;
          if !web_continue {
            let node_continue =
              worker.dispatch_process_beforeexit_event()?;
            if !node_continue {
              break;
            }
          }
        }
      }
    }

    worker.dispatch_unload_event()?;
    worker.dispatch_process_exit_event()?;
    Ok(worker.exit_code())
  } else if has_desktop {
    // Same as `LibMainWorker::run()`, but tag any failure from the load phase
    // (main module still loading or first evaluating) as a
    // `DesktopStartupError` (see its docs for why these otherwise vanish). The
    // tag lets the desktop shell surface them in a native dialog. Errors raised
    // later, during the steady-state event loop, are left untagged: those are
    // already reported by the app's JS listeners, and tagging them would
    // produce a duplicate dialog.
    worker
      .execute_load_phase()
      .await
      .map_err(desktop_startup_error)?;
    Ok(worker.run_event_loop_to_completion().await?)
  } else {
    let exit_code = worker.run().await?;
    Ok(exit_code)
  }
}

// Internal env var carrying the module a fork()ed child of a compiled binary
// should run. Kept in sync with ext/node/polyfills/child_process.ts.
const INTERNAL_CHILD_ENTRYPOINT_ENV_VAR: &str =
  "DENO_INTERNAL_CHILD_ENTRYPOINT";

/// Resolves the module path passed to `node:child_process.fork()` to the module
/// that a compiled-binary child should run as its main module.
///
/// A relative path is first resolved against the parent entrypoint's directory
/// so it points at a sibling module embedded in the compile VFS; if no such
/// module is embedded there, it falls back to a path relative to the real cwd
/// so an on-disk module can still be loaded (matching how fork() resolves a
/// relative path outside a compiled binary). Absolute paths are used as-is: the
/// module loader consults the VFS first and falls back to disk. Falls back to
/// the entrypoint if resolution fails entirely.
fn resolve_child_entrypoint(
  module_path: &str,
  entrypoint: &Url,
  vfs: &FileBackedVfs,
  sys: &DenoRtSys,
) -> Url {
  // Falling back to the entrypoint silently would re-run the parent's
  // entrypoint, i.e. the very symptom of #26304. Warn so the failure is
  // diagnosable instead of looking like a successful fork.
  //
  // Re-running the entrypoint risks a re-fork loop if the entrypoint
  // unconditionally fork()s the same bad path. In practice this almost never
  // fires: the common "missing module" case takes the cwd branch below, where
  // resolve_path() builds a URL without an existence check, so the bad path
  // surfaces as a module load error rather than reaching here. fallback() only
  // triggers on an unparseable path or an unreadable cwd, which a re-fork would
  // hit identically (and thus surface) rather than spinning silently.
  let fallback = || {
    log::warn!(
      "Could not resolve module {:?} passed to child_process.fork(); running the compiled entrypoint instead.",
      module_path
    );
    entrypoint.clone()
  };
  let path = std::path::Path::new(module_path);
  if path.is_absolute() {
    return deno_path_util::url_from_file_path(path)
      .unwrap_or_else(|_| fallback());
  }
  // Prefer the sibling module embedded next to the entrypoint in the VFS.
  if let Ok(candidate) = entrypoint.join(module_path)
    && let Ok(candidate_path) = deno_path_util::url_to_file_path(&candidate)
    && vfs.stat(&candidate_path).is_ok()
  {
    return candidate;
  }
  // Otherwise resolve against the real cwd to load an on-disk module. fork()
  // resolves a relative module path against the process's current working
  // directory, so reading the real cwd is the intended behavior here even
  // though the lint discourages ambient cwd access elsewhere.
  #[allow(
    clippy::disallowed_methods,
    reason = "fork() resolves relative module paths against the process cwd"
  )]
  let cwd = sys.env_current_dir();
  match cwd {
    Ok(cwd) => {
      deno_core::resolve_path(module_path, &cwd).unwrap_or_else(|_| fallback())
    }
    Err(_) => fallback(),
  }
}

/// Build the `importAttributes` object handed to a `module.registerHooks()`
/// load hook. V8 only surfaces the `type` attribute at load time, so recover
/// the full `with { ... }` clause recorded during resolution and make sure
/// `type` is present for the common JSON/text/bytes case.
fn hook_load_import_attributes(
  registry: &LoaderHookRegistry,
  specifier: &str,
  requested_module_type: &RequestedModuleType,
) -> std::collections::HashMap<String, String> {
  let mut attrs = registry.take_resolved_attributes(specifier);
  if let Some(ty) = requested_module_type.as_str() {
    attrs
      .entry("type".to_string())
      .or_insert_with(|| ty.to_string());
  }
  attrs
}

/// Pick the `ModuleType` for source returned by a `module.registerHooks()`
/// load hook. Honors a hook-supplied `format` first (Node's hook contract),
/// then falls back to the importer's `with { type: "..." }` attribute.
fn pick_hook_module_type(
  format: Option<&str>,
  requested: &deno_core::RequestedModuleType,
) -> deno_core::ModuleType {
  if let Some(format) = format {
    match format {
      "json" => return deno_core::ModuleType::Json,
      "text" => return deno_core::ModuleType::Text,
      "bytes" => return deno_core::ModuleType::Bytes,
      "wasm" => return deno_core::ModuleType::Wasm,
      _ => {}
    }
  }
  match requested {
    deno_core::RequestedModuleType::Json => deno_core::ModuleType::Json,
    deno_core::RequestedModuleType::Text => deno_core::ModuleType::Text,
    deno_core::RequestedModuleType::Bytes => deno_core::ModuleType::Bytes,
    _ => deno_core::ModuleType::JavaScript,
  }
}

fn create_default_npmrc() -> Arc<ResolvedNpmRc> {
  // this is fine because multiple registries are combined into
  // one when compiling the binary
  Arc::new(ResolvedNpmRc {
    default_config: deno_npmrc::RegistryConfigWithUrl {
      registry_url: Url::parse(deno_npmrc::NPM_DEFAULT_REGISTRY).unwrap(),
      config: Default::default(),
    },
    scopes: Default::default(),
    registry_configs: Default::default(),
    replace_registry_host: Default::default(),
    min_release_age_days: None,
    trust_policy: Default::default(),
    trust_policy_ignore_after_minutes: None,
    trust_policy_exclude: Vec::new(),
  })
}

/// Grant read access to the embedded VFS root so the compiled binary
/// can read the files it shipped with. Preserves the `Some(vec![])`
/// allow-all sentinel and avoids duplicating an already-present entry.
pub(crate) fn grant_vfs_read_access(
  permissions: &mut deno_runtime::deno_permissions::PermissionsOptions,
  root_path: &std::path::Path,
) {
  let entry = root_path.to_string_lossy().into_owned();
  match &mut permissions.allow_read {
    Some(vec) if vec.is_empty() => {
      // already wide-open (--allow-read with no args / -A); nothing to
      // extend.
    }
    Some(vec) => {
      if !vec.contains(&entry) {
        vec.push(entry);
      }
    }
    None => {
      permissions.allow_read = Some(vec![entry]);
    }
  }
}

/// Loopback hosts a desktop runtime must be able to reach. The compiled
/// app's renderer talks to its `Deno.serve()` over loopback HTTP, so
/// net access here is structural, not optional.
const DESKTOP_LOOPBACK_HOSTS: &[&str] = &["127.0.0.1", "localhost", "[::1]"];

/// Env vars the DESKTOP_JS init script reads. Granting only these
/// (instead of blanket `allow-env`) keeps the user's shell environment
/// off-limits to arbitrary `Deno.env.get` calls in their app code while
/// still letting the framework boot.
const DESKTOP_ENV_KEYS: &[&str] = &[
  "DENO_DESKTOP_MUX_WS",
  "DENO_DESKTOP_HMR",
  "DENO_DESKTOP_INSPECT_INTERNAL_PORT",
  "DENO_DESKTOP_INSPECT_BRK",
  "DENO_DESKTOP_INSPECT_WAIT",
  "LAUFEY_RUNTIME_PATH",
  "LAUFEY_REMOTE_DEBUGGING_PORT",
];

/// Grant the baseline net (loopback) and env (DESKTOP_JS read keys)
/// access a desktop runtime needs to boot. Idempotent: re-applying
/// preserves any wider grants the user already had.
///
/// `Some(vec)` with `vec.is_empty()` represents allow-all (e.g. `-A`)
/// and is left untouched. `Some(vec)` with entries gets the missing
/// loopback/key entries appended. `None` is replaced with the baseline
/// vec.
pub(crate) fn apply_desktop_permission_defaults(
  permissions: &mut deno_runtime::deno_permissions::PermissionsOptions,
) {
  fn extend(slot: &mut Option<Vec<String>>, defaults: &[&str]) {
    match slot {
      Some(vec) if vec.is_empty() => {
        // already allow-all — wider than our baseline; don't narrow.
      }
      Some(vec) => {
        for entry in defaults {
          let s = (*entry).to_string();
          if !vec.contains(&s) {
            vec.push(s);
          }
        }
      }
      None => {
        *slot = Some(defaults.iter().map(|s| (*s).to_string()).collect());
      }
    }
  }

  extend(&mut permissions.allow_net, DESKTOP_LOOPBACK_HOSTS);
  extend(&mut permissions.allow_env, DESKTOP_ENV_KEYS);
}

#[cfg(test)]
mod tests {
  use deno_runtime::deno_permissions::PermissionsOptions;

  use super::DESKTOP_ENV_KEYS;
  use super::DESKTOP_LOOPBACK_HOSTS;
  use super::DesktopStartupError;
  use super::apply_desktop_permission_defaults;
  use super::desktop_startup_error;
  use super::grant_vfs_read_access;

  fn strs(v: &[&str]) -> Vec<String> {
    v.iter().map(|s| (*s).to_string()).collect()
  }

  #[test]
  fn desktop_startup_error_tags_and_carries_message() {
    // A desktop main-module load/eval failure is wrapped so the shell can
    // recognize it (downcast) and show the carried message in a dialog,
    // instead of letting it exit silently (deno#35544).
    let original =
      deno_error::JsErrorBox::generic("could not load workspace member");
    let wrapped = desktop_startup_error(original);
    let startup = wrapped
      .downcast_ref::<DesktopStartupError>()
      .expect("must be tagged as a startup error");
    assert!(startup.message.contains("could not load workspace member"));
    // Display delegates to the carried message.
    assert_eq!(startup.to_string(), startup.message);
    // The original error is retained as the source so `{:?}` debug logs keep
    // the full cause chain.
    let source =
      std::error::Error::source(startup).expect("source must be retained");
    assert!(
      source
        .to_string()
        .contains("could not load workspace member")
    );
  }

  #[test]
  fn defaults_populate_empty_options() {
    let mut p = PermissionsOptions::default();
    apply_desktop_permission_defaults(&mut p);
    assert_eq!(p.allow_net, Some(strs(DESKTOP_LOOPBACK_HOSTS)));
    assert_eq!(p.allow_env, Some(strs(DESKTOP_ENV_KEYS)));
    // Other permission categories must remain untouched.
    assert_eq!(p.allow_read, None);
    assert_eq!(p.allow_write, None);
  }

  #[test]
  fn defaults_extend_existing_allowlist_without_duplicating() {
    let mut p = PermissionsOptions {
      allow_net: Some(vec![
        "example.com".to_string(),
        "127.0.0.1".to_string(), // already present — must not duplicate
      ]),
      allow_env: Some(vec!["MY_VAR".to_string()]),
      ..Default::default()
    };
    apply_desktop_permission_defaults(&mut p);

    let net = p.allow_net.unwrap();
    // Original entries preserved.
    assert!(net.contains(&"example.com".to_string()));
    // 127.0.0.1 still appears exactly once.
    assert_eq!(net.iter().filter(|s| *s == "127.0.0.1").count(), 1);
    // Missing loopback entries appended.
    assert!(net.contains(&"localhost".to_string()));
    assert!(net.contains(&"[::1]".to_string()));

    let env = p.allow_env.unwrap();
    assert!(env.contains(&"MY_VAR".to_string()));
    for key in DESKTOP_ENV_KEYS {
      assert!(env.contains(&key.to_string()), "missing {key}");
    }
  }

  #[test]
  fn defaults_preserve_allow_all_sentinel() {
    // `Some(vec![])` is the allow-all sentinel (-A / --allow-net with
    // no args). Narrowing it down to a specific allowlist would silently
    // break user code that depends on broader access — leave it alone.
    let mut p = PermissionsOptions {
      allow_net: Some(vec![]),
      allow_env: Some(vec![]),
      ..Default::default()
    };
    apply_desktop_permission_defaults(&mut p);
    assert_eq!(p.allow_net, Some(vec![]));
    assert_eq!(p.allow_env, Some(vec![]));
  }

  #[test]
  fn defaults_are_idempotent() {
    let mut p = PermissionsOptions::default();
    apply_desktop_permission_defaults(&mut p);
    let after_once = p.clone();
    apply_desktop_permission_defaults(&mut p);
    assert_eq!(p, after_once);
  }

  // --- grant_vfs_read_access ---

  #[test]
  fn vfs_grant_populates_empty_options() {
    let mut p = PermissionsOptions::default();
    grant_vfs_read_access(&mut p, std::path::Path::new("/data/vfs"));
    assert_eq!(p.allow_read, Some(vec!["/data/vfs".to_string()]));
    // Net / env / write must remain None.
    assert_eq!(p.allow_net, None);
    assert_eq!(p.allow_env, None);
    assert_eq!(p.allow_write, None);
  }

  #[test]
  fn vfs_grant_preserves_allow_all_sentinel() {
    // `Some(vec![])` means --allow-read with no args (allow-all). The
    // compiled binary should keep its broader privilege; narrowing it
    // to a single path here would silently break user code that reads
    // outside the VFS.
    let mut p = PermissionsOptions {
      allow_read: Some(vec![]),
      ..Default::default()
    };
    grant_vfs_read_access(&mut p, std::path::Path::new("/data/vfs"));
    assert_eq!(p.allow_read, Some(vec![]));
  }

  #[test]
  fn vfs_grant_extends_existing_allowlist() {
    let mut p = PermissionsOptions {
      allow_read: Some(vec!["/etc".to_string()]),
      ..Default::default()
    };
    grant_vfs_read_access(&mut p, std::path::Path::new("/data/vfs"));
    let v = p.allow_read.unwrap();
    assert!(
      v.contains(&"/etc".to_string()),
      "original entries preserved"
    );
    assert!(v.contains(&"/data/vfs".to_string()));
  }

  #[test]
  fn vfs_grant_is_idempotent() {
    let mut p = PermissionsOptions::default();
    grant_vfs_read_access(&mut p, std::path::Path::new("/data/vfs"));
    grant_vfs_read_access(&mut p, std::path::Path::new("/data/vfs"));
    // Same path twice must not duplicate.
    assert_eq!(p.allow_read, Some(vec!["/data/vfs".to_string()]));
  }

  #[test]
  fn vfs_grant_handles_path_with_unicode() {
    let mut p = PermissionsOptions::default();
    grant_vfs_read_access(&mut p, std::path::Path::new("/Users/café/My App"));
    assert_eq!(p.allow_read, Some(vec!["/Users/café/My App".to_string()]));
  }
}
