// Copyright 2018-2022 the Deno authors. All rights reserved. MIT license. use std::path::Path; use std::path::PathBuf; use deno_core::anyhow::bail; use deno_core::anyhow::Context; use deno_core::error::AnyError; use deno_core::resolve_url_or_path; use deno_runtime::permissions::Permissions; use crate::flags::VendorFlags; use crate::fs_util; use crate::lockfile; use crate::proc_state::ProcState; use crate::resolver::ImportMapResolver; use crate::resolver::JsxResolver; use crate::tools::vendor::specifiers::is_remote_specifier_text; mod analyze; mod build; mod import_map; mod mappings; mod specifiers; #[cfg(test)] mod test; pub async fn vendor(ps: ProcState, flags: VendorFlags) -> Result<(), AnyError> { let raw_output_dir = match &flags.output_path { Some(output_path) => output_path.to_owned(), None => PathBuf::from("vendor/"), }; let output_dir = fs_util::resolve_from_cwd(&raw_output_dir)?; validate_output_dir(&output_dir, &flags, &ps)?; let graph = create_graph(&ps, &flags).await?; let vendored_count = build::build(&graph, &output_dir, &build::RealVendorEnvironment)?; eprintln!( r#"Vendored {} {} into {} directory. To use vendored modules, specify the `--import-map` flag when invoking deno subcommands: deno run -A --import-map {} {}"#, vendored_count, if vendored_count == 1 { "module" } else { "modules" }, raw_output_dir.display(), raw_output_dir.join("import_map.json").display(), flags .specifiers .iter() .map(|s| s.as_str()) .find(|s| !is_remote_specifier_text(s)) .unwrap_or("main.ts"), ); Ok(()) } fn validate_output_dir( output_dir: &Path, flags: &VendorFlags, ps: &ProcState, ) -> Result<(), AnyError> { if !flags.force && !is_dir_empty(output_dir)? { bail!(concat!( "Output directory was not empty. Please specify an empty directory or use ", "--force to ignore this error and potentially overwrite its contents.", )); } // check the import map if let Some(import_map_path) = ps .maybe_import_map .as_ref() .and_then(|m| m.base_url().to_file_path().ok()) .and_then(|p| fs_util::canonicalize_path(&p).ok()) { // make the output directory in order to canonicalize it for the check below std::fs::create_dir_all(&output_dir)?; let output_dir = fs_util::canonicalize_path(output_dir).with_context(|| { format!("Failed to canonicalize: {}", output_dir.display()) })?; if import_map_path.starts_with(&output_dir) { // We don't allow using the output directory to help generate the new state // of itself because supporting this scenario adds a lot of complexity. bail!( "Using an import map found in the output directory is not supported." ); } } Ok(()) } fn is_dir_empty(dir_path: &Path) -> Result { match std::fs::read_dir(&dir_path) { Ok(mut dir) => Ok(dir.next().is_none()), Err(err) if err.kind() == std::io::ErrorKind::NotFound => Ok(true), Err(err) => { bail!("Error reading directory {}: {}", dir_path.display(), err) } } } async fn create_graph( ps: &ProcState, flags: &VendorFlags, ) -> Result { let entry_points = flags .specifiers .iter() .map(|p| { let url = resolve_url_or_path(p)?; Ok((url, deno_graph::ModuleKind::Esm)) }) .collect::, AnyError>>()?; // todo(dsherret): there is a lot of copy and paste here from // other parts of the codebase. We should consolidate this. let mut cache = crate::cache::FetchCacher::new( ps.dir.gen_cache.clone(), ps.file_fetcher.clone(), Permissions::allow_all(), Permissions::allow_all(), ); let maybe_locker = lockfile::as_maybe_locker(ps.lockfile.clone()); let maybe_imports = if let Some(config_file) = &ps.maybe_config_file { config_file.to_maybe_imports()? } else { None }; let maybe_import_map_resolver = ps.maybe_import_map.clone().map(ImportMapResolver::new); let maybe_jsx_resolver = ps .maybe_config_file .as_ref() .map(|cf| { cf.to_maybe_jsx_import_source_module() .map(|im| JsxResolver::new(im, maybe_import_map_resolver.clone())) }) .flatten(); let maybe_resolver = if maybe_jsx_resolver.is_some() { maybe_jsx_resolver.as_ref().map(|jr| jr.as_resolver()) } else { maybe_import_map_resolver .as_ref() .map(|im| im.as_resolver()) }; let graph = deno_graph::create_graph( entry_points, false, maybe_imports, &mut cache, maybe_resolver, maybe_locker, None, None, ) .await; graph.lock()?; graph.valid()?; Ok(graph) }