diff options
Diffstat (limited to 'cli/tools/vendor/mod.rs')
-rw-r--r-- | cli/tools/vendor/mod.rs | 259 |
1 files changed, 228 insertions, 31 deletions
diff --git a/cli/tools/vendor/mod.rs b/cli/tools/vendor/mod.rs index 3a5455aae..69c759154 100644 --- a/cli/tools/vendor/mod.rs +++ b/cli/tools/vendor/mod.rs @@ -3,19 +3,24 @@ use std::path::Path; use std::path::PathBuf; +use deno_ast::ModuleSpecifier; 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 log::warn; +use crate::config_file::FmtOptionsConfig; use crate::flags::VendorFlags; use crate::fs_util; +use crate::fs_util::relative_specifier; +use crate::fs_util::specifier_to_file_path; 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; +use crate::tools::fmt::format_json; mod analyze; mod build; @@ -33,14 +38,15 @@ pub async fn vendor(ps: ProcState, flags: VendorFlags) -> Result<(), AnyError> { 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)?; + let vendored_count = build::build( + graph, + &output_dir, + ps.maybe_import_map.as_deref(), + &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 {} {}"#, + concat!("Vendored {} {} into {} directory.",), vendored_count, if vendored_count == 1 { "module" @@ -48,14 +54,31 @@ To use vendored modules, specify the `--import-map` flag when invoking deno subc "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"), ); + if vendored_count > 0 { + let import_map_path = raw_output_dir.join("import_map.json"); + if maybe_update_config_file(&output_dir, &ps) { + eprintln!( + concat!( + "\nUpdated your local Deno configuration file with a reference to the ", + "new vendored import map at {}. Invoking Deno subcommands will now ", + "automatically resolve using the vendored modules. You may override ", + "this by providing the `--import-map <other-import-map>` flag or by ", + "manually editing your Deno configuration file.", + ), + import_map_path.display(), + ); + } else { + eprintln!( + concat!( + "\nTo use vendored modules, specify the `--import-map {}` flag when ", + r#"invoking Deno subcommands or add an `"importMap": "<path_to_vendored_import_map>"` "#, + "entry to a deno.json file.", + ), + import_map_path.display(), + ); + } + } Ok(()) } @@ -76,7 +99,7 @@ fn validate_output_dir( if let Some(import_map_path) = ps .maybe_import_map .as_ref() - .and_then(|m| m.base_url().to_file_path().ok()) + .and_then(|m| specifier_to_file_path(m.base_url()).ok()) .and_then(|p| fs_util::canonicalize_path(&p).ok()) { // make the output directory in order to canonicalize it for the check below @@ -87,10 +110,21 @@ fn validate_output_dir( })?; 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. + // canonicalize to make the test for this pass on the CI + let cwd = fs_util::canonicalize_path(&std::env::current_dir()?)?; + // We don't allow using the output directory to help generate the + // new state because this may lead to cryptic error messages. bail!( - "Using an import map found in the output directory is not supported." + concat!( + "Specifying an import map file ({}) in the deno vendor output ", + "directory is not supported. Please specify no import map or one ", + "located outside this directory." + ), + import_map_path + .strip_prefix(&cwd) + .unwrap_or(&import_map_path) + .display() + .to_string(), ); } } @@ -98,6 +132,104 @@ fn validate_output_dir( Ok(()) } +fn maybe_update_config_file(output_dir: &Path, ps: &ProcState) -> bool { + assert!(output_dir.is_absolute()); + let config_file = match &ps.maybe_config_file { + Some(f) => f, + None => return false, + }; + let fmt_config = config_file + .to_fmt_config() + .unwrap_or_default() + .unwrap_or_default(); + let result = update_config_file( + &config_file.specifier, + &ModuleSpecifier::from_file_path(output_dir.join("import_map.json")) + .unwrap(), + &fmt_config.options, + ); + match result { + Ok(()) => true, + Err(err) => { + warn!("Error updating config file. {:#}", err); + false + } + } +} + +fn update_config_file( + config_specifier: &ModuleSpecifier, + import_map_specifier: &ModuleSpecifier, + fmt_options: &FmtOptionsConfig, +) -> Result<(), AnyError> { + if config_specifier.scheme() != "file" { + return Ok(()); + } + + let config_path = specifier_to_file_path(config_specifier)?; + let config_text = std::fs::read_to_string(&config_path)?; + let relative_text = + match relative_specifier(config_specifier, import_map_specifier) { + Some(text) => text, + None => return Ok(()), // ignore + }; + if let Some(new_text) = + update_config_text(&config_text, &relative_text, fmt_options) + { + std::fs::write(config_path, new_text)?; + } + + Ok(()) +} + +fn update_config_text( + text: &str, + import_map_specifier: &str, + fmt_options: &FmtOptionsConfig, +) -> Option<String> { + use jsonc_parser::ast::ObjectProp; + use jsonc_parser::ast::Value; + let ast = jsonc_parser::parse_to_ast(text, &Default::default()).ok()?; + let obj = match ast.value { + Some(Value::Object(obj)) => obj, + _ => return None, // shouldn't happen, so ignore + }; + let import_map_specifier = import_map_specifier.replace('\"', "\\\""); + + match obj.get("importMap") { + Some(ObjectProp { + value: Value::StringLit(lit), + .. + }) => Some(format!( + "{}{}{}", + &text[..lit.range.start + 1], + import_map_specifier, + &text[lit.range.end - 1..], + )), + None => { + // insert it crudely at a position that won't cause any issues + // with comments and format after to make it look nice + let insert_position = obj.range.end - 1; + let insert_text = format!( + r#"{}"importMap": "{}""#, + if obj.properties.is_empty() { "" } else { "," }, + import_map_specifier + ); + let new_text = format!( + "{}{}{}", + &text[..insert_position], + insert_text, + &text[insert_position..], + ); + format_json(&new_text, fmt_options) + .ok() + .map(|formatted_text| formatted_text.unwrap_or(new_text)) + } + // shouldn't happen, so ignore + Some(_) => None, + } +} + fn is_dir_empty(dir_path: &Path) -> Result<bool, AnyError> { match std::fs::read_dir(&dir_path) { Ok(mut dir) => Ok(dir.next().is_none()), @@ -149,20 +281,85 @@ async fn create_graph( .map(|im| im.as_resolver()) }; - let graph = deno_graph::create_graph( - entry_points, - false, - maybe_imports, - &mut cache, - maybe_resolver, - maybe_locker, - None, - None, + Ok( + deno_graph::create_graph( + entry_points, + false, + maybe_imports, + &mut cache, + maybe_resolver, + maybe_locker, + None, + None, + ) + .await, ) - .await; +} - graph.lock()?; - graph.valid()?; +#[cfg(test)] +mod internal_test { + use super::*; + use pretty_assertions::assert_eq; - Ok(graph) + #[test] + fn update_config_text_no_existing_props_add_prop() { + let text = update_config_text( + "{\n}", + "./vendor/import_map.json", + &Default::default(), + ) + .unwrap(); + assert_eq!( + text, + r#"{ + "importMap": "./vendor/import_map.json" +} +"# + ); + } + + #[test] + fn update_config_text_existing_props_add_prop() { + let text = update_config_text( + r#"{ + "tasks": { + "task1": "other" + } +} +"#, + "./vendor/import_map.json", + &Default::default(), + ) + .unwrap(); + assert_eq!( + text, + r#"{ + "tasks": { + "task1": "other" + }, + "importMap": "./vendor/import_map.json" +} +"# + ); + } + + #[test] + fn update_config_text_update_prop() { + let text = update_config_text( + r#"{ + "importMap": "./local.json" +} +"#, + "./vendor/import_map.json", + &Default::default(), + ) + .unwrap(); + assert_eq!( + text, + r#"{ + "importMap": "./vendor/import_map.json" +} +"# + ); + } } |