summaryrefslogtreecommitdiff
path: root/cli/tools/vendor/mod.rs
diff options
context:
space:
mode:
Diffstat (limited to 'cli/tools/vendor/mod.rs')
-rw-r--r--cli/tools/vendor/mod.rs336
1 files changed, 260 insertions, 76 deletions
diff --git a/cli/tools/vendor/mod.rs b/cli/tools/vendor/mod.rs
index d478c2b57..5690f5b22 100644
--- a/cli/tools/vendor/mod.rs
+++ b/cli/tools/vendor/mod.rs
@@ -5,6 +5,7 @@ use std::path::PathBuf;
use std::sync::Arc;
use deno_ast::ModuleSpecifier;
+use deno_ast::TextChange;
use deno_core::anyhow::bail;
use deno_core::anyhow::Context;
use deno_core::error::AnyError;
@@ -12,6 +13,7 @@ use deno_core::resolve_url_or_path;
use log::warn;
use crate::args::CliOptions;
+use crate::args::ConfigFile;
use crate::args::Flags;
use crate::args::FmtOptionsConfig;
use crate::args::VendorFlags;
@@ -51,6 +53,9 @@ pub async fn vendor(
cli_options.initial_cwd(),
)
.await?;
+ let npm_package_count = graph.npm_packages.len();
+ let try_add_node_modules_dir = npm_package_count > 0
+ && cli_options.node_modules_dir_enablement().unwrap_or(true);
let vendored_count = build::build(
graph,
factory.parsed_source_cache()?,
@@ -70,9 +75,48 @@ pub async fn vendor(
},
raw_output_dir.display(),
);
+
+ let try_add_import_map = vendored_count > 0;
+ let modified_result = maybe_update_config_file(
+ &output_dir,
+ cli_options,
+ try_add_import_map,
+ try_add_node_modules_dir,
+ );
+
+ // cache the node_modules folder when it's been added to the config file
+ if modified_result.added_node_modules_dir {
+ let node_modules_path = cli_options.node_modules_dir_path().or_else(|| {
+ cli_options
+ .maybe_config_file_specifier()
+ .filter(|c| c.scheme() == "file")
+ .and_then(|c| c.to_file_path().ok())
+ .map(|config_path| config_path.parent().unwrap().join("node_modules"))
+ });
+ if let Some(node_modules_path) = node_modules_path {
+ factory
+ .create_node_modules_npm_fs_resolver(node_modules_path)
+ .await?
+ .cache_packages()
+ .await?;
+ }
+ log::info!(
+ concat!(
+ "Vendored {} npm {} into node_modules directory. Set `nodeModulesDir: false` ",
+ "in the Deno configuration file to disable vendoring npm packages in the future.",
+ ),
+ npm_package_count,
+ if npm_package_count == 1 {
+ "package"
+ } else {
+ "packages"
+ },
+ );
+ }
+
if vendored_count > 0 {
let import_map_path = raw_output_dir.join("import_map.json");
- if maybe_update_config_file(&output_dir, cli_options) {
+ if modified_result.updated_import_map {
log::info!(
concat!(
"\nUpdated your local Deno configuration file with a reference to the ",
@@ -154,107 +198,156 @@ fn validate_options(
Ok(())
}
-fn maybe_update_config_file(output_dir: &Path, options: &CliOptions) -> bool {
+fn maybe_update_config_file(
+ output_dir: &Path,
+ options: &CliOptions,
+ try_add_import_map: bool,
+ try_add_node_modules_dir: bool,
+) -> ModifiedResult {
assert!(output_dir.is_absolute());
- let config_file_specifier = match options.maybe_config_file_specifier() {
- Some(f) => f,
- None => return false,
+ let config_file = match options.maybe_config_file() {
+ Some(config_file) => config_file,
+ None => return ModifiedResult::default(),
};
+ if config_file.specifier.scheme() != "file" {
+ return ModifiedResult::default();
+ }
- let fmt_config = options
- .maybe_config_file()
- .as_ref()
- .and_then(|config| config.to_fmt_config().ok())
+ let fmt_config = config_file
+ .to_fmt_config()
+ .ok()
.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(),
+ config_file,
&fmt_config.options,
+ if try_add_import_map {
+ Some(
+ ModuleSpecifier::from_file_path(output_dir.join("import_map.json"))
+ .unwrap(),
+ )
+ } else {
+ None
+ },
+ try_add_node_modules_dir,
);
match result {
- Ok(()) => true,
+ Ok(modified_result) => modified_result,
Err(err) => {
warn!("Error updating config file. {:#}", err);
- false
+ ModifiedResult::default()
}
}
}
fn update_config_file(
- config_specifier: &ModuleSpecifier,
- import_map_specifier: &ModuleSpecifier,
+ config_file: &ConfigFile,
fmt_options: &FmtOptionsConfig,
-) -> Result<(), AnyError> {
- if config_specifier.scheme() != "file" {
- return Ok(());
- }
-
- let config_path = specifier_to_file_path(config_specifier)?;
+ import_map_specifier: Option<ModuleSpecifier>,
+ try_add_node_modules_dir: bool,
+) -> Result<ModifiedResult, AnyError> {
+ let config_path = specifier_to_file_path(&config_file.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)
- {
+ let import_map_specifier =
+ import_map_specifier.and_then(|import_map_specifier| {
+ relative_specifier(&config_file.specifier, &import_map_specifier)
+ });
+ let modified_result = update_config_text(
+ &config_text,
+ fmt_options,
+ import_map_specifier.as_deref(),
+ try_add_node_modules_dir,
+ )?;
+ if let Some(new_text) = &modified_result.new_text {
std::fs::write(config_path, new_text)?;
}
+ Ok(modified_result)
+}
- Ok(())
+#[derive(Default)]
+struct ModifiedResult {
+ updated_import_map: bool,
+ added_node_modules_dir: bool,
+ new_text: Option<String>,
}
fn update_config_text(
text: &str,
- import_map_specifier: &str,
fmt_options: &FmtOptionsConfig,
-) -> Option<String> {
+ import_map_specifier: Option<&str>,
+ try_add_node_modules_dir: bool,
+) -> Result<ModifiedResult, AnyError> {
use jsonc_parser::ast::ObjectProp;
use jsonc_parser::ast::Value;
let ast =
- jsonc_parser::parse_to_ast(text, &Default::default(), &Default::default())
- .ok()?;
+ jsonc_parser::parse_to_ast(text, &Default::default(), &Default::default())?;
let obj = match ast.value {
Some(Value::Object(obj)) => obj,
- _ => return None, // shouldn't happen, so ignore
+ _ => bail!("Failed updating config file due to no object."),
};
- 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 mut modified_result = ModifiedResult::default();
+ let mut text_changes = Vec::new();
+ let mut should_format = false;
+
+ if try_add_node_modules_dir {
+ // Only modify the nodeModulesDir property if it's not set
+ // as this allows people to opt-out of this when vendoring
+ // by specifying `nodeModulesDir: false`
+ if obj.get("nodeModulesDir").is_none() {
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))
+ text_changes.push(TextChange {
+ range: insert_position..insert_position,
+ new_text: r#""nodeModulesDir": true"#.to_string(),
+ });
+ should_format = true;
+ modified_result.added_node_modules_dir = true;
}
- // shouldn't happen, so ignore
- Some(_) => None,
}
+
+ if let Some(import_map_specifier) = import_map_specifier {
+ let import_map_specifier = import_map_specifier.replace('\"', "\\\"");
+ match obj.get("importMap") {
+ Some(ObjectProp {
+ value: Value::StringLit(lit),
+ ..
+ }) => {
+ text_changes.push(TextChange {
+ range: lit.range.start..lit.range.end,
+ new_text: format!("\"{}\"", import_map_specifier),
+ });
+ modified_result.updated_import_map = true;
+ }
+ 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;
+ text_changes.push(TextChange {
+ range: insert_position..insert_position,
+ new_text: format!(r#""importMap": "{}""#, import_map_specifier),
+ });
+ should_format = true;
+ modified_result.updated_import_map = true;
+ }
+ // shouldn't happen
+ Some(_) => {
+ bail!("Failed updating importMap in config file due to invalid type.")
+ }
+ }
+ }
+
+ if text_changes.is_empty() {
+ return Ok(modified_result);
+ }
+
+ let new_text = deno_ast::apply_text_changes(text, text_changes);
+ modified_result.new_text = if should_format {
+ format_json(&new_text, fmt_options)
+ .ok()
+ .map(|formatted_text| formatted_text.unwrap_or(new_text))
+ } else {
+ Some(new_text)
+ };
+ Ok(modified_result)
}
fn is_dir_empty(dir_path: &Path) -> Result<bool, AnyError> {
@@ -288,36 +381,94 @@ mod internal_test {
#[test]
fn update_config_text_no_existing_props_add_prop() {
- let text = update_config_text(
+ let result = update_config_text(
+ "{\n}",
+ &Default::default(),
+ Some("./vendor/import_map.json"),
+ false,
+ )
+ .unwrap();
+ assert!(result.updated_import_map);
+ assert!(!result.added_node_modules_dir);
+ assert_eq!(
+ result.new_text.unwrap(),
+ r#"{
+ "importMap": "./vendor/import_map.json"
+}
+"#
+ );
+
+ let result = update_config_text(
"{\n}",
- "./vendor/import_map.json",
&Default::default(),
+ Some("./vendor/import_map.json"),
+ true,
)
.unwrap();
+ assert!(result.updated_import_map);
+ assert!(result.added_node_modules_dir);
assert_eq!(
- text,
+ result.new_text.unwrap(),
r#"{
+ "nodeModulesDir": true,
"importMap": "./vendor/import_map.json"
}
"#
);
+
+ let result =
+ update_config_text("{\n}", &Default::default(), None, true).unwrap();
+ assert!(!result.updated_import_map);
+ assert!(result.added_node_modules_dir);
+ assert_eq!(
+ result.new_text.unwrap(),
+ r#"{
+ "nodeModulesDir": true
+}
+"#
+ );
}
#[test]
fn update_config_text_existing_props_add_prop() {
- let text = update_config_text(
+ let result = update_config_text(
r#"{
"tasks": {
"task1": "other"
}
}
"#,
- "./vendor/import_map.json",
&Default::default(),
+ Some("./vendor/import_map.json"),
+ false,
+ )
+ .unwrap();
+ assert_eq!(
+ result.new_text.unwrap(),
+ r#"{
+ "tasks": {
+ "task1": "other"
+ },
+ "importMap": "./vendor/import_map.json"
+}
+"#
+ );
+
+ // trailing comma
+ let result = update_config_text(
+ r#"{
+ "tasks": {
+ "task1": "other"
+ },
+}
+"#,
+ &Default::default(),
+ Some("./vendor/import_map.json"),
+ false,
)
.unwrap();
assert_eq!(
- text,
+ result.new_text.unwrap(),
r#"{
"tasks": {
"task1": "other"
@@ -330,21 +481,54 @@ mod internal_test {
#[test]
fn update_config_text_update_prop() {
- let text = update_config_text(
+ let result = update_config_text(
r#"{
"importMap": "./local.json"
}
"#,
- "./vendor/import_map.json",
&Default::default(),
+ Some("./vendor/import_map.json"),
+ false,
)
.unwrap();
assert_eq!(
- text,
+ result.new_text.unwrap(),
r#"{
"importMap": "./vendor/import_map.json"
}
"#
);
}
+
+ #[test]
+ fn no_update_node_modules_dir() {
+ // will not update if this is already set (even if it's false)
+ let result = update_config_text(
+ r#"{
+ "nodeModulesDir": false
+}
+"#,
+ &Default::default(),
+ None,
+ true,
+ )
+ .unwrap();
+ assert!(!result.added_node_modules_dir);
+ assert!(!result.updated_import_map);
+ assert_eq!(result.new_text, None);
+
+ let result = update_config_text(
+ r#"{
+ "nodeModulesDir": true
+}
+"#,
+ &Default::default(),
+ None,
+ true,
+ )
+ .unwrap();
+ assert!(!result.added_node_modules_dir);
+ assert!(!result.updated_import_map);
+ assert_eq!(result.new_text, None);
+ }
}