summaryrefslogtreecommitdiff
path: root/cli/tools/vendor/mod.rs
diff options
context:
space:
mode:
authorDavid Sherret <dsherret@users.noreply.github.com>2022-06-14 10:05:37 -0400
committerGitHub <noreply@github.com>2022-06-14 10:05:37 -0400
commit443041c23e2e02ea59d69e1f2093c67ddfd818fd (patch)
tree06f449773377ec655982d00cdaf4bbd60857973f /cli/tools/vendor/mod.rs
parentfc3a966a2d0be8fc76c384603bf18b55e0bbcf14 (diff)
feat(vendor): support using an existing import map (#14836)
Diffstat (limited to 'cli/tools/vendor/mod.rs')
-rw-r--r--cli/tools/vendor/mod.rs259
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"
+}
+"#
+ );
+ }
}