diff --git a/cli/args/flags.rs b/cli/args/flags.rs index 9e50ccc4927d59..5470be97642744 100644 --- a/cli/args/flags.rs +++ b/cli/args/flags.rs @@ -412,13 +412,6 @@ pub struct UpgradeFlags { pub version_or_hash_or_channel: Option, } -#[derive(Clone, Debug, Eq, PartialEq)] -pub struct VendorFlags { - pub specifiers: Vec, - pub output_path: Option, - pub force: bool, -} - #[derive(Clone, Debug, Eq, PartialEq)] pub struct PublishFlags { pub token: Option, @@ -463,7 +456,7 @@ pub enum DenoSubcommand { Test(TestFlags), Types, Upgrade(UpgradeFlags), - Vendor(VendorFlags), + Vendor, Publish(PublishFlags), Help(HelpFlags), } @@ -3008,58 +3001,14 @@ update to a different location, use the --output flag: }) } -// TODO(bartlomieju): this subcommand is now deprecated, remove it in Deno 2. fn vendor_subcommand() -> Command { command("vendor", - "⚠️ Warning: `deno vendor` is deprecated and will be removed in Deno 2.0. -Add `\"vendor\": true` to your `deno.json` or use the `--vendor` flag instead. - -Vendor remote modules into a local directory. + "⚠️ `deno vendor` was removed in Deno 2. -Analyzes the provided modules along with their dependencies, downloads -remote modules to the output directory, and produces an import map that -maps remote specifiers to the downloaded files. - deno vendor main.ts - deno run --import-map vendor/import_map.json main.ts - -Remote modules and multiple modules may also be specified: - deno vendor main.ts test.deps.ts jsr:@std/path", +See the Deno 1.x to 2.x Migration Guide for migration instructions: https://docs.deno.com/runtime/manual/advanced/migrate_deprecations", UnstableArgsConfig::ResolutionOnly ) .hide(true) - .defer(|cmd| cmd - .arg( - Arg::new("specifiers") - .num_args(1..) - .action(ArgAction::Append) - .required_unless_present("help"), - ) - .arg( - Arg::new("output") - .long("output") - .help("The directory to output the vendored modules to") - .value_parser(value_parser!(String)) - .value_hint(ValueHint::DirPath), - ) - .arg( - Arg::new("force") - .long("force") - .short('f') - .help( - "Forcefully overwrite conflicting files in existing output directory", - ) - .action(ArgAction::SetTrue), - ) - .arg(no_config_arg()) - .arg(config_arg()) - .arg(import_map_arg()) - .arg(lock_arg()) - .arg(node_modules_dir_arg()) - .arg(vendor_arg()) - .arg(reload_arg()) - .arg(ca_file_arg()) - .arg(unsafely_ignore_certificate_errors_arg()) - ) } fn publish_subcommand() -> Command { @@ -4751,24 +4700,8 @@ fn upgrade_parse(flags: &mut Flags, matches: &mut ArgMatches) { }); } -fn vendor_parse(flags: &mut Flags, matches: &mut ArgMatches) { - unstable_args_parse(flags, matches, UnstableArgsConfig::ResolutionOnly); - ca_file_arg_parse(flags, matches); - unsafely_ignore_certificate_errors_parse(flags, matches); - config_args_parse(flags, matches); - import_map_arg_parse(flags, matches); - lock_arg_parse(flags, matches); - node_modules_and_vendor_dir_arg_parse(flags, matches); - reload_arg_parse(flags, matches); - - flags.subcommand = DenoSubcommand::Vendor(VendorFlags { - specifiers: matches - .remove_many::("specifiers") - .map(|p| p.collect()) - .unwrap_or_default(), - output_path: matches.remove_one::("output"), - force: matches.get_flag("force"), - }); +fn vendor_parse(flags: &mut Flags, _matches: &mut ArgMatches) { + flags.subcommand = DenoSubcommand::Vendor } fn publish_parse(flags: &mut Flags, matches: &mut ArgMatches) { @@ -9671,57 +9604,6 @@ mod tests { assert!(&error_message.contains("--watch[=...]")); } - #[test] - fn vendor_minimal() { - let r = flags_from_vec(svec!["deno", "vendor", "mod.ts",]); - assert_eq!( - r.unwrap(), - Flags { - subcommand: DenoSubcommand::Vendor(VendorFlags { - specifiers: svec!["mod.ts"], - force: false, - output_path: None, - }), - ..Flags::default() - } - ); - } - - #[test] - fn vendor_all() { - let r = flags_from_vec(svec![ - "deno", - "vendor", - "--config", - "deno.json", - "--import-map", - "import_map.json", - "--lock", - "lock.json", - "--force", - "--output", - "out_dir", - "--reload", - "mod.ts", - "deps.test.ts", - ]); - assert_eq!( - r.unwrap(), - Flags { - subcommand: DenoSubcommand::Vendor(VendorFlags { - specifiers: svec!["mod.ts", "deps.test.ts"], - force: true, - output_path: Some(String::from("out_dir")), - }), - config_flag: ConfigFlag::Path("deno.json".to_owned()), - import_map_path: Some("import_map.json".to_string()), - lock: Some(String::from("lock.json")), - reload: true, - ..Flags::default() - } - ); - } - #[test] fn task_subcommand() { let r = flags_from_vec(svec!["deno", "task", "build", "hello", "world",]); diff --git a/cli/args/mod.rs b/cli/args/mod.rs index 50132b1fb502cf..9be115d5c42e5c 100644 --- a/cli/args/mod.rs +++ b/cli/args/mod.rs @@ -1217,11 +1217,6 @@ impl CliOptions { NPM_PROCESS_STATE.is_some() } - /// Overrides the import map specifier to use. - pub fn set_import_map_specifier(&mut self, path: Option) { - self.overrides.import_map_specifier = Some(path); - } - pub fn has_node_modules_dir(&self) -> bool { self.maybe_node_modules_folder.is_some() } @@ -1230,21 +1225,6 @@ impl CliOptions { self.maybe_node_modules_folder.as_ref() } - pub fn with_node_modules_dir_path(&self, path: PathBuf) -> Self { - Self { - flags: self.flags.clone(), - initial_cwd: self.initial_cwd.clone(), - maybe_node_modules_folder: Some(path), - npmrc: self.npmrc.clone(), - maybe_lockfile: self.maybe_lockfile.clone(), - start_dir: self.start_dir.clone(), - overrides: self.overrides.clone(), - disable_deprecated_api_warning: self.disable_deprecated_api_warning, - verbose_deprecated_api_warning: self.verbose_deprecated_api_warning, - deno_dir_provider: self.deno_dir_provider.clone(), - } - } - pub fn node_modules_dir( &self, ) -> Result, AnyError> { diff --git a/cli/main.rs b/cli/main.rs index 33ccc198c8cf4d..c963cb21cab4a9 100644 --- a/cli/main.rs +++ b/cli/main.rs @@ -283,9 +283,7 @@ async fn run_subcommand(flags: Arc) -> Result { "This deno was built without the \"upgrade\" feature. Please upgrade using the installation method originally used to install Deno.", 1, ), - DenoSubcommand::Vendor(vendor_flags) => spawn_subcommand(async { - tools::vendor::vendor(flags, vendor_flags).await - }), + DenoSubcommand::Vendor => exit_with_message("⚠️ `deno vendor` was removed in Deno 2.\n\nSee the Deno 1.x to 2.x Migration Guide for migration instructions: https://docs.deno.com/runtime/manual/advanced/migrate_deprecations", 1), DenoSubcommand::Publish(publish_flags) => spawn_subcommand(async { tools::registry::publish(flags, publish_flags).await }), diff --git a/cli/tools/mod.rs b/cli/tools/mod.rs index 0b720e2ac6dd81..a458da9f1b7333 100644 --- a/cli/tools/mod.rs +++ b/cli/tools/mod.rs @@ -19,4 +19,3 @@ pub mod serve; pub mod task; pub mod test; pub mod upgrade; -pub mod vendor; diff --git a/cli/tools/vendor/analyze.rs b/cli/tools/vendor/analyze.rs deleted file mode 100644 index 3e5964be66d2ab..00000000000000 --- a/cli/tools/vendor/analyze.rs +++ /dev/null @@ -1,113 +0,0 @@ -// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. - -use deno_ast::swc::ast::ExportDefaultDecl; -use deno_ast::swc::ast::ExportSpecifier; -use deno_ast::swc::ast::ModuleExportName; -use deno_ast::swc::ast::NamedExport; -use deno_ast::swc::ast::Program; -use deno_ast::swc::visit::noop_visit_type; -use deno_ast::swc::visit::Visit; -use deno_ast::swc::visit::VisitWith; -use deno_ast::ParsedSource; - -/// Gets if the parsed source has a default export. -pub fn has_default_export(source: &ParsedSource) -> bool { - let mut visitor = DefaultExportFinder { - has_default_export: false, - }; - let program = source.program(); - let program: &Program = &program; - program.visit_with(&mut visitor); - visitor.has_default_export -} - -struct DefaultExportFinder { - has_default_export: bool, -} - -impl Visit for DefaultExportFinder { - noop_visit_type!(); - - fn visit_export_default_decl(&mut self, _: &ExportDefaultDecl) { - self.has_default_export = true; - } - - fn visit_named_export(&mut self, named_export: &NamedExport) { - if named_export - .specifiers - .iter() - .any(export_specifier_has_default) - { - self.has_default_export = true; - } - } -} - -fn export_specifier_has_default(s: &ExportSpecifier) -> bool { - match s { - ExportSpecifier::Default(_) => true, - ExportSpecifier::Namespace(_) => false, - ExportSpecifier::Named(named) => { - let export_name = named.exported.as_ref().unwrap_or(&named.orig); - - match export_name { - ModuleExportName::Str(_) => false, - ModuleExportName::Ident(ident) => &*ident.sym == "default", - } - } - } -} - -#[cfg(test)] -mod test { - use deno_ast::MediaType; - use deno_ast::ModuleSpecifier; - use deno_ast::ParseParams; - use deno_ast::ParsedSource; - - use super::has_default_export; - - #[test] - fn has_default_when_export_default_decl() { - let parsed_source = parse_module("export default class Class {}"); - assert!(has_default_export(&parsed_source)); - } - - #[test] - fn has_default_when_named_export() { - let parsed_source = parse_module("export {default} from './test.ts';"); - assert!(has_default_export(&parsed_source)); - } - - #[test] - fn has_default_when_named_export_alias() { - let parsed_source = - parse_module("export {test as default} from './test.ts';"); - assert!(has_default_export(&parsed_source)); - } - - #[test] - fn not_has_default_when_named_export_not_exported() { - let parsed_source = - parse_module("export {default as test} from './test.ts';"); - assert!(!has_default_export(&parsed_source)); - } - - #[test] - fn not_has_default_when_not() { - let parsed_source = parse_module("export {test} from './test.ts'; export class Test{} export * from './test';"); - assert!(!has_default_export(&parsed_source)); - } - - fn parse_module(text: &str) -> ParsedSource { - deno_ast::parse_module(ParseParams { - specifier: ModuleSpecifier::parse("file:///mod.ts").unwrap(), - capture_tokens: false, - maybe_syntax: None, - media_type: MediaType::TypeScript, - scope_analysis: false, - text: text.into(), - }) - .unwrap() - } -} diff --git a/cli/tools/vendor/build.rs b/cli/tools/vendor/build.rs deleted file mode 100644 index a4424e3f32f506..00000000000000 --- a/cli/tools/vendor/build.rs +++ /dev/null @@ -1,1330 +0,0 @@ -// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. - -use std::fmt::Write as _; -use std::path::Path; -use std::sync::Arc; - -use deno_ast::ModuleSpecifier; -use deno_core::anyhow::bail; -use deno_core::anyhow::Context; -use deno_core::error::AnyError; -use deno_core::futures::future::LocalBoxFuture; -use deno_graph::source::ResolutionMode; -use deno_graph::JsModule; -use deno_graph::Module; -use deno_graph::ModuleGraph; -use deno_runtime::deno_fs; -use import_map::ImportMap; -use import_map::SpecifierMap; - -use crate::args::JsxImportSourceConfig; -use crate::cache::ParsedSourceCache; -use crate::graph_util; -use crate::tools::vendor::import_map::BuildImportMapInput; - -use super::analyze::has_default_export; -use super::import_map::build_import_map; -use super::mappings::Mappings; -use super::mappings::ProxiedModule; -use super::specifiers::is_remote_specifier; - -/// Allows substituting the environment for testing purposes. -pub trait VendorEnvironment { - fn create_dir_all(&self, dir_path: &Path) -> Result<(), AnyError>; - fn write_file(&self, file_path: &Path, bytes: &[u8]) -> Result<(), AnyError>; -} - -pub struct RealVendorEnvironment; - -impl VendorEnvironment for RealVendorEnvironment { - fn create_dir_all(&self, dir_path: &Path) -> Result<(), AnyError> { - Ok(std::fs::create_dir_all(dir_path)?) - } - - fn write_file(&self, file_path: &Path, bytes: &[u8]) -> Result<(), AnyError> { - std::fs::write(file_path, bytes) - .with_context(|| format!("Failed writing {}", file_path.display())) - } -} - -type BuildGraphFuture = LocalBoxFuture<'static, Result>; - -pub struct BuildInput< - 'a, - TBuildGraphFn: FnOnce(Vec) -> BuildGraphFuture, - TEnvironment: VendorEnvironment, -> { - pub entry_points: Vec, - pub build_graph: TBuildGraphFn, - pub parsed_source_cache: &'a ParsedSourceCache, - pub output_dir: &'a Path, - pub maybe_original_import_map: Option<&'a ImportMap>, - pub maybe_jsx_import_source: Option<&'a JsxImportSourceConfig>, - pub resolver: &'a dyn deno_graph::source::Resolver, - pub environment: &'a TEnvironment, -} - -pub struct BuildOutput { - pub vendored_count: usize, - pub graph: ModuleGraph, -} - -/// Vendors remote modules and returns how many were vendored. -pub async fn build< - TBuildGraphFn: FnOnce(Vec) -> BuildGraphFuture, - TEnvironment: VendorEnvironment, ->( - input: BuildInput<'_, TBuildGraphFn, TEnvironment>, -) -> Result { - let BuildInput { - mut entry_points, - build_graph, - parsed_source_cache, - output_dir, - maybe_original_import_map, - maybe_jsx_import_source, - resolver, - environment, - } = input; - assert!(output_dir.is_absolute()); - let output_dir_specifier = - ModuleSpecifier::from_directory_path(output_dir).unwrap(); - - if let Some(original_im) = &maybe_original_import_map { - validate_original_import_map(original_im, &output_dir_specifier)?; - } - - // add the jsx import source to the entry points to ensure it is always vendored - if let Some(jsx_import_source) = maybe_jsx_import_source { - if let Some(specifier_text) = jsx_import_source.maybe_specifier_text() { - if let Ok(specifier) = resolver.resolve( - &specifier_text, - &deno_graph::Range { - specifier: jsx_import_source.base_url.clone(), - start: deno_graph::Position::zeroed(), - end: deno_graph::Position::zeroed(), - }, - ResolutionMode::Execution, - ) { - entry_points.push(specifier); - } - } - } - - let graph = build_graph(entry_points).await?; - - // surface any errors - let real_fs = Arc::new(deno_fs::RealFs) as Arc; - graph_util::graph_valid( - &graph, - &real_fs, - &graph.roots.iter().cloned().collect::>(), - graph_util::GraphValidOptions { - is_vendoring: true, - check_js: true, - follow_type_only: true, - exit_lockfile_errors: true, - }, - )?; - - // figure out how to map remote modules to local - let all_modules = graph.modules().collect::>(); - let remote_modules = all_modules - .iter() - .filter(|m| is_remote_specifier(m.specifier())) - .copied() - .collect::>(); - let mappings = - Mappings::from_remote_modules(&graph, &remote_modules, output_dir)?; - - // write out all the files - for module in &remote_modules { - let source = match module { - Module::Js(module) => &module.source, - Module::Json(module) => &module.source, - Module::Node(_) | Module::Npm(_) | Module::External(_) => continue, - }; - let specifier = module.specifier(); - let local_path = mappings - .proxied_path(specifier) - .unwrap_or_else(|| mappings.local_path(specifier)); - - environment.create_dir_all(local_path.parent().unwrap())?; - environment.write_file(&local_path, source.as_bytes())?; - } - - // write out the proxies - for (specifier, proxied_module) in mappings.proxied_modules() { - let proxy_path = mappings.local_path(specifier); - let module = graph.get(specifier).unwrap().js().unwrap(); - let text = - build_proxy_module_source(module, proxied_module, parsed_source_cache)?; - - environment.write_file(&proxy_path, text.as_bytes())?; - } - - // create the import map if necessary - if !remote_modules.is_empty() { - let import_map_path = output_dir.join("import_map.json"); - let import_map_text = build_import_map(BuildImportMapInput { - base_dir: &output_dir_specifier, - graph: &graph, - modules: &all_modules, - mappings: &mappings, - maybe_original_import_map, - maybe_jsx_import_source, - resolver, - parsed_source_cache, - })?; - environment.write_file(&import_map_path, import_map_text.as_bytes())?; - } - - Ok(BuildOutput { - vendored_count: remote_modules.len(), - graph, - }) -} - -fn validate_original_import_map( - import_map: &ImportMap, - output_dir: &ModuleSpecifier, -) -> Result<(), AnyError> { - fn validate_imports( - imports: &SpecifierMap, - output_dir: &ModuleSpecifier, - ) -> Result<(), AnyError> { - for entry in imports.entries() { - if let Some(value) = entry.value { - if value.as_str().starts_with(output_dir.as_str()) { - bail!( - "Providing an existing import map with entries for the output directory is not supported (\"{}\": \"{}\").", - entry.raw_key, - entry.raw_value.unwrap_or(""), - ); - } - } - } - Ok(()) - } - - validate_imports(import_map.imports(), output_dir)?; - - for scope in import_map.scopes() { - if scope.key.starts_with(output_dir.as_str()) { - bail!( - "Providing an existing import map with a scope for the output directory is not supported (\"{}\").", - scope.raw_key, - ); - } - validate_imports(scope.imports, output_dir)?; - } - - Ok(()) -} - -fn build_proxy_module_source( - module: &JsModule, - proxied_module: &ProxiedModule, - parsed_source_cache: &ParsedSourceCache, -) -> Result { - let mut text = String::new(); - writeln!( - text, - "// @deno-types=\"{}\"", - proxied_module.declaration_specifier - ) - .unwrap(); - - let relative_specifier = format!( - "./{}", - proxied_module - .output_path - .file_name() - .unwrap() - .to_string_lossy() - ); - - // for simplicity, always include the `export *` statement as it won't error - // even when the module does not contain a named export - writeln!(text, "export * from \"{relative_specifier}\";").unwrap(); - - // add a default export if one exists in the module - let parsed_source = - parsed_source_cache.get_parsed_source_from_js_module(module)?; - if has_default_export(&parsed_source) { - writeln!(text, "export {{ default }} from \"{relative_specifier}\";") - .unwrap(); - } - - Ok(text) -} - -#[cfg(test)] -mod test { - use crate::args::JsxImportSourceConfig; - use crate::tools::vendor::test::VendorTestBuilder; - use deno_core::serde_json::json; - use pretty_assertions::assert_eq; - - #[tokio::test] - async fn no_remote_modules() { - let mut builder = VendorTestBuilder::with_default_setup(); - let output = builder - .with_loader(|loader| { - loader.add("/mod.ts", ""); - }) - .build() - .await - .unwrap(); - - assert_eq!(output.import_map, None,); - assert_eq!(output.files, vec![],); - } - - #[tokio::test] - async fn local_specifiers_to_remote() { - let mut builder = VendorTestBuilder::with_default_setup(); - let output = builder - .with_loader(|loader| { - loader - .add( - "/mod.ts", - concat!( - r#"import "https://localhost/mod.ts";"#, - r#"import "https://localhost/other.ts?test";"#, - r#"import "https://localhost/redirect.ts";"#, - ), - ) - .add("https://localhost/mod.ts", "export class Mod {}") - .add("https://localhost/other.ts?test", "export class Other {}") - .add_redirect( - "https://localhost/redirect.ts", - "https://localhost/mod.ts", - ); - }) - .build() - .await - .unwrap(); - - assert_eq!( - output.import_map, - Some(json!({ - "imports": { - "https://localhost/other.ts?test": "./localhost/other.ts", - "https://localhost/redirect.ts": "./localhost/mod.ts", - "https://localhost/": "./localhost/", - } - })) - ); - assert_eq!( - output.files, - to_file_vec(&[ - ("/vendor/localhost/mod.ts", "export class Mod {}"), - ("/vendor/localhost/other.ts", "export class Other {}"), - ]), - ); - } - - #[tokio::test] - async fn remote_specifiers() { - let mut builder = VendorTestBuilder::with_default_setup(); - let output = builder - .with_loader(|loader| { - loader - .add( - "/mod.ts", - concat!( - r#"import "https://localhost/mod.ts";"#, - r#"import "https://other/mod.ts";"#, - ), - ) - .add( - "https://localhost/mod.ts", - concat!( - "export * from './other.ts';", - "export * from './redirect.ts';", - "export * from '/absolute.ts';", - ), - ) - .add("https://localhost/other.ts", "export class Other {}") - .add_redirect( - "https://localhost/redirect.ts", - "https://localhost/other.ts", - ) - .add("https://localhost/absolute.ts", "export class Absolute {}") - .add("https://other/mod.ts", "export * from './sub/mod.ts';") - .add( - "https://other/sub/mod.ts", - concat!( - "export * from '../sub2/mod.ts';", - "export * from '../sub2/other?asdf';", - // reference a path on a different origin - "export * from 'https://localhost/other.ts';", - "export * from 'https://localhost/redirect.ts';", - ), - ) - .add("https://other/sub2/mod.ts", "export class Mod {}") - .add_with_headers( - "https://other/sub2/other?asdf", - "export class Other {}", - &[("content-type", "application/javascript")], - ); - }) - .build() - .await - .unwrap(); - - assert_eq!( - output.import_map, - Some(json!({ - "imports": { - "https://localhost/": "./localhost/", - "https://localhost/redirect.ts": "./localhost/other.ts", - "https://other/": "./other/", - }, - "scopes": { - "./localhost/": { - "./localhost/redirect.ts": "./localhost/other.ts", - "/absolute.ts": "./localhost/absolute.ts", - }, - "./other/": { - "./other/sub2/other?asdf": "./other/sub2/other.js" - } - } - })) - ); - assert_eq!( - output.files, - to_file_vec(&[ - ("/vendor/localhost/absolute.ts", "export class Absolute {}"), - ( - "/vendor/localhost/mod.ts", - concat!( - "export * from './other.ts';", - "export * from './redirect.ts';", - "export * from '/absolute.ts';", - ) - ), - ("/vendor/localhost/other.ts", "export class Other {}"), - ("/vendor/other/mod.ts", "export * from './sub/mod.ts';"), - ( - "/vendor/other/sub/mod.ts", - concat!( - "export * from '../sub2/mod.ts';", - "export * from '../sub2/other?asdf';", - "export * from 'https://localhost/other.ts';", - "export * from 'https://localhost/redirect.ts';", - ) - ), - ("/vendor/other/sub2/mod.ts", "export class Mod {}"), - ("/vendor/other/sub2/other.js", "export class Other {}"), - ]), - ); - } - - #[tokio::test] - async fn remote_redirect_entrypoint() { - let mut builder = VendorTestBuilder::with_default_setup(); - let output = builder - .with_loader(|loader| { - loader - .add( - "/mod.ts", - concat!( - "import * as test from 'https://x.nest.land/Yenv@1.0.0/mod.ts';\n", - "console.log(test)", - ), - ) - .add_redirect("https://x.nest.land/Yenv@1.0.0/mod.ts", "https://arweave.net/VFtWNW3QZ-7__v7c7kck22eFI24OuK1DFzyQHKoZ9AE/mod.ts") - .add( - "https://arweave.net/VFtWNW3QZ-7__v7c7kck22eFI24OuK1DFzyQHKoZ9AE/mod.ts", - "export * from './src/mod.ts'", - ) - .add( - "https://arweave.net/VFtWNW3QZ-7__v7c7kck22eFI24OuK1DFzyQHKoZ9AE/src/mod.ts", - "export class Test {}", - ); - }) - .build() - .await - .unwrap(); - - assert_eq!( - output.import_map, - Some(json!({ - "imports": { - "https://x.nest.land/Yenv@1.0.0/mod.ts": "./arweave.net/VFtWNW3QZ-7__v7c7kck22eFI24OuK1DFzyQHKoZ9AE/mod.ts", - "https://arweave.net/": "./arweave.net/" - }, - })) - ); - assert_eq!( - output.files, - to_file_vec(&[ - ("/vendor/arweave.net/VFtWNW3QZ-7__v7c7kck22eFI24OuK1DFzyQHKoZ9AE/mod.ts", "export * from './src/mod.ts'"), - ( - "/vendor/arweave.net/VFtWNW3QZ-7__v7c7kck22eFI24OuK1DFzyQHKoZ9AE/src/mod.ts", - "export class Test {}", - ), - ]), - ); - } - - #[tokio::test] - async fn same_target_filename_specifiers() { - let mut builder = VendorTestBuilder::with_default_setup(); - let output = builder - .with_loader(|loader| { - loader - .add( - "/mod.ts", - concat!( - r#"import "https://localhost/MOD.TS";"#, - r#"import "https://localhost/mod.TS";"#, - r#"import "https://localhost/mod.ts";"#, - r#"import "https://localhost/mod.ts?test";"#, - r#"import "https://localhost/CAPS.TS";"#, - ), - ) - .add("https://localhost/MOD.TS", "export class Mod {}") - .add("https://localhost/mod.TS", "export class Mod2 {}") - .add("https://localhost/mod.ts", "export class Mod3 {}") - .add("https://localhost/mod.ts?test", "export class Mod4 {}") - .add("https://localhost/CAPS.TS", "export class Caps {}"); - }) - .build() - .await - .unwrap(); - - assert_eq!( - output.import_map, - Some(json!({ - "imports": { - "https://localhost/mod.TS": "./localhost/mod_2.TS", - "https://localhost/mod.ts": "./localhost/mod_3.ts", - "https://localhost/mod.ts?test": "./localhost/mod_4.ts", - "https://localhost/": "./localhost/", - } - })) - ); - assert_eq!( - output.files, - to_file_vec(&[ - ("/vendor/localhost/CAPS.TS", "export class Caps {}"), - ("/vendor/localhost/MOD.TS", "export class Mod {}"), - ("/vendor/localhost/mod_2.TS", "export class Mod2 {}"), - ("/vendor/localhost/mod_3.ts", "export class Mod3 {}"), - ("/vendor/localhost/mod_4.ts", "export class Mod4 {}"), - ]), - ); - } - - #[tokio::test] - async fn multiple_entrypoints() { - let mut builder = VendorTestBuilder::with_default_setup(); - let output = builder - .add_entry_point("/test.deps.ts") - .with_loader(|loader| { - loader - .add("/mod.ts", r#"import "https://localhost/mod.ts";"#) - .add( - "/test.deps.ts", - r#"export * from "https://localhost/test.ts";"#, - ) - .add("https://localhost/mod.ts", "export class Mod {}") - .add("https://localhost/test.ts", "export class Test {}"); - }) - .build() - .await - .unwrap(); - - assert_eq!( - output.import_map, - Some(json!({ - "imports": { - "https://localhost/": "./localhost/", - } - })) - ); - assert_eq!( - output.files, - to_file_vec(&[ - ("/vendor/localhost/mod.ts", "export class Mod {}"), - ("/vendor/localhost/test.ts", "export class Test {}"), - ]), - ); - } - - #[tokio::test] - async fn json_module() { - let mut builder = VendorTestBuilder::with_default_setup(); - let output = builder - .with_loader(|loader| { - loader - .add( - "/mod.ts", - r#"import data from "https://localhost/data.json" assert { type: "json" };"#, - ) - .add("https://localhost/data.json", "{ \"a\": \"b\" }"); - }) - .build() - .await - .unwrap(); - - assert_eq!( - output.import_map, - Some(json!({ - "imports": { - "https://localhost/": "./localhost/" - } - })) - ); - assert_eq!( - output.files, - to_file_vec(&[("/vendor/localhost/data.json", "{ \"a\": \"b\" }"),]), - ); - } - - #[tokio::test] - async fn data_urls() { - let mut builder = VendorTestBuilder::with_default_setup(); - - let mod_file_text = r#"import * as b from "data:application/typescript,export%20*%20from%20%22https://localhost/mod.ts%22;";"#; - - let output = builder - .with_loader(|loader| { - loader - .add("/mod.ts", mod_file_text) - .add("https://localhost/mod.ts", "export class Example {}"); - }) - .build() - .await - .unwrap(); - - assert_eq!( - output.import_map, - Some(json!({ - "imports": { - "https://localhost/": "./localhost/" - } - })) - ); - assert_eq!( - output.files, - to_file_vec(&[("/vendor/localhost/mod.ts", "export class Example {}"),]), - ); - } - - #[tokio::test] - async fn x_typescript_types_no_default() { - let mut builder = VendorTestBuilder::with_default_setup(); - let output = builder - .with_loader(|loader| { - loader - .add("/mod.ts", r#"import "https://localhost/mod.js";"#) - .add_with_headers( - "https://localhost/mod.js", - "export class Mod {}", - &[("x-typescript-types", "https://localhost/mod.d.ts")], - ) - .add("https://localhost/mod.d.ts", "export class Mod {}"); - }) - .build() - .await - .unwrap(); - - assert_eq!( - output.import_map, - Some(json!({ - "imports": { - "https://localhost/": "./localhost/" - } - })) - ); - assert_eq!( - output.files, - to_file_vec(&[ - ("/vendor/localhost/mod.d.ts", "export class Mod {}"), - ( - "/vendor/localhost/mod.js", - concat!( - "// @deno-types=\"https://localhost/mod.d.ts\"\n", - "export * from \"./mod.proxied.js\";\n" - ) - ), - ("/vendor/localhost/mod.proxied.js", "export class Mod {}"), - ]), - ); - } - - #[tokio::test] - async fn x_typescript_types_default_export() { - let mut builder = VendorTestBuilder::with_default_setup(); - let output = builder - .with_loader(|loader| { - loader - .add("/mod.ts", r#"import "https://localhost/mod.js";"#) - .add_with_headers( - "https://localhost/mod.js", - "export default class Mod {}", - &[("x-typescript-types", "https://localhost/mod.d.ts")], - ) - .add("https://localhost/mod.d.ts", "export default class Mod {}"); - }) - .build() - .await - .unwrap(); - - assert_eq!( - output.import_map, - Some(json!({ - "imports": { - "https://localhost/": "./localhost/" - } - })) - ); - assert_eq!( - output.files, - to_file_vec(&[ - ("/vendor/localhost/mod.d.ts", "export default class Mod {}"), - ( - "/vendor/localhost/mod.js", - concat!( - "// @deno-types=\"https://localhost/mod.d.ts\"\n", - "export * from \"./mod.proxied.js\";\n", - "export { default } from \"./mod.proxied.js\";\n", - ) - ), - ( - "/vendor/localhost/mod.proxied.js", - "export default class Mod {}" - ), - ]), - ); - } - - #[tokio::test] - async fn subdir() { - let mut builder = VendorTestBuilder::with_default_setup(); - let output = builder - .with_loader(|loader| { - loader - .add( - "/mod.ts", - r#"import "http://localhost:4545/sub/logger/mod.ts?testing";"#, - ) - .add( - "http://localhost:4545/sub/logger/mod.ts?testing", - "export * from './logger.ts?test';", - ) - .add( - "http://localhost:4545/sub/logger/logger.ts?test", - "export class Logger {}", - ); - }) - .build() - .await - .unwrap(); - - assert_eq!( - output.import_map, - Some(json!({ - "imports": { - "http://localhost:4545/sub/logger/mod.ts?testing": "./localhost_4545/sub/logger/mod.ts", - "http://localhost:4545/": "./localhost_4545/", - }, - "scopes": { - "./localhost_4545/": { - "./localhost_4545/sub/logger/logger.ts?test": "./localhost_4545/sub/logger/logger.ts" - } - } - })) - ); - assert_eq!( - output.files, - to_file_vec(&[ - ( - "/vendor/localhost_4545/sub/logger/logger.ts", - "export class Logger {}", - ), - ( - "/vendor/localhost_4545/sub/logger/mod.ts", - "export * from './logger.ts?test';" - ), - ]), - ); - } - - #[tokio::test] - async fn same_origin_absolute_with_redirect() { - let mut builder = VendorTestBuilder::with_default_setup(); - let output = builder - .with_loader(|loader| { - loader - .add( - "/mod.ts", - r#"import "https://localhost/subdir/sub/mod.ts";"#, - ) - .add( - "https://localhost/subdir/sub/mod.ts", - "import 'https://localhost/std/hash/mod.ts'", - ) - .add_redirect( - "https://localhost/std/hash/mod.ts", - "https://localhost/std@0.1.0/hash/mod.ts", - ) - .add( - "https://localhost/std@0.1.0/hash/mod.ts", - "export class Test {}", - ); - }) - .build() - .await - .unwrap(); - - assert_eq!( - output.import_map, - Some(json!({ - "imports": { - "https://localhost/std/hash/mod.ts": "./localhost/std@0.1.0/hash/mod.ts", - "https://localhost/": "./localhost/", - }, - })) - ); - assert_eq!( - output.files, - to_file_vec(&[ - ( - "/vendor/localhost/std@0.1.0/hash/mod.ts", - "export class Test {}" - ), - ( - "/vendor/localhost/subdir/sub/mod.ts", - "import 'https://localhost/std/hash/mod.ts'" - ), - ]), - ); - } - - #[tokio::test] - async fn remote_relative_specifier_with_scheme_like_folder_name() { - let mut builder = VendorTestBuilder::with_default_setup(); - let output = builder - .with_loader(|loader| { - loader - .add("/mod.ts", "import 'https://localhost/mod.ts';") - .add( - "https://localhost/mod.ts", - "import './npm:test@1.0.0/test/test!cjs?test';import './npm:test@1.0.0/mod.ts';", - ) - .add( - "https://localhost/npm:test@1.0.0/mod.ts", - "console.log(4);", - ) - .add_with_headers( - "https://localhost/npm:test@1.0.0/test/test!cjs?test", - "console.log(5);", - &[("content-type", "application/javascript")], - ); - }) - .build() - .await - .unwrap(); - - assert_eq!( - output.import_map, - Some(json!({ - "imports": { - "https://localhost/": "./localhost/" - }, - "scopes": { - "./localhost/": { - "./localhost/npm:test@1.0.0/mod.ts": "./localhost/npm_test@1.0.0/mod.ts", - "./localhost/npm:test@1.0.0/test/test!cjs?test": "./localhost/npm_test@1.0.0/test/test!cjs.js", - "./localhost/npm_test@1.0.0/test/test!cjs?test": "./localhost/npm_test@1.0.0/test/test!cjs.js" - } - } - })) - ); - assert_eq!( - output.files, - to_file_vec(&[ - ( - "/vendor/localhost/mod.ts", - "import './npm:test@1.0.0/test/test!cjs?test';import './npm:test@1.0.0/mod.ts';" - ), - ("/vendor/localhost/npm_test@1.0.0/mod.ts", "console.log(4);"), - ( - "/vendor/localhost/npm_test@1.0.0/test/test!cjs.js", - "console.log(5);" - ), - ]), - ); - } - - #[tokio::test] - async fn existing_import_map_basic() { - let mut builder = VendorTestBuilder::with_default_setup(); - let mut original_import_map = builder.new_import_map("/import_map2.json"); - original_import_map - .imports_mut() - .append( - "https://localhost/mod.ts".to_string(), - "./local_vendor/mod.ts".to_string(), - ) - .unwrap(); - let local_vendor_scope = original_import_map - .get_or_append_scope_mut("./local_vendor/") - .unwrap(); - local_vendor_scope - .append( - "https://localhost/logger.ts".to_string(), - "./local_vendor/logger.ts".to_string(), - ) - .unwrap(); - local_vendor_scope - .append( - "/console_logger.ts".to_string(), - "./local_vendor/console_logger.ts".to_string(), - ) - .unwrap(); - - let output = builder - .with_loader(|loader| { - loader.add("/mod.ts", "import 'https://localhost/mod.ts'; import 'https://localhost/other.ts';"); - loader.add("/local_vendor/mod.ts", "import 'https://localhost/logger.ts'; import '/console_logger.ts'; console.log(5);"); - loader.add("/local_vendor/logger.ts", "export class Logger {}"); - loader.add("/local_vendor/console_logger.ts", "export class ConsoleLogger {}"); - loader.add("https://localhost/mod.ts", "console.log(6);"); - loader.add("https://localhost/other.ts", "import './mod.ts';"); - }) - .set_original_import_map(original_import_map) - .build() - .await - .unwrap(); - - assert_eq!( - output.import_map, - Some(json!({ - "imports": { - "https://localhost/mod.ts": "../local_vendor/mod.ts", - "https://localhost/": "./localhost/" - }, - "scopes": { - "../local_vendor/": { - "https://localhost/logger.ts": "../local_vendor/logger.ts", - "/console_logger.ts": "../local_vendor/console_logger.ts", - }, - "./localhost/": { - "./localhost/mod.ts": "../local_vendor/mod.ts", - }, - } - })) - ); - assert_eq!( - output.files, - to_file_vec(&[("/vendor/localhost/other.ts", "import './mod.ts';")]), - ); - } - - #[tokio::test] - async fn existing_import_map_remote_dep_bare_specifier() { - let mut builder = VendorTestBuilder::with_default_setup(); - let mut original_import_map = builder.new_import_map("/import_map2.json"); - original_import_map - .imports_mut() - .append( - "twind".to_string(), - "https://localhost/twind.ts".to_string(), - ) - .unwrap(); - - let output = builder - .with_loader(|loader| { - loader.add("/mod.ts", "import 'https://remote/mod.ts';"); - loader.add("https://remote/mod.ts", "import 'twind';"); - loader.add("https://localhost/twind.ts", "export class Test {}"); - }) - .set_original_import_map(original_import_map) - .build() - .await - .unwrap(); - - assert_eq!( - output.import_map, - Some(json!({ - "imports": { - "https://localhost/": "./localhost/", - "https://remote/": "./remote/" - }, - "scopes": { - "./remote/": { - "twind": "./localhost/twind.ts" - }, - } - })) - ); - assert_eq!( - output.files, - to_file_vec(&[ - ("/vendor/localhost/twind.ts", "export class Test {}"), - ("/vendor/remote/mod.ts", "import 'twind';"), - ]), - ); - } - - #[tokio::test] - async fn existing_import_map_mapped_bare_specifier() { - let mut builder = VendorTestBuilder::with_default_setup(); - let mut original_import_map = builder.new_import_map("/import_map.json"); - let imports = original_import_map.imports_mut(); - imports - .append("$fresh".to_string(), "https://localhost/fresh".to_string()) - .unwrap(); - imports - .append("std/".to_string(), "https://deno.land/std/".to_string()) - .unwrap(); - let output = builder - .with_loader(|loader| { - loader.add("/mod.ts", "import 'std/mod.ts'; import '$fresh';"); - loader.add("https://deno.land/std/mod.ts", "export function test() {}"); - loader.add_with_headers( - "https://localhost/fresh", - "export function fresh() {}", - &[("content-type", "application/typescript")], - ); - }) - .set_original_import_map(original_import_map) - .build() - .await - .unwrap(); - - assert_eq!( - output.import_map, - Some(json!({ - "imports": { - "https://deno.land/": "./deno.land/", - "https://localhost/": "./localhost/", - "$fresh": "./localhost/fresh.ts", - "std/mod.ts": "./deno.land/std/mod.ts", - }, - })) - ); - assert_eq!( - output.files, - to_file_vec(&[ - ("/vendor/deno.land/std/mod.ts", "export function test() {}"), - ("/vendor/localhost/fresh.ts", "export function fresh() {}") - ]), - ); - } - - #[tokio::test] - async fn existing_import_map_remote_absolute_specifier_local() { - let mut builder = VendorTestBuilder::with_default_setup(); - let mut original_import_map = builder.new_import_map("/import_map.json"); - original_import_map - .imports_mut() - .append( - "https://localhost/logger.ts?test".to_string(), - "./local/logger.ts".to_string(), - ) - .unwrap(); - - let output = builder - .with_loader(|loader| { - loader.add("/mod.ts", "import 'https://localhost/mod.ts'; import 'https://localhost/logger.ts?test';"); - loader.add("/local/logger.ts", "export class Logger {}"); - // absolute specifier in a remote module that will point at ./local/logger.ts - loader.add("https://localhost/mod.ts", "import '/logger.ts?test';"); - loader.add("https://localhost/logger.ts?test", "export class Logger {}"); - }) - .set_original_import_map(original_import_map) - .build() - .await - .unwrap(); - - assert_eq!( - output.import_map, - Some(json!({ - "imports": { - "https://localhost/logger.ts?test": "../local/logger.ts", - "https://localhost/": "./localhost/", - }, - "scopes": { - "./localhost/": { - "/logger.ts?test": "../local/logger.ts", - }, - } - })) - ); - assert_eq!( - output.files, - to_file_vec(&[("/vendor/localhost/mod.ts", "import '/logger.ts?test';")]), - ); - } - - #[tokio::test] - async fn existing_import_map_imports_output_dir() { - let mut builder = VendorTestBuilder::with_default_setup(); - let mut original_import_map = builder.new_import_map("/import_map.json"); - original_import_map - .imports_mut() - .append( - "std/mod.ts".to_string(), - "./vendor/deno.land/std/mod.ts".to_string(), - ) - .unwrap(); - let err = builder - .with_loader(|loader| { - loader.add("/mod.ts", "import 'std/mod.ts';"); - loader.add("/vendor/deno.land/std/mod.ts", "export function f() {}"); - loader.add("https://deno.land/std/mod.ts", "export function f() {}"); - }) - .set_original_import_map(original_import_map) - .build() - .await - .err() - .unwrap(); - - assert_eq!( - err.to_string(), - concat!( - "Providing an existing import map with entries for the output ", - "directory is not supported ", - "(\"std/mod.ts\": \"./vendor/deno.land/std/mod.ts\").", - ) - ); - } - - #[tokio::test] - async fn existing_import_map_scopes_entry_output_dir() { - let mut builder = VendorTestBuilder::with_default_setup(); - let mut original_import_map = builder.new_import_map("/import_map.json"); - let scopes = original_import_map - .get_or_append_scope_mut("./other/") - .unwrap(); - scopes - .append("/mod.ts".to_string(), "./vendor/mod.ts".to_string()) - .unwrap(); - let err = builder - .with_loader(|loader| { - loader.add("/mod.ts", "console.log(5);"); - }) - .set_original_import_map(original_import_map) - .build() - .await - .err() - .unwrap(); - - assert_eq!( - err.to_string(), - concat!( - "Providing an existing import map with entries for the output ", - "directory is not supported ", - "(\"/mod.ts\": \"./vendor/mod.ts\").", - ) - ); - } - - #[tokio::test] - async fn existing_import_map_scopes_key_output_dir() { - let mut builder = VendorTestBuilder::with_default_setup(); - let mut original_import_map = builder.new_import_map("/import_map.json"); - let scopes = original_import_map - .get_or_append_scope_mut("./vendor/") - .unwrap(); - scopes - .append("/mod.ts".to_string(), "./vendor/mod.ts".to_string()) - .unwrap(); - let err = builder - .with_loader(|loader| { - loader.add("/mod.ts", "console.log(5);"); - }) - .set_original_import_map(original_import_map) - .build() - .await - .err() - .unwrap(); - - assert_eq!( - err.to_string(), - concat!( - "Providing an existing import map with a scope for the output ", - "directory is not supported (\"./vendor/\").", - ) - ); - } - - #[tokio::test] - async fn existing_import_map_http_key() { - let mut builder = VendorTestBuilder::with_default_setup(); - let mut original_import_map = builder.new_import_map("/import_map.json"); - original_import_map - .imports_mut() - .append( - "http/".to_string(), - "https://deno.land/std/http/".to_string(), - ) - .unwrap(); - let output = builder - .with_loader(|loader| { - loader.add("/mod.ts", "import 'http/mod.ts';"); - loader.add("https://deno.land/std/http/mod.ts", "console.log(5);"); - }) - .set_original_import_map(original_import_map) - .build() - .await - .unwrap(); - assert_eq!( - output.import_map, - Some(json!({ - "imports": { - "http/mod.ts": "./deno.land/std/http/mod.ts", - "https://deno.land/": "./deno.land/", - } - })) - ); - assert_eq!( - output.files, - to_file_vec(&[("/vendor/deno.land/std/http/mod.ts", "console.log(5);")]), - ); - } - - #[tokio::test] - async fn existing_import_map_jsx_import_source_jsx_files() { - let mut builder = VendorTestBuilder::default(); - builder.add_entry_point("/mod.tsx"); - builder.set_jsx_import_source_config(JsxImportSourceConfig { - default_specifier: Some("preact".to_string()), - default_types_specifier: None, - module: "jsx-runtime".to_string(), - base_url: builder.resolve_to_url("/deno.json"), - }); - let mut original_import_map = builder.new_import_map("/import_map.json"); - let imports = original_import_map.imports_mut(); - imports - .append( - "preact/".to_string(), - "https://localhost/preact/".to_string(), - ) - .unwrap(); - let output = builder - .with_loader(|loader| { - loader.add("/mod.tsx", "const myComponent =
;"); - loader.add_with_headers( - "https://localhost/preact/jsx-runtime", - "export function stuff() {}", - &[("content-type", "application/typescript")], - ); - }) - .set_original_import_map(original_import_map) - .build() - .await - .unwrap(); - - assert_eq!( - output.import_map, - Some(json!({ - "imports": { - "https://localhost/": "./localhost/", - "preact/jsx-runtime": "./localhost/preact/jsx-runtime.ts", - }, - })) - ); - assert_eq!( - output.files, - to_file_vec(&[( - "/vendor/localhost/preact/jsx-runtime.ts", - "export function stuff() {}" - ),]), - ); - } - - #[tokio::test] - async fn existing_import_map_jsx_import_source_no_jsx_files() { - let mut builder = VendorTestBuilder::default(); - builder.add_entry_point("/mod.ts"); - builder.set_jsx_import_source_config(JsxImportSourceConfig { - default_specifier: Some("preact".to_string()), - default_types_specifier: None, - module: "jsx-runtime".to_string(), - base_url: builder.resolve_to_url("/deno.json"), - }); - let mut original_import_map = builder.new_import_map("/import_map.json"); - let imports = original_import_map.imports_mut(); - imports - .append( - "preact/".to_string(), - "https://localhost/preact/".to_string(), - ) - .unwrap(); - let output = builder - .with_loader(|loader| { - loader.add("/mod.ts", "import 'https://localhost/mod.ts';"); - loader.add("https://localhost/mod.ts", "console.log(1)"); - loader.add_with_headers( - "https://localhost/preact/jsx-runtime", - "export function stuff() {}", - &[("content-type", "application/typescript")], - ); - }) - .set_original_import_map(original_import_map) - .build() - .await - .unwrap(); - - assert_eq!( - output.import_map, - Some(json!({ - "imports": { - "https://localhost/": "./localhost/", - "preact/jsx-runtime": "./localhost/preact/jsx-runtime.ts" - }, - })) - ); - assert_eq!( - output.files, - to_file_vec(&[ - ("/vendor/localhost/mod.ts", "console.log(1)"), - ( - "/vendor/localhost/preact/jsx-runtime.ts", - "export function stuff() {}" - ), - ]), - ); - } - - #[tokio::test] - async fn vendor_file_fails_loading_dynamic_import() { - let mut builder = VendorTestBuilder::with_default_setup(); - let err = builder - .with_loader(|loader| { - loader.add("/mod.ts", "import 'https://localhost/mod.ts';"); - loader.add("https://localhost/mod.ts", "await import('./test.ts');"); - loader.add_failure( - "https://localhost/test.ts", - "500 Internal Server Error", - ); - }) - .build() - .await - .err() - .unwrap(); - - assert_eq!( - test_util::strip_ansi_codes(&err.to_string()), - concat!( - "500 Internal Server Error\n", - " at https://localhost/mod.ts:1:14" - ) - ); - } - - fn to_file_vec(items: &[(&str, &str)]) -> Vec<(String, String)> { - items - .iter() - .map(|(f, t)| (f.to_string(), t.to_string())) - .collect() - } -} diff --git a/cli/tools/vendor/import_map.rs b/cli/tools/vendor/import_map.rs deleted file mode 100644 index 644e84a7b39ddd..00000000000000 --- a/cli/tools/vendor/import_map.rs +++ /dev/null @@ -1,508 +0,0 @@ -// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. - -use deno_ast::LineAndColumnIndex; -use deno_ast::ModuleSpecifier; -use deno_ast::SourceTextInfo; -use deno_core::error::AnyError; -use deno_graph::source::ResolutionMode; -use deno_graph::Module; -use deno_graph::ModuleGraph; -use deno_graph::Position; -use deno_graph::Range; -use deno_graph::Resolution; -use import_map::ImportMap; -use import_map::SpecifierMap; -use indexmap::IndexMap; -use log::warn; - -use crate::args::JsxImportSourceConfig; -use crate::cache::ParsedSourceCache; - -use super::mappings::Mappings; -use super::specifiers::is_remote_specifier; -use super::specifiers::is_remote_specifier_text; - -struct ImportMapBuilder<'a> { - base_dir: &'a ModuleSpecifier, - mappings: &'a Mappings, - imports: ImportsBuilder<'a>, - scopes: IndexMap>, -} - -impl<'a> ImportMapBuilder<'a> { - pub fn new(base_dir: &'a ModuleSpecifier, mappings: &'a Mappings) -> Self { - ImportMapBuilder { - base_dir, - mappings, - imports: ImportsBuilder::new(base_dir, mappings), - scopes: Default::default(), - } - } - - pub fn base_dir(&self) -> &ModuleSpecifier { - self.base_dir - } - - pub fn scope( - &mut self, - base_specifier: &ModuleSpecifier, - ) -> &mut ImportsBuilder<'a> { - self - .scopes - .entry( - self - .mappings - .relative_specifier_text(self.base_dir, base_specifier), - ) - .or_insert_with(|| ImportsBuilder::new(self.base_dir, self.mappings)) - } - - pub fn into_import_map( - self, - maybe_original_import_map: Option<&ImportMap>, - ) -> ImportMap { - fn get_local_imports( - new_relative_path: &str, - original_imports: &SpecifierMap, - ) -> Vec<(String, String)> { - let mut result = Vec::new(); - for entry in original_imports.entries() { - if let Some(raw_value) = entry.raw_value { - if raw_value.starts_with("./") || raw_value.starts_with("../") { - let sub_index = raw_value.find('/').unwrap() + 1; - result.push(( - entry.raw_key.to_string(), - format!("{}{}", new_relative_path, &raw_value[sub_index..]), - )); - } - } - } - result - } - - fn add_local_imports<'a>( - new_relative_path: &str, - original_imports: &SpecifierMap, - get_new_imports: impl FnOnce() -> &'a mut SpecifierMap, - ) { - let local_imports = - get_local_imports(new_relative_path, original_imports); - if !local_imports.is_empty() { - let new_imports = get_new_imports(); - for (key, value) in local_imports { - if let Err(warning) = new_imports.append(key, value) { - warn!("{}", warning); - } - } - } - } - - let mut import_map = ImportMap::new(self.base_dir.clone()); - - if let Some(original_im) = maybe_original_import_map { - let original_base_dir = ModuleSpecifier::from_directory_path( - original_im - .base_url() - .to_file_path() - .unwrap() - .parent() - .unwrap(), - ) - .unwrap(); - let new_relative_path = self - .mappings - .relative_specifier_text(self.base_dir, &original_base_dir); - // add the imports - add_local_imports(&new_relative_path, original_im.imports(), || { - import_map.imports_mut() - }); - - for scope in original_im.scopes() { - if scope.raw_key.starts_with("./") || scope.raw_key.starts_with("../") { - let sub_index = scope.raw_key.find('/').unwrap() + 1; - let new_key = - format!("{}{}", new_relative_path, &scope.raw_key[sub_index..]); - add_local_imports(&new_relative_path, scope.imports, || { - import_map.get_or_append_scope_mut(&new_key).unwrap() - }); - } - } - } - - let imports = import_map.imports_mut(); - for (key, value) in self.imports.imports { - if !imports.contains(&key) { - imports.append(key, value).unwrap(); - } - } - - for (scope_key, scope_value) in self.scopes { - if !scope_value.imports.is_empty() { - let imports = import_map.get_or_append_scope_mut(&scope_key).unwrap(); - for (key, value) in scope_value.imports { - if !imports.contains(&key) { - imports.append(key, value).unwrap(); - } - } - } - } - - import_map - } -} - -struct ImportsBuilder<'a> { - base_dir: &'a ModuleSpecifier, - mappings: &'a Mappings, - imports: IndexMap, -} - -impl<'a> ImportsBuilder<'a> { - pub fn new(base_dir: &'a ModuleSpecifier, mappings: &'a Mappings) -> Self { - Self { - base_dir, - mappings, - imports: Default::default(), - } - } - - pub fn add(&mut self, key: String, specifier: &ModuleSpecifier) { - let value = self - .mappings - .relative_specifier_text(self.base_dir, specifier); - - // skip creating identity entries - if key != value { - self.imports.insert(key, value); - } - } -} - -pub struct BuildImportMapInput<'a> { - pub base_dir: &'a ModuleSpecifier, - pub modules: &'a [&'a Module], - pub graph: &'a ModuleGraph, - pub mappings: &'a Mappings, - pub maybe_original_import_map: Option<&'a ImportMap>, - pub maybe_jsx_import_source: Option<&'a JsxImportSourceConfig>, - pub resolver: &'a dyn deno_graph::source::Resolver, - pub parsed_source_cache: &'a ParsedSourceCache, -} - -pub fn build_import_map( - input: BuildImportMapInput<'_>, -) -> Result { - let BuildImportMapInput { - base_dir, - modules, - graph, - mappings, - maybe_original_import_map, - maybe_jsx_import_source, - resolver, - parsed_source_cache, - } = input; - let mut builder = ImportMapBuilder::new(base_dir, mappings); - visit_modules(graph, modules, mappings, &mut builder, parsed_source_cache)?; - - for base_specifier in mappings.base_specifiers() { - builder - .imports - .add(base_specifier.to_string(), base_specifier); - } - - // add the jsx import source to the destination import map, if mapped in the original import map - if let Some(jsx_import_source) = maybe_jsx_import_source { - if let Some(specifier_text) = jsx_import_source.maybe_specifier_text() { - if let Ok(resolved_url) = resolver.resolve( - &specifier_text, - &deno_graph::Range { - specifier: jsx_import_source.base_url.clone(), - start: deno_graph::Position::zeroed(), - end: deno_graph::Position::zeroed(), - }, - ResolutionMode::Execution, - ) { - builder.imports.add(specifier_text, &resolved_url); - } - } - } - - Ok(builder.into_import_map(maybe_original_import_map).to_json()) -} - -fn visit_modules( - graph: &ModuleGraph, - modules: &[&Module], - mappings: &Mappings, - import_map: &mut ImportMapBuilder, - parsed_source_cache: &ParsedSourceCache, -) -> Result<(), AnyError> { - for module in modules { - let module = match module { - Module::Js(module) => module, - // skip visiting Json modules as they are leaves - Module::Json(_) - | Module::Npm(_) - | Module::Node(_) - | Module::External(_) => continue, - }; - - let parsed_source = - parsed_source_cache.get_parsed_source_from_js_module(module)?; - let text_info = parsed_source.text_info_lazy().clone(); - - for dep in module.dependencies.values() { - visit_resolution( - &dep.maybe_code, - graph, - import_map, - &module.specifier, - mappings, - &text_info, - &module.source, - ); - visit_resolution( - &dep.maybe_type, - graph, - import_map, - &module.specifier, - mappings, - &text_info, - &module.source, - ); - } - - if let Some(types_dep) = &module.maybe_types_dependency { - visit_resolution( - &types_dep.dependency, - graph, - import_map, - &module.specifier, - mappings, - &text_info, - &module.source, - ); - } - } - - Ok(()) -} - -fn visit_resolution( - resolution: &Resolution, - graph: &ModuleGraph, - import_map: &mut ImportMapBuilder, - referrer: &ModuleSpecifier, - mappings: &Mappings, - text_info: &SourceTextInfo, - source_text: &str, -) { - if let Some(resolved) = resolution.ok() { - let text = text_from_range(text_info, source_text, &resolved.range); - // if the text is empty then it's probably an x-TypeScript-types - if !text.is_empty() { - handle_dep_specifier( - text, - &resolved.specifier, - graph, - import_map, - referrer, - mappings, - ); - } - } -} - -fn handle_dep_specifier( - text: &str, - unresolved_specifier: &ModuleSpecifier, - graph: &ModuleGraph, - import_map: &mut ImportMapBuilder, - referrer: &ModuleSpecifier, - mappings: &Mappings, -) { - let specifier = match graph.get(unresolved_specifier) { - Some(module) => module.specifier().clone(), - // Ignore when None. The graph was previous validated so this is a - // dynamic import that was missing and is ignored for vendoring - None => return, - }; - // check if it's referencing a remote module - if is_remote_specifier(&specifier) { - handle_remote_dep_specifier( - text, - unresolved_specifier, - &specifier, - import_map, - referrer, - mappings, - ) - } else if specifier.scheme() == "file" { - handle_local_dep_specifier( - text, - unresolved_specifier, - &specifier, - import_map, - referrer, - mappings, - ); - } -} - -fn handle_remote_dep_specifier( - text: &str, - unresolved_specifier: &ModuleSpecifier, - specifier: &ModuleSpecifier, - import_map: &mut ImportMapBuilder, - referrer: &ModuleSpecifier, - mappings: &Mappings, -) { - if is_remote_specifier_text(text) { - let base_specifier = mappings.base_specifier(specifier); - if text.starts_with(base_specifier.as_str()) { - let sub_path = &text[base_specifier.as_str().len()..]; - let relative_text = - mappings.relative_specifier_text(base_specifier, specifier); - let expected_sub_path = relative_text.trim_start_matches("./"); - if expected_sub_path != sub_path { - import_map.imports.add(text.to_string(), specifier); - } - } else { - // it's probably a redirect. Add it explicitly to the import map - import_map.imports.add(text.to_string(), specifier); - } - } else { - let expected_relative_specifier_text = - mappings.relative_specifier_text(referrer, specifier); - if expected_relative_specifier_text == text { - return; - } - - if !is_remote_specifier(referrer) { - // local module referencing a remote module using - // non-remote specifier text means it was something in - // the original import map, so add a mapping to it - import_map.imports.add(text.to_string(), specifier); - return; - } - - let base_referrer = mappings.base_specifier(referrer); - let base_dir = import_map.base_dir().clone(); - let imports = import_map.scope(base_referrer); - if text.starts_with("./") || text.starts_with("../") { - // resolve relative specifier key - let mut local_base_specifier = mappings.local_uri(base_referrer); - local_base_specifier = local_base_specifier - // path includes "/" so make it relative - .join(&format!(".{}", unresolved_specifier.path())) - .unwrap_or_else(|_| { - panic!( - "Error joining {} to {}", - unresolved_specifier.path(), - local_base_specifier - ) - }); - local_base_specifier.set_query(unresolved_specifier.query()); - - imports.add( - mappings.relative_specifier_text(&base_dir, &local_base_specifier), - specifier, - ); - - // add a mapping that uses the local directory name and the remote - // filename in order to support files importing this relatively - imports.add( - { - let local_path = mappings.local_path(specifier); - let mut value = - ModuleSpecifier::from_directory_path(local_path.parent().unwrap()) - .unwrap(); - value.set_query(specifier.query()); - value.set_path(&format!( - "{}{}", - value.path(), - specifier.path_segments().unwrap().last().unwrap(), - )); - mappings.relative_specifier_text(&base_dir, &value) - }, - specifier, - ); - } else { - // absolute (`/`) or bare specifier should be left as-is - imports.add(text.to_string(), specifier); - } - } -} - -fn handle_local_dep_specifier( - text: &str, - unresolved_specifier: &ModuleSpecifier, - specifier: &ModuleSpecifier, - import_map: &mut ImportMapBuilder, - referrer: &ModuleSpecifier, - mappings: &Mappings, -) { - if !is_remote_specifier(referrer) { - // do not handle local modules referencing local modules - return; - } - - // The remote module is referencing a local file. This could occur via an - // existing import map. In this case, we'll have to add an import map - // entry in order to map the path back to the local path once vendored. - let base_dir = import_map.base_dir().clone(); - let base_specifier = mappings.base_specifier(referrer); - let imports = import_map.scope(base_specifier); - - if text.starts_with("./") || text.starts_with("../") { - let referrer_local_uri = mappings.local_uri(referrer); - let mut specifier_local_uri = - referrer_local_uri.join(text).unwrap_or_else(|_| { - panic!( - "Error joining {} to {}", - unresolved_specifier.path(), - referrer_local_uri - ) - }); - specifier_local_uri.set_query(unresolved_specifier.query()); - - imports.add( - mappings.relative_specifier_text(&base_dir, &specifier_local_uri), - specifier, - ); - } else { - imports.add(text.to_string(), specifier); - } -} - -fn text_from_range<'a>( - text_info: &SourceTextInfo, - text: &'a str, - range: &Range, -) -> &'a str { - let result = &text[byte_range(text_info, range)]; - if result.starts_with('"') || result.starts_with('\'') { - // remove the quotes - &result[1..result.len() - 1] - } else { - result - } -} - -fn byte_range( - text_info: &SourceTextInfo, - range: &Range, -) -> std::ops::Range { - let start = byte_index(text_info, &range.start); - let end = byte_index(text_info, &range.end); - start..end -} - -fn byte_index(text_info: &SourceTextInfo, pos: &Position) -> usize { - // todo(https://github.com/denoland/deno_graph/issues/79): use byte indexes all the way down - text_info.loc_to_source_pos(LineAndColumnIndex { - line_index: pos.line, - column_index: pos.character, - }) - text_info.range().start -} diff --git a/cli/tools/vendor/mappings.rs b/cli/tools/vendor/mappings.rs deleted file mode 100644 index 6d2722b89ca4a4..00000000000000 --- a/cli/tools/vendor/mappings.rs +++ /dev/null @@ -1,255 +0,0 @@ -// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. - -use std::collections::HashMap; -use std::collections::HashSet; -use std::path::Path; -use std::path::PathBuf; - -use deno_ast::MediaType; -use deno_ast::ModuleSpecifier; -use deno_core::error::AnyError; -use deno_graph::Module; -use deno_graph::ModuleGraph; -use deno_graph::Position; - -use crate::util::path::path_with_stem_suffix; -use crate::util::path::relative_specifier; - -use super::specifiers::dir_name_for_root; -use super::specifiers::get_unique_path; -use super::specifiers::make_url_relative; -use super::specifiers::partition_by_root_specifiers; -use super::specifiers::sanitize_filepath; - -pub struct ProxiedModule { - pub output_path: PathBuf, - pub declaration_specifier: ModuleSpecifier, -} - -/// Constructs and holds the remote specifier to local path mappings. -pub struct Mappings { - mappings: HashMap, - base_specifiers: Vec, - proxies: HashMap, -} - -impl Mappings { - pub fn from_remote_modules( - graph: &ModuleGraph, - remote_modules: &[&Module], - output_dir: &Path, - ) -> Result { - let partitioned_specifiers = partition_by_root_specifiers( - remote_modules.iter().map(|m| m.specifier()), - ); - let mut mapped_paths = HashSet::new(); - let mut mappings = HashMap::new(); - let mut proxies = HashMap::new(); - let mut base_specifiers = Vec::new(); - - for (root, specifiers) in partitioned_specifiers.into_iter() { - let base_dir = get_unique_path( - output_dir.join(dir_name_for_root(&root)), - &mut mapped_paths, - ); - for specifier in specifiers { - let module = graph.get(&specifier).unwrap(); - let media_type = match module { - Module::Js(module) => module.media_type, - Module::Json(_) => MediaType::Json, - Module::Node(_) | Module::Npm(_) | Module::External(_) => continue, - }; - let sub_path = sanitize_filepath(&make_url_relative(&root, &{ - let mut specifier = specifier.clone(); - specifier.set_query(None); - specifier - })?); - let new_path = path_with_extension( - &base_dir.join(if cfg!(windows) { - sub_path.replace('/', "\\") - } else { - sub_path - }), - &media_type.as_ts_extension()[1..], - ); - mappings - .insert(specifier, get_unique_path(new_path, &mut mapped_paths)); - } - base_specifiers.push(root.clone()); - mappings.insert(root, base_dir); - } - - // resolve all the "proxy" paths to use for when an x-typescript-types header is specified - for module in remote_modules { - if let Some(module) = module.js() { - if let Some(resolved) = &module - .maybe_types_dependency - .as_ref() - .and_then(|d| d.dependency.ok()) - { - let range = &resolved.range; - // hack to tell if it's an x-typescript-types header - let is_ts_types_header = range.start == Position::zeroed() - && range.end == Position::zeroed(); - if is_ts_types_header { - let module_path = mappings.get(&module.specifier).unwrap(); - let proxied_path = get_unique_path( - path_with_stem_suffix(module_path, ".proxied"), - &mut mapped_paths, - ); - proxies.insert( - module.specifier.clone(), - ProxiedModule { - output_path: proxied_path, - declaration_specifier: resolved.specifier.clone(), - }, - ); - } - } - } - } - - Ok(Self { - mappings, - base_specifiers, - proxies, - }) - } - - pub fn local_uri(&self, specifier: &ModuleSpecifier) -> ModuleSpecifier { - if specifier.scheme() == "file" { - specifier.clone() - } else { - let local_path = self.local_path(specifier); - if specifier.path().ends_with('/') { - ModuleSpecifier::from_directory_path(&local_path) - } else { - ModuleSpecifier::from_file_path(&local_path) - } - .unwrap_or_else(|_| { - panic!("Could not convert {} to uri.", local_path.display()) - }) - } - } - - pub fn local_path(&self, specifier: &ModuleSpecifier) -> PathBuf { - if specifier.scheme() == "file" { - specifier.to_file_path().unwrap() - } else { - self - .mappings - .get(specifier) - .unwrap_or_else(|| panic!("Could not find local path for {specifier}")) - .to_path_buf() - } - } - - pub fn relative_specifier_text( - &self, - from: &ModuleSpecifier, - to: &ModuleSpecifier, - ) -> String { - let from = self.local_uri(from); - let to = self.local_uri(to); - relative_specifier(&from, &to).unwrap() - } - - pub fn base_specifiers(&self) -> &Vec { - &self.base_specifiers - } - - pub fn base_specifier( - &self, - child_specifier: &ModuleSpecifier, - ) -> &ModuleSpecifier { - self - .base_specifiers - .iter() - .find(|s| child_specifier.as_str().starts_with(s.as_str())) - .unwrap_or_else(|| { - panic!("Could not find base specifier for {child_specifier}") - }) - } - - pub fn proxied_path(&self, specifier: &ModuleSpecifier) -> Option { - self.proxies.get(specifier).map(|s| s.output_path.clone()) - } - - pub fn proxied_modules( - &self, - ) -> std::collections::hash_map::Iter<'_, ModuleSpecifier, ProxiedModule> { - self.proxies.iter() - } -} - -fn path_with_extension(path: &Path, new_ext: &str) -> PathBuf { - if let Some(file_stem) = path.file_stem().map(|f| f.to_string_lossy()) { - if let Some(old_ext) = path.extension().map(|f| f.to_string_lossy()) { - if file_stem.to_lowercase().ends_with(".d") { - if new_ext.to_lowercase() == format!("d.{}", old_ext.to_lowercase()) { - // maintain casing - return path.to_path_buf(); - } - return path.with_file_name(format!( - "{}.{}", - &file_stem[..file_stem.len() - ".d".len()], - new_ext - )); - } - if new_ext.to_lowercase() == old_ext.to_lowercase() { - // maintain casing - return path.to_path_buf(); - } - let media_type = MediaType::from_path(path); - if media_type == MediaType::Unknown { - return path.with_file_name(format!( - "{}.{}", - path.file_name().unwrap().to_string_lossy(), - new_ext - )); - } - } - } - path.with_extension(new_ext) -} - -#[cfg(test)] -mod test { - use pretty_assertions::assert_eq; - - use super::*; - - #[test] - fn test_path_with_extension() { - assert_eq!( - path_with_extension(&PathBuf::from("/test.D.TS"), "ts"), - PathBuf::from("/test.ts") - ); - assert_eq!( - path_with_extension(&PathBuf::from("/test.D.MTS"), "js"), - PathBuf::from("/test.js") - ); - assert_eq!( - path_with_extension(&PathBuf::from("/test.D.TS"), "d.ts"), - // maintains casing - PathBuf::from("/test.D.TS"), - ); - assert_eq!( - path_with_extension(&PathBuf::from("/test.TS"), "ts"), - // maintains casing - PathBuf::from("/test.TS"), - ); - assert_eq!( - path_with_extension(&PathBuf::from("/test.ts"), "js"), - PathBuf::from("/test.js") - ); - assert_eq!( - path_with_extension(&PathBuf::from("/test.js"), "js"), - PathBuf::from("/test.js") - ); - assert_eq!( - path_with_extension(&PathBuf::from("/chai@1.2.3"), "js"), - PathBuf::from("/chai@1.2.3.js") - ); - } -} diff --git a/cli/tools/vendor/mod.rs b/cli/tools/vendor/mod.rs deleted file mode 100644 index 3de08f1d0a2f83..00000000000000 --- a/cli/tools/vendor/mod.rs +++ /dev/null @@ -1,578 +0,0 @@ -// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. - -use std::path::Path; -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; -use deno_core::futures::FutureExt; -use deno_core::resolve_url_or_path; -use deno_graph::GraphKind; -use deno_runtime::colors; -use log::warn; - -use crate::args::CliOptions; -use crate::args::ConfigFile; -use crate::args::Flags; -use crate::args::FmtOptionsConfig; -use crate::args::VendorFlags; -use crate::factory::CliFactory; -use crate::tools::fmt::format_json; -use crate::util::fs::canonicalize_path; -use crate::util::fs::resolve_from_cwd; -use crate::util::path::relative_specifier; -use deno_runtime::fs_util::specifier_to_file_path; - -mod analyze; -mod build; -mod import_map; -mod mappings; -mod specifiers; -#[cfg(test)] -mod test; - -pub async fn vendor( - flags: Arc, - vendor_flags: VendorFlags, -) -> Result<(), AnyError> { - log::info!( - "{}", - colors::yellow("⚠️ Warning: `deno vendor` is deprecated and will be removed in Deno 2.0.\nAdd `\"vendor\": true` to your `deno.json` or use the `--vendor` flag instead."), - ); - let mut cli_options = CliOptions::from_flags(flags)?; - let raw_output_dir = match &vendor_flags.output_path { - Some(output_path) => PathBuf::from(output_path).to_owned(), - None => PathBuf::from("vendor/"), - }; - let output_dir = resolve_from_cwd(&raw_output_dir)?; - validate_output_dir(&output_dir, &vendor_flags)?; - validate_options(&mut cli_options, &output_dir)?; - let factory = CliFactory::from_cli_options(Arc::new(cli_options)); - let cli_options = factory.cli_options()?; - if cli_options.workspace().config_folders().len() > 1 { - bail!("deno vendor is not supported in a workspace. Set `\"vendor\": true` in the workspace deno.json file instead"); - } - let entry_points = - resolve_entry_points(&vendor_flags, cli_options.initial_cwd())?; - let jsx_import_source = cli_options - .workspace() - .to_maybe_jsx_import_source_config()?; - let module_graph_creator = factory.module_graph_creator().await?.clone(); - let workspace_resolver = factory.workspace_resolver().await?; - let root_folder = cli_options.workspace().root_folder_configs(); - let maybe_config_file = root_folder.deno_json.as_ref(); - let output = build::build(build::BuildInput { - entry_points, - build_graph: move |entry_points| { - async move { - module_graph_creator - .create_graph(GraphKind::All, entry_points) - .await - } - .boxed_local() - }, - parsed_source_cache: factory.parsed_source_cache(), - output_dir: &output_dir, - maybe_original_import_map: workspace_resolver.maybe_import_map(), - maybe_jsx_import_source: jsx_import_source.as_ref(), - resolver: factory.resolver().await?.as_graph_resolver(), - environment: &build::RealVendorEnvironment, - }) - .await?; - - let vendored_count = output.vendored_count; - let graph = output.graph; - let npm_package_count = graph.npm_packages.len(); - let try_add_node_modules_dir = npm_package_count > 0 - && cli_options - .node_modules_dir()? - .map(|m| m.uses_node_modules_dir()) - .unwrap_or(true); - - log::info!( - concat!("Vendored {} {} into {} directory.",), - vendored_count, - if vendored_count == 1 { - "module" - } else { - "modules" - }, - raw_output_dir.display(), - ); - - let try_add_import_map = vendored_count > 0; - let modified_result = maybe_update_config_file( - &output_dir, - maybe_config_file, - 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().cloned().or_else(|| { - maybe_config_file - .as_ref() - .map(|d| &d.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 { - let cli_options = - cli_options.with_node_modules_dir_path(node_modules_path); - let factory = CliFactory::from_cli_options(Arc::new(cli_options)); - if let Some(managed) = factory.npm_resolver().await?.as_managed() { - managed.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 modified_result.updated_import_map { - log::info!( - 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 ` flag or by ", - "manually editing your Deno configuration file.", - ), - import_map_path.display(), - ); - } else { - log::info!( - concat!( - "\nTo use vendored modules, specify the `--import-map {}` flag when ", - r#"invoking Deno subcommands or add an `"importMap": ""` "#, - "entry to a deno.json file.", - ), - import_map_path.display(), - ); - } - } - - Ok(()) -} - -fn validate_output_dir( - output_dir: &Path, - flags: &VendorFlags, -) -> 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.", - )); - } - Ok(()) -} - -fn validate_options( - options: &mut CliOptions, - output_dir: &Path, -) -> Result<(), AnyError> { - let import_map_specifier = options - .resolve_specified_import_map_specifier()? - .or_else(|| { - let config_file = options.workspace().root_deno_json()?; - config_file - .to_import_map_specifier() - .ok() - .flatten() - .or_else(|| { - if config_file.is_an_import_map() { - Some(config_file.specifier.clone()) - } else { - None - } - }) - }); - // check the import map - if let Some(import_map_path) = import_map_specifier - .and_then(|p| specifier_to_file_path(&p).ok()) - .and_then(|p| 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 = canonicalize_path(output_dir).with_context(|| { - format!("Failed to canonicalize: {}", output_dir.display()) - })?; - - if import_map_path.starts_with(output_dir) { - // canonicalize to make the test for this pass on the CI - let cwd = 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. - log::warn!( - concat!( - "Ignoring import map. Specifying an import map file ({}) in the ", - "deno vendor output directory is not supported. If you wish to use ", - "an import map while vendoring, please specify one located outside ", - "this directory." - ), - import_map_path - .strip_prefix(&cwd) - .unwrap_or(&import_map_path) - .display() - .to_string(), - ); - - // don't use an import map in the config - options.set_import_map_specifier(None); - } - } - - Ok(()) -} - -fn maybe_update_config_file( - output_dir: &Path, - maybe_config_file: Option<&Arc>, - try_add_import_map: bool, - try_add_node_modules_dir: bool, -) -> ModifiedResult { - assert!(output_dir.is_absolute()); - let config_file = match 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 = config_file - .to_fmt_config() - .ok() - .map(|config| config.options) - .unwrap_or_default(); - let result = update_config_file( - 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(modified_result) => modified_result, - Err(err) => { - warn!("Error updating config file. {:#}", err); - ModifiedResult::default() - } - } -} - -fn update_config_file( - config_file: &ConfigFile, - fmt_options: &FmtOptionsConfig, - import_map_specifier: Option, - try_add_node_modules_dir: bool, -) -> Result { - let config_path = specifier_to_file_path(&config_file.specifier)?; - let config_text = std::fs::read_to_string(&config_path)?; - 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) -} - -#[derive(Default)] -struct ModifiedResult { - updated_import_map: bool, - added_node_modules_dir: bool, - new_text: Option, -} - -fn update_config_text( - text: &str, - fmt_options: &FmtOptionsConfig, - import_map_specifier: Option<&str>, - try_add_node_modules_dir: bool, -) -> Result { - use jsonc_parser::ast::ObjectProp; - use jsonc_parser::ast::Value; - let text = if text.trim().is_empty() { "{}\n" } else { text }; - let ast = - jsonc_parser::parse_to_ast(text, &Default::default(), &Default::default())?; - let obj = match ast.value { - Some(Value::Object(obj)) => obj, - _ => bail!("Failed updating config file due to no object."), - }; - 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; - text_changes.push(TextChange { - range: insert_position..insert_position, - new_text: r#""nodeModulesDir": "auto""#.to_string(), - }); - should_format = true; - modified_result.added_node_modules_dir = true; - } - } - - 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(&PathBuf::from("deno.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 { - 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) - } - } -} - -fn resolve_entry_points( - flags: &VendorFlags, - initial_cwd: &Path, -) -> Result, AnyError> { - flags - .specifiers - .iter() - .map(|p| resolve_url_or_path(p, initial_cwd).map_err(|e| e.into())) - .collect::, _>>() -} - -#[cfg(test)] -mod internal_test { - use super::*; - use pretty_assertions::assert_eq; - - #[test] - fn update_config_text_no_existing_props_add_prop() { - 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}", - &Default::default(), - Some("./vendor/import_map.json"), - true, - ) - .unwrap(); - assert!(result.updated_import_map); - assert!(result.added_node_modules_dir); - assert_eq!( - result.new_text.unwrap(), - r#"{ - "nodeModulesDir": "auto", - "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": "auto" -} -"# - ); - } - - #[test] - fn update_config_text_existing_props_add_prop() { - let result = update_config_text( - r#"{ - "tasks": { - "task1": "other" - } -} -"#, - &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!( - result.new_text.unwrap(), - r#"{ - "tasks": { - "task1": "other" - }, - "importMap": "./vendor/import_map.json" -} -"# - ); - } - - #[test] - fn update_config_text_update_prop() { - let result = update_config_text( - r#"{ - "importMap": "./local.json" -} -"#, - &Default::default(), - Some("./vendor/import_map.json"), - false, - ) - .unwrap(); - assert_eq!( - 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 "none") - let result = update_config_text( - r#"{ - "nodeModulesDir": "none" -} -"#, - &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": "auto" -} -"#, - &Default::default(), - None, - true, - ) - .unwrap(); - assert!(!result.added_node_modules_dir); - assert!(!result.updated_import_map); - assert_eq!(result.new_text, None); - } -} diff --git a/cli/tools/vendor/specifiers.rs b/cli/tools/vendor/specifiers.rs deleted file mode 100644 index e0e0f5337aa7c4..00000000000000 --- a/cli/tools/vendor/specifiers.rs +++ /dev/null @@ -1,208 +0,0 @@ -// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. - -use std::collections::BTreeMap; -use std::collections::HashSet; -use std::path::PathBuf; - -use deno_ast::ModuleSpecifier; -use deno_core::anyhow::anyhow; -use deno_core::error::AnyError; - -use crate::util::path::is_banned_path_char; -use crate::util::path::path_with_stem_suffix; -use crate::util::path::root_url_to_safe_local_dirname; - -/// Partitions the provided specifiers by the non-path and non-query parts of a specifier. -pub fn partition_by_root_specifiers<'a>( - specifiers: impl Iterator, -) -> BTreeMap> { - let mut root_specifiers: BTreeMap> = - Default::default(); - for remote_specifier in specifiers { - let mut root_specifier = remote_specifier.clone(); - root_specifier.set_query(None); - root_specifier.set_path("/"); - - let specifiers = root_specifiers.entry(root_specifier).or_default(); - specifiers.push(remote_specifier.clone()); - } - root_specifiers -} - -/// Gets the directory name to use for the provided root. -pub fn dir_name_for_root(root: &ModuleSpecifier) -> PathBuf { - root_url_to_safe_local_dirname(root) -} - -/// Gets a unique file path given the provided file path -/// and the set of existing file paths. Inserts to the -/// set when finding a unique path. -pub fn get_unique_path( - mut path: PathBuf, - unique_set: &mut HashSet, -) -> PathBuf { - let original_path = path.clone(); - let mut count = 2; - // case insensitive comparison so the output works on case insensitive file systems - while !unique_set.insert(path.to_string_lossy().to_lowercase()) { - path = path_with_stem_suffix(&original_path, &format!("_{count}")); - count += 1; - } - path -} - -pub fn make_url_relative( - root: &ModuleSpecifier, - url: &ModuleSpecifier, -) -> Result { - root.make_relative(url).ok_or_else(|| { - anyhow!( - "Error making url ({}) relative to root: {}", - url.to_string(), - root.to_string() - ) - }) -} - -pub fn is_remote_specifier(specifier: &ModuleSpecifier) -> bool { - matches!(specifier.scheme().to_lowercase().as_str(), "http" | "https") -} - -pub fn is_remote_specifier_text(text: &str) -> bool { - let text = text.trim_start().to_lowercase(); - text.starts_with("http:") || text.starts_with("https:") -} - -pub fn sanitize_filepath(text: &str) -> String { - text - .chars() - .map(|c| if is_banned_path_char(c) { '_' } else { c }) - .collect() -} - -#[cfg(test)] -mod test { - use super::*; - use pretty_assertions::assert_eq; - - #[test] - fn partition_by_root_specifiers_same_sub_folder() { - run_partition_by_root_specifiers_test( - vec![ - "https://deno.land/x/mod/A.ts", - "https://deno.land/x/mod/other/A.ts", - ], - vec![( - "https://deno.land/", - vec![ - "https://deno.land/x/mod/A.ts", - "https://deno.land/x/mod/other/A.ts", - ], - )], - ); - } - - #[test] - fn partition_by_root_specifiers_different_sub_folder() { - run_partition_by_root_specifiers_test( - vec![ - "https://deno.land/x/mod/A.ts", - "https://deno.land/x/other/A.ts", - ], - vec![( - "https://deno.land/", - vec![ - "https://deno.land/x/mod/A.ts", - "https://deno.land/x/other/A.ts", - ], - )], - ); - } - - #[test] - fn partition_by_root_specifiers_different_hosts() { - run_partition_by_root_specifiers_test( - vec![ - "https://deno.land/mod/A.ts", - "http://deno.land/B.ts", - "https://deno.land:8080/C.ts", - "https://localhost/mod/A.ts", - "https://other/A.ts", - ], - vec![ - ("http://deno.land/", vec!["http://deno.land/B.ts"]), - ("https://deno.land/", vec!["https://deno.land/mod/A.ts"]), - ( - "https://deno.land:8080/", - vec!["https://deno.land:8080/C.ts"], - ), - ("https://localhost/", vec!["https://localhost/mod/A.ts"]), - ("https://other/", vec!["https://other/A.ts"]), - ], - ); - } - - fn run_partition_by_root_specifiers_test( - input: Vec<&str>, - expected: Vec<(&str, Vec<&str>)>, - ) { - let input = input - .iter() - .map(|s| ModuleSpecifier::parse(s).unwrap()) - .collect::>(); - let output = partition_by_root_specifiers(input.iter()); - // the assertion is much easier to compare when everything is strings - let output = output - .into_iter() - .map(|(s, vec)| { - ( - s.to_string(), - vec.into_iter().map(|s| s.to_string()).collect::>(), - ) - }) - .collect::>(); - let expected = expected - .into_iter() - .map(|(s, vec)| { - ( - s.to_string(), - vec.into_iter().map(|s| s.to_string()).collect::>(), - ) - }) - .collect::>(); - assert_eq!(output, expected); - } - - #[test] - fn test_unique_path() { - let mut paths = HashSet::new(); - assert_eq!( - get_unique_path(PathBuf::from("/test"), &mut paths), - PathBuf::from("/test") - ); - assert_eq!( - get_unique_path(PathBuf::from("/test"), &mut paths), - PathBuf::from("/test_2") - ); - assert_eq!( - get_unique_path(PathBuf::from("/test"), &mut paths), - PathBuf::from("/test_3") - ); - assert_eq!( - get_unique_path(PathBuf::from("/TEST"), &mut paths), - PathBuf::from("/TEST_4") - ); - assert_eq!( - get_unique_path(PathBuf::from("/test.txt"), &mut paths), - PathBuf::from("/test.txt") - ); - assert_eq!( - get_unique_path(PathBuf::from("/test.txt"), &mut paths), - PathBuf::from("/test_2.txt") - ); - assert_eq!( - get_unique_path(PathBuf::from("/TEST.TXT"), &mut paths), - PathBuf::from("/TEST_3.TXT") - ); - } -} diff --git a/cli/tools/vendor/test.rs b/cli/tools/vendor/test.rs deleted file mode 100644 index 65f37efdcf05c9..00000000000000 --- a/cli/tools/vendor/test.rs +++ /dev/null @@ -1,357 +0,0 @@ -// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. - -use std::cell::RefCell; -use std::collections::HashMap; -use std::collections::HashSet; -use std::path::Path; -use std::path::PathBuf; -use std::sync::Arc; - -use deno_ast::ModuleSpecifier; -use deno_config::workspace::WorkspaceResolver; -use deno_core::anyhow::anyhow; -use deno_core::anyhow::bail; -use deno_core::error::AnyError; -use deno_core::futures; -use deno_core::futures::FutureExt; -use deno_core::serde_json; -use deno_graph::source::LoadFuture; -use deno_graph::source::LoadResponse; -use deno_graph::source::Loader; -use deno_graph::DefaultModuleAnalyzer; -use deno_graph::GraphKind; -use deno_graph::ModuleGraph; -use import_map::ImportMap; - -use crate::args::JsxImportSourceConfig; -use crate::cache::ParsedSourceCache; -use crate::resolver::CliGraphResolver; -use crate::resolver::CliGraphResolverOptions; - -use super::build::VendorEnvironment; - -// Utilities that help `deno vendor` get tested in memory. - -type RemoteFileText = String; -type RemoteFileHeaders = Option>; -type RemoteFileResult = Result<(RemoteFileText, RemoteFileHeaders), String>; - -#[derive(Clone, Default)] -pub struct TestLoader { - files: HashMap, - redirects: HashMap, -} - -impl TestLoader { - pub fn add( - &mut self, - path_or_specifier: impl AsRef, - text: impl AsRef, - ) -> &mut Self { - self.add_result(path_or_specifier, Ok((text.as_ref().to_string(), None))) - } - - pub fn add_failure( - &mut self, - path_or_specifier: impl AsRef, - message: impl AsRef, - ) -> &mut Self { - self.add_result(path_or_specifier, Err(message.as_ref().to_string())) - } - - fn add_result( - &mut self, - path_or_specifier: impl AsRef, - result: RemoteFileResult, - ) -> &mut Self { - if path_or_specifier - .as_ref() - .to_lowercase() - .starts_with("http") - { - self.files.insert( - ModuleSpecifier::parse(path_or_specifier.as_ref()).unwrap(), - result, - ); - } else { - let path = make_path(path_or_specifier.as_ref()); - let specifier = ModuleSpecifier::from_file_path(path).unwrap(); - self.files.insert(specifier, result); - } - self - } - - pub fn add_with_headers( - &mut self, - specifier: impl AsRef, - text: impl AsRef, - headers: &[(&str, &str)], - ) -> &mut Self { - let headers = headers - .iter() - .map(|(key, value)| (key.to_string(), value.to_string())) - .collect(); - self.files.insert( - ModuleSpecifier::parse(specifier.as_ref()).unwrap(), - Ok((text.as_ref().to_string(), Some(headers))), - ); - self - } - - pub fn add_redirect( - &mut self, - from: impl AsRef, - to: impl AsRef, - ) -> &mut Self { - self.redirects.insert( - ModuleSpecifier::parse(from.as_ref()).unwrap(), - ModuleSpecifier::parse(to.as_ref()).unwrap(), - ); - self - } -} - -impl Loader for TestLoader { - fn load( - &self, - specifier: &ModuleSpecifier, - _options: deno_graph::source::LoadOptions, - ) -> LoadFuture { - if let Some(redirect) = self.redirects.get(specifier) { - return Box::pin(futures::future::ready(Ok(Some( - LoadResponse::Redirect { - specifier: redirect.clone(), - }, - )))); - } - let result = self.files.get(specifier).map(|result| match result { - Ok(result) => Ok(LoadResponse::Module { - specifier: specifier.clone(), - content: result.0.clone().into_bytes().into(), - maybe_headers: result.1.clone(), - }), - Err(err) => Err(err), - }); - let result = match result { - Some(Ok(result)) => Ok(Some(result)), - Some(Err(err)) => Err(anyhow!("{}", err)), - None if specifier.scheme() == "data" => { - deno_graph::source::load_data_url(specifier) - } - None => Ok(None), - }; - Box::pin(futures::future::ready(result)) - } -} - -#[derive(Default)] -struct TestVendorEnvironment { - directories: RefCell>, - files: RefCell>, -} - -impl VendorEnvironment for TestVendorEnvironment { - fn create_dir_all(&self, dir_path: &Path) -> Result<(), AnyError> { - let mut directories = self.directories.borrow_mut(); - for path in dir_path.ancestors() { - if !directories.insert(path.to_path_buf()) { - break; - } - } - Ok(()) - } - - fn write_file(&self, file_path: &Path, text: &[u8]) -> Result<(), AnyError> { - let parent = file_path.parent().unwrap(); - if !self.directories.borrow().contains(parent) { - bail!("Directory not found: {}", parent.display()); - } - self.files.borrow_mut().insert( - file_path.to_path_buf(), - String::from_utf8(text.to_vec()).unwrap(), - ); - Ok(()) - } -} - -pub struct VendorOutput { - pub files: Vec<(String, String)>, - pub import_map: Option, -} - -#[derive(Default)] -pub struct VendorTestBuilder { - entry_points: Vec, - loader: TestLoader, - maybe_original_import_map: Option, - environment: TestVendorEnvironment, - jsx_import_source_config: Option, -} - -impl VendorTestBuilder { - pub fn with_default_setup() -> Self { - let mut builder = VendorTestBuilder::default(); - builder.add_entry_point("/mod.ts"); - builder - } - - pub fn resolve_to_url(&self, path: &str) -> ModuleSpecifier { - ModuleSpecifier::from_file_path(make_path(path)).unwrap() - } - - pub fn new_import_map(&self, base_path: &str) -> ImportMap { - let base = self.resolve_to_url(base_path); - ImportMap::new(base) - } - - pub fn set_original_import_map( - &mut self, - import_map: ImportMap, - ) -> &mut Self { - self.maybe_original_import_map = Some(import_map); - self - } - - pub fn add_entry_point(&mut self, entry_point: impl AsRef) -> &mut Self { - let entry_point = make_path(entry_point.as_ref()); - self - .entry_points - .push(ModuleSpecifier::from_file_path(entry_point).unwrap()); - self - } - - pub fn set_jsx_import_source_config( - &mut self, - jsx_import_source_config: JsxImportSourceConfig, - ) -> &mut Self { - self.jsx_import_source_config = Some(jsx_import_source_config); - self - } - - pub async fn build(&mut self) -> Result { - let output_dir = make_path("/vendor"); - let entry_points = self.entry_points.clone(); - let loader = self.loader.clone(); - let parsed_source_cache = ParsedSourceCache::default(); - let resolver = Arc::new(build_resolver( - output_dir.parent().unwrap(), - self.jsx_import_source_config.clone(), - self.maybe_original_import_map.clone(), - )); - super::build::build(super::build::BuildInput { - entry_points, - build_graph: { - let resolver = resolver.clone(); - move |entry_points| { - async move { - Ok( - build_test_graph( - entry_points, - loader, - resolver.as_graph_resolver(), - &DefaultModuleAnalyzer, - ) - .await, - ) - } - .boxed_local() - } - }, - parsed_source_cache: &parsed_source_cache, - output_dir: &output_dir, - maybe_original_import_map: self.maybe_original_import_map.as_ref(), - maybe_jsx_import_source: self.jsx_import_source_config.as_ref(), - resolver: resolver.as_graph_resolver(), - environment: &self.environment, - }) - .await?; - - let mut files = self.environment.files.borrow_mut(); - let import_map = files.remove(&output_dir.join("import_map.json")); - let mut files = files - .iter() - .map(|(path, text)| (path_to_string(path), text.to_string())) - .collect::>(); - - files.sort_by(|a, b| a.0.cmp(&b.0)); - - Ok(VendorOutput { - import_map: import_map.map(|text| serde_json::from_str(&text).unwrap()), - files, - }) - } - - pub fn with_loader(&mut self, action: impl Fn(&mut TestLoader)) -> &mut Self { - action(&mut self.loader); - self - } -} - -fn build_resolver( - root_dir: &Path, - maybe_jsx_import_source_config: Option, - maybe_original_import_map: Option, -) -> CliGraphResolver { - CliGraphResolver::new(CliGraphResolverOptions { - node_resolver: None, - npm_resolver: None, - sloppy_imports_resolver: None, - workspace_resolver: Arc::new(WorkspaceResolver::new_raw( - Arc::new(ModuleSpecifier::from_directory_path(root_dir).unwrap()), - maybe_original_import_map, - Vec::new(), - Vec::new(), - deno_config::workspace::PackageJsonDepResolution::Enabled, - )), - maybe_jsx_import_source_config, - maybe_vendor_dir: None, - bare_node_builtins_enabled: false, - }) -} - -async fn build_test_graph( - roots: Vec, - loader: TestLoader, - resolver: &dyn deno_graph::source::Resolver, - analyzer: &dyn deno_graph::ModuleAnalyzer, -) -> ModuleGraph { - let mut graph = ModuleGraph::new(GraphKind::All); - graph - .build( - roots, - &loader, - deno_graph::BuildOptions { - resolver: Some(resolver), - module_analyzer: analyzer, - ..Default::default() - }, - ) - .await; - graph -} - -fn make_path(text: &str) -> PathBuf { - // This should work all in memory. We're waiting on - // https://github.com/servo/rust-url/issues/730 to provide - // a cross platform path here - assert!(text.starts_with('/')); - if cfg!(windows) { - PathBuf::from(format!("C:{}", text.replace('/', "\\"))) - } else { - PathBuf::from(text) - } -} - -fn path_to_string

(path: P) -> String -where - P: AsRef, -{ - let path = path.as_ref(); - // inverse of the function above - let path = path.to_string_lossy(); - if cfg!(windows) { - path.replace("C:\\", "\\").replace('\\', "/") - } else { - path.to_string() - } -} diff --git a/cli/util/fs.rs b/cli/util/fs.rs index 145f9c83b89de6..d723d24e1f184f 100644 --- a/cli/util/fs.rs +++ b/cli/util/fs.rs @@ -1,6 +1,5 @@ // Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. -use std::env::current_dir; use std::fs::OpenOptions; use std::io::Error; use std::io::ErrorKind; @@ -18,7 +17,6 @@ use deno_config::glob::WalkEntry; use deno_core::anyhow::anyhow; use deno_core::anyhow::Context; use deno_core::error::AnyError; -pub use deno_core::normalize_path; use deno_core::unsync::spawn_blocking; use deno_core::ModuleSpecifier; use deno_runtime::deno_fs::FileSystem; @@ -255,18 +253,6 @@ fn canonicalize_path_maybe_not_exists_with_custom_fn( } } -pub fn resolve_from_cwd(path: &Path) -> Result { - let resolved_path = if path.is_absolute() { - path.to_owned() - } else { - let cwd = - current_dir().context("Failed to get current working directory")?; - cwd.join(path) - }; - - Ok(normalize_path(resolved_path)) -} - /// Collects module specifiers that satisfy the given predicate as a file path, by recursively walking `include`. /// Specifiers that start with http and https are left intact. /// Note: This ignores all .git and node_modules folders. @@ -715,30 +701,13 @@ pub fn specifier_from_file_path( mod tests { use super::*; use deno_core::futures; + use deno_core::normalize_path; use deno_core::parking_lot::Mutex; use pretty_assertions::assert_eq; use test_util::PathRef; use test_util::TempDir; use tokio::sync::Notify; - #[test] - fn resolve_from_cwd_child() { - let cwd = current_dir().unwrap(); - assert_eq!(resolve_from_cwd(Path::new("a")).unwrap(), cwd.join("a")); - } - - #[test] - fn resolve_from_cwd_dot() { - let cwd = current_dir().unwrap(); - assert_eq!(resolve_from_cwd(Path::new(".")).unwrap(), cwd); - } - - #[test] - fn resolve_from_cwd_parent() { - let cwd = current_dir().unwrap(); - assert_eq!(resolve_from_cwd(Path::new("a/..")).unwrap(), cwd); - } - #[test] fn test_normalize_path() { assert_eq!(normalize_path(Path::new("a/../b")), PathBuf::from("b")); @@ -756,14 +725,6 @@ mod tests { } } - #[test] - fn resolve_from_cwd_absolute() { - let expected = Path::new("a"); - let cwd = current_dir().unwrap(); - let absolute_expected = cwd.join(expected); - assert_eq!(resolve_from_cwd(expected).unwrap(), absolute_expected); - } - #[test] fn test_collect_specifiers() { fn create_files(dir_path: &PathRef, files: &[&str]) { diff --git a/cli/util/path.rs b/cli/util/path.rs index 16378e30b05b5a..804b26f65f7219 100644 --- a/cli/util/path.rs +++ b/cli/util/path.rs @@ -145,34 +145,6 @@ pub fn relative_specifier( Some(to_percent_decoded_str(&text)) } -/// Gets a path with the specified file stem suffix. -/// -/// Ex. `file.ts` with suffix `_2` returns `file_2.ts` -pub fn path_with_stem_suffix(path: &Path, suffix: &str) -> PathBuf { - if let Some(file_name) = path.file_name().map(|f| f.to_string_lossy()) { - if let Some(file_stem) = path.file_stem().map(|f| f.to_string_lossy()) { - if let Some(ext) = path.extension().map(|f| f.to_string_lossy()) { - return if file_stem.to_lowercase().ends_with(".d") { - path.with_file_name(format!( - "{}{}.{}.{}", - &file_stem[..file_stem.len() - ".d".len()], - suffix, - // maintain casing - &file_stem[file_stem.len() - "d".len()..], - ext - )) - } else { - path.with_file_name(format!("{file_stem}{suffix}.{ext}")) - }; - } - } - - path.with_file_name(format!("{file_name}{suffix}")) - } else { - path.with_file_name(suffix) - } -} - #[cfg_attr(windows, allow(dead_code))] pub fn relative_path(from: &Path, to: &Path) -> Option { pathdiff::diff_paths(to, from) @@ -405,46 +377,6 @@ mod test { } } - #[test] - fn test_path_with_stem_suffix() { - assert_eq!( - path_with_stem_suffix(&PathBuf::from("/"), "_2"), - PathBuf::from("/_2") - ); - assert_eq!( - path_with_stem_suffix(&PathBuf::from("/test"), "_2"), - PathBuf::from("/test_2") - ); - assert_eq!( - path_with_stem_suffix(&PathBuf::from("/test.txt"), "_2"), - PathBuf::from("/test_2.txt") - ); - assert_eq!( - path_with_stem_suffix(&PathBuf::from("/test/subdir"), "_2"), - PathBuf::from("/test/subdir_2") - ); - assert_eq!( - path_with_stem_suffix(&PathBuf::from("/test/subdir.other.txt"), "_2"), - PathBuf::from("/test/subdir.other_2.txt") - ); - assert_eq!( - path_with_stem_suffix(&PathBuf::from("/test.d.ts"), "_2"), - PathBuf::from("/test_2.d.ts") - ); - assert_eq!( - path_with_stem_suffix(&PathBuf::from("/test.D.TS"), "_2"), - PathBuf::from("/test_2.D.TS") - ); - assert_eq!( - path_with_stem_suffix(&PathBuf::from("/test.d.mts"), "_2"), - PathBuf::from("/test_2.d.mts") - ); - assert_eq!( - path_with_stem_suffix(&PathBuf::from("/test.d.cts"), "_2"), - PathBuf::from("/test_2.d.cts") - ); - } - #[test] fn test_to_percent_decoded_str() { let str = to_percent_decoded_str("%F0%9F%A6%95"); diff --git a/tests/integration/mod.rs b/tests/integration/mod.rs index ea3269aaaf8849..d12abcde67d079 100644 --- a/tests/integration/mod.rs +++ b/tests/integration/mod.rs @@ -68,8 +68,6 @@ mod task; mod test; #[path = "upgrade_tests.rs"] mod upgrade; -#[path = "vendor_tests.rs"] -mod vendor; #[path = "watcher_tests.rs"] mod watcher; #[path = "worker_tests.rs"] diff --git a/tests/integration/vendor_tests.rs b/tests/integration/vendor_tests.rs deleted file mode 100644 index 550fbf8b7623d6..00000000000000 --- a/tests/integration/vendor_tests.rs +++ /dev/null @@ -1,751 +0,0 @@ -// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. - -use deno_core::serde_json; -use deno_core::serde_json::json; -use pretty_assertions::assert_eq; -use std::fmt::Write as _; -use std::path::PathBuf; -use test_util as util; -use test_util::itest; -use test_util::TempDir; -use util::http_server; -use util::new_deno_dir; -use util::TestContextBuilder; - -const DEPRECATION_NOTICE: &str = "⚠️ Warning: `deno vendor` is deprecated and will be removed in Deno 2.0.\nAdd `\"vendor\": true` to your `deno.json` or use the `--vendor` flag instead.\n"; - -#[test] -fn output_dir_exists() { - let t = TempDir::new(); - t.write("mod.ts", ""); - t.create_dir_all("vendor"); - t.write("vendor/mod.ts", ""); - - let deno = util::deno_cmd() - .current_dir(t.path()) - .env("NO_COLOR", "1") - .arg("vendor") - .arg("mod.ts") - .stderr_piped() - .spawn() - .unwrap(); - let output = deno.wait_with_output().unwrap(); - assert_eq!( - String::from_utf8_lossy(&output.stderr).trim(), - format!("{}error: Output directory was not empty. Please specify an empty directory or use --force to ignore this error and potentially overwrite its contents.", &DEPRECATION_NOTICE) - ); - assert!(!output.status.success()); - - // ensure it errors when using the `--output` arg too - let deno = util::deno_cmd() - .current_dir(t.path()) - .env("NO_COLOR", "1") - .arg("vendor") - .arg("--output") - .arg("vendor") - .arg("mod.ts") - .stderr_piped() - .spawn() - .unwrap(); - let output = deno.wait_with_output().unwrap(); - assert_eq!( - String::from_utf8_lossy(&output.stderr).trim(), - format!("{}error: Output directory was not empty. Please specify an empty directory or use --force to ignore this error and potentially overwrite its contents.", &DEPRECATION_NOTICE) - ); - assert!(!output.status.success()); - - // now use `--force` - let status = util::deno_cmd() - .current_dir(t.path()) - .env("NO_COLOR", "1") - .arg("vendor") - .arg("mod.ts") - .arg("--force") - .spawn() - .unwrap() - .wait() - .unwrap(); - assert!(status.success()); -} - -#[test] -fn standard_test() { - let _server = http_server(); - let t = TempDir::new(); - let vendor_dir = t.path().join("vendor2"); - t.write( - "my_app.ts", - "import {Logger} from 'http://localhost:4545/vendor/query_reexport.ts?testing'; new Logger().log('outputted');", - ); - - let deno = util::deno_cmd() - .current_dir(t.path()) - .arg("vendor") - .arg("my_app.ts") - .arg("--output") - .arg("vendor2") - .env("NO_COLOR", "1") - .piped_output() - .spawn() - .unwrap(); - let output = deno.wait_with_output().unwrap(); - assert_eq!( - String::from_utf8_lossy(&output.stderr).trim(), - format!( - concat!( - "{}", - "Download http://localhost:4545/vendor/query_reexport.ts?testing\n", - "Download http://localhost:4545/vendor/logger.ts?test\n", - "{}", - ), - &DEPRECATION_NOTICE, - success_text("2 modules", "vendor2", true), - ) - ); - assert_eq!(String::from_utf8_lossy(&output.stdout).trim(), ""); - assert!(output.status.success()); - - assert!(vendor_dir.exists()); - assert!(!t.path().join("vendor").exists()); - let import_map: serde_json::Value = - serde_json::from_str(&t.read_to_string("vendor2/import_map.json")).unwrap(); - assert_eq!( - import_map, - json!({ - "imports": { - "http://localhost:4545/vendor/query_reexport.ts?testing": "./localhost_4545/vendor/query_reexport.ts", - "http://localhost:4545/": "./localhost_4545/", - }, - "scopes": { - "./localhost_4545/": { - "./localhost_4545/vendor/logger.ts?test": "./localhost_4545/vendor/logger.ts" - } - } - }), - ); - - // try running the output with `--no-remote` - let deno = util::deno_cmd() - .current_dir(t.path()) - .env("NO_COLOR", "1") - .arg("run") - .arg("--no-remote") - .arg("--check") - .arg("--quiet") - .arg("--import-map") - .arg("vendor2/import_map.json") - .arg("my_app.ts") - .piped_output() - .spawn() - .unwrap(); - let output = deno.wait_with_output().unwrap(); - assert_eq!(String::from_utf8_lossy(&output.stderr).trim(), ""); - assert_eq!(String::from_utf8_lossy(&output.stdout).trim(), "outputted"); - assert!(output.status.success()); -} - -#[test] -fn import_map_output_dir() { - let _server = http_server(); - let t = TempDir::new(); - t.write("mod.ts", ""); - t.create_dir_all("vendor"); - t.write( - "vendor/import_map.json", - // will be ignored - "{ \"imports\": { \"https://localhost:4545/\": \"./localhost/\" }}", - ); - t.write( - "deno.json", - "{ \"import_map\": \"./vendor/import_map.json\" }", - ); - t.write( - "my_app.ts", - "import {Logger} from 'http://localhost:4545/vendor/logger.ts'; new Logger().log('outputted');", - ); - - let deno = util::deno_cmd() - .current_dir(t.path()) - .env("NO_COLOR", "1") - .arg("vendor") - .arg("--force") - .arg("--import-map") - .arg("vendor/import_map.json") - .arg("my_app.ts") - .piped_output() - .spawn() - .unwrap(); - let output = deno.wait_with_output().unwrap(); - assert_eq!( - String::from_utf8_lossy(&output.stderr).trim(), - format!( - concat!( - "{}{}\n", - "Download http://localhost:4545/vendor/logger.ts\n", - "{}\n\n{}", - ), - &DEPRECATION_NOTICE, - ignoring_import_map_text(), - vendored_text("1 module", "vendor/"), - success_text_updated_deno_json("vendor/"), - ) - ); - assert!(output.status.success()); -} - -#[test] -fn remote_module_test() { - let _server = http_server(); - let t = TempDir::new(); - let vendor_dir = t.path().join("vendor"); - - let deno = util::deno_cmd() - .current_dir(t.path()) - .env("NO_COLOR", "1") - .arg("vendor") - .arg("http://localhost:4545/vendor/query_reexport.ts") - .piped_output() - .spawn() - .unwrap(); - let output = deno.wait_with_output().unwrap(); - assert_eq!( - String::from_utf8_lossy(&output.stderr).trim(), - format!( - concat!( - "{}", - "Download http://localhost:4545/vendor/query_reexport.ts\n", - "Download http://localhost:4545/vendor/logger.ts?test\n", - "{}", - ), - &DEPRECATION_NOTICE, - success_text("2 modules", "vendor/", true), - ) - ); - assert_eq!(String::from_utf8_lossy(&output.stdout).trim(), ""); - assert!(output.status.success()); - assert!(vendor_dir.exists()); - assert!(vendor_dir - .join("localhost_4545/vendor/query_reexport.ts") - .exists()); - assert!(vendor_dir.join("localhost_4545/vendor/logger.ts").exists()); - let import_map: serde_json::Value = - serde_json::from_str(&t.read_to_string("vendor/import_map.json")).unwrap(); - assert_eq!( - import_map, - json!({ - "imports": { - "http://localhost:4545/": "./localhost_4545/", - }, - "scopes": { - "./localhost_4545/": { - "./localhost_4545/vendor/logger.ts?test": "./localhost_4545/vendor/logger.ts", - } - } - }), - ); -} - -#[test] -fn existing_import_map_no_remote() { - let _server = http_server(); - let t = TempDir::new(); - t.write( - "mod.ts", - "import {Logger} from 'http://localhost:4545/vendor/logger.ts';", - ); - let import_map_filename = "imports2.json"; - let import_map_text = - r#"{ "imports": { "http://localhost:4545/vendor/": "./logger/" } }"#; - t.write(import_map_filename, import_map_text); - t.create_dir_all("logger"); - t.write("logger/logger.ts", "export class Logger {}"); - - let deno = util::deno_cmd() - .current_dir(t.path()) - .env("NO_COLOR", "1") - .arg("vendor") - .arg("mod.ts") - .arg("--import-map") - .arg(import_map_filename) - .stderr_piped() - .spawn() - .unwrap(); - let output = deno.wait_with_output().unwrap(); - assert_eq!( - String::from_utf8_lossy(&output.stderr).trim(), - format!( - "{}{}", - &DEPRECATION_NOTICE, - success_text("0 modules", "vendor/", false) - ) - ); - assert!(output.status.success()); - // it should not have found any remote dependencies because - // the provided import map mapped it to a local directory - assert_eq!(t.read_to_string(import_map_filename), import_map_text); -} - -#[test] -fn existing_import_map_mixed_with_remote() { - let _server = http_server(); - let deno_dir = new_deno_dir(); - let t = TempDir::new(); - t.write( - "mod.ts", - "import {Logger} from 'http://localhost:4545/vendor/logger.ts';", - ); - - let status = util::deno_cmd_with_deno_dir(&deno_dir) - .current_dir(t.path()) - .arg("vendor") - .arg("mod.ts") - .spawn() - .unwrap() - .wait() - .unwrap(); - assert!(status.success()); - - assert_eq!( - t.read_to_string("vendor/import_map.json"), - r#"{ - "imports": { - "http://localhost:4545/": "./localhost_4545/" - } -} -"#, - ); - - // make the import map specific to support vendoring mod.ts in the next step - t.write( - "vendor/import_map.json", - r#"{ - "imports": { - "http://localhost:4545/vendor/logger.ts": "./localhost_4545/vendor/logger.ts" - } -} -"#, - ); - - t.write( - "mod.ts", - concat!( - "import {Logger} from 'http://localhost:4545/vendor/logger.ts';\n", - "import {Logger as OtherLogger} from 'http://localhost:4545/vendor/mod.ts';\n", - ), - ); - - // now vendor with the existing import map in a separate vendor directory - let deno = util::deno_cmd_with_deno_dir(&deno_dir) - .env("NO_COLOR", "1") - .current_dir(t.path()) - .arg("vendor") - .arg("mod.ts") - .arg("--import-map") - .arg("vendor/import_map.json") - .arg("--output") - .arg("vendor2") - .piped_output() - .spawn() - .unwrap(); - let output = deno.wait_with_output().unwrap(); - assert_eq!( - String::from_utf8_lossy(&output.stderr).trim(), - format!( - "{}Download http://localhost:4545/vendor/mod.ts\n{}", - &DEPRECATION_NOTICE, - success_text("1 module", "vendor2", true), - ) - ); - assert!(output.status.success()); - - // tricky scenario here where the output directory now contains a mapping - // back to the previous vendor location - assert_eq!( - t.read_to_string("vendor2/import_map.json"), - r#"{ - "imports": { - "http://localhost:4545/vendor/logger.ts": "../vendor/localhost_4545/vendor/logger.ts", - "http://localhost:4545/": "./localhost_4545/" - }, - "scopes": { - "./localhost_4545/": { - "./localhost_4545/vendor/logger.ts": "../vendor/localhost_4545/vendor/logger.ts" - } - } -} -"#, - ); - - // ensure it runs - let status = util::deno_cmd() - .current_dir(t.path()) - .arg("run") - .arg("--check") - .arg("--no-remote") - .arg("--import-map") - .arg("vendor2/import_map.json") - .arg("mod.ts") - .spawn() - .unwrap() - .wait() - .unwrap(); - assert!(status.success()); -} - -#[test] -fn dynamic_import() { - let _server = http_server(); - let t = TempDir::new(); - t.write( - "mod.ts", - "import {Logger} from 'http://localhost:4545/vendor/dynamic.ts'; new Logger().log('outputted');", - ); - - let status = util::deno_cmd() - .current_dir(t.path()) - .arg("vendor") - .arg("mod.ts") - .spawn() - .unwrap() - .wait() - .unwrap(); - assert!(status.success()); - let import_map: serde_json::Value = - serde_json::from_str(&t.read_to_string("vendor/import_map.json")).unwrap(); - assert_eq!( - import_map, - json!({ - "imports": { - "http://localhost:4545/": "./localhost_4545/", - } - }), - ); - - // try running the output with `--no-remote` - let deno = util::deno_cmd() - .current_dir(t.path()) - .env("NO_COLOR", "1") - .arg("run") - .arg("--allow-read=.") - .arg("--no-remote") - .arg("--check") - .arg("--quiet") - .arg("--import-map") - .arg("vendor/import_map.json") - .arg("mod.ts") - .piped_output() - .spawn() - .unwrap(); - let output = deno.wait_with_output().unwrap(); - assert_eq!(String::from_utf8_lossy(&output.stderr).trim(), ""); - assert_eq!(String::from_utf8_lossy(&output.stdout).trim(), "outputted"); - assert!(output.status.success()); -} - -#[test] -fn dynamic_non_analyzable_import() { - let _server = http_server(); - let t = TempDir::new(); - t.write( - "mod.ts", - "import {Logger} from 'http://localhost:4545/vendor/dynamic_non_analyzable.ts'; new Logger().log('outputted');", - ); - - let deno = util::deno_cmd() - .current_dir(t.path()) - .env("NO_COLOR", "1") - .arg("vendor") - .arg("--reload") - .arg("mod.ts") - .piped_output() - .spawn() - .unwrap(); - let output = deno.wait_with_output().unwrap(); - // todo(https://github.com/denoland/deno_graph/issues/138): it should warn about - // how it couldn't analyze the dynamic import - assert_eq!( - String::from_utf8_lossy(&output.stderr).trim(), - format!( - "{}Download http://localhost:4545/vendor/dynamic_non_analyzable.ts\n{}", - &DEPRECATION_NOTICE, - success_text("1 module", "vendor/", true), - ) - ); - assert_eq!(String::from_utf8_lossy(&output.stdout).trim(), ""); - assert!(output.status.success()); -} - -itest!(dynamic_non_existent { - args: "vendor http://localhost:4545/vendor/dynamic_non_existent.ts", - temp_cwd: true, - exit_code: 0, - http_server: true, - output: "vendor/dynamic_non_existent.ts.out", -}); - -#[test] -fn update_existing_config_test() { - let _server = http_server(); - let t = TempDir::new(); - t.write( - "my_app.ts", - "import {Logger} from 'http://localhost:4545/vendor/logger.ts'; new Logger().log('outputted');", - ); - t.write("deno.json", "{\n}"); - - let deno = util::deno_cmd() - .current_dir(t.path()) - .arg("vendor") - .arg("my_app.ts") - .arg("--output") - .arg("vendor2") - .env("NO_COLOR", "1") - .piped_output() - .spawn() - .unwrap(); - let output = deno.wait_with_output().unwrap(); - assert_eq!( - String::from_utf8_lossy(&output.stderr).trim(), - format!( - "{}Download http://localhost:4545/vendor/logger.ts\n{}\n\n{}", - &DEPRECATION_NOTICE, - vendored_text("1 module", "vendor2"), - success_text_updated_deno_json("vendor2",) - ) - ); - assert_eq!(String::from_utf8_lossy(&output.stdout).trim(), ""); - assert!(output.status.success()); - - // try running the output with `--no-remote` and not specifying a `--vendor` - let deno = util::deno_cmd() - .current_dir(t.path()) - .env("NO_COLOR", "1") - .arg("run") - .arg("--no-remote") - .arg("--check") - .arg("--quiet") - .arg("my_app.ts") - .piped_output() - .spawn() - .unwrap(); - let output = deno.wait_with_output().unwrap(); - assert_eq!(String::from_utf8_lossy(&output.stderr).trim(), ""); - assert_eq!(String::from_utf8_lossy(&output.stdout).trim(), "outputted"); - assert!(output.status.success()); -} - -#[test] -fn update_existing_empty_config_test() { - let _server = http_server(); - let t = TempDir::new(); - t.write( - "my_app.ts", - "import {Logger} from 'http://localhost:4545/vendor/logger.ts'; new Logger().log('outputted');", - ); - t.write("deno.json", ""); - - let deno = util::deno_cmd() - .current_dir(t.path()) - .arg("vendor") - .arg("my_app.ts") - .arg("--output") - .arg("vendor2") - .env("NO_COLOR", "1") - .piped_output() - .spawn() - .unwrap(); - let output = deno.wait_with_output().unwrap(); - assert_eq!( - String::from_utf8_lossy(&output.stderr).trim(), - format!( - "⚠️ Warning: `deno vendor` is deprecated and will be removed in Deno 2.0. -Add `\"vendor\": true` to your `deno.json` or use the `--vendor` flag instead. -Download http://localhost:4545/vendor/logger.ts\n{}\n\n{}", - vendored_text("1 module", "vendor2"), - success_text_updated_deno_json("vendor2",) - ) - ); - assert_eq!(String::from_utf8_lossy(&output.stdout).trim(), ""); - assert!(output.status.success()); -} - -// TODO(2.0): decide if this test should be updated or removed -#[test] -#[ignore] -fn vendor_npm_node_specifiers() { - let context = TestContextBuilder::for_npm().use_temp_cwd().build(); - let temp_dir = context.temp_dir(); - temp_dir.write( - "my_app.ts", - concat!( - "import { path, getValue, setValue } from 'http://localhost:4545/vendor/npm_and_node_specifier.ts';\n", - "setValue(5);\n", - "console.log(path.isAbsolute(Deno.cwd()), getValue());", - ), - ); - temp_dir.write("deno.json", "{}"); - - let output = context.new_command().args("vendor my_app.ts").run(); - output.assert_matches_text(format!( - concat!( - "⚠️ Warning: `deno vendor` is deprecated and will be removed in Deno 2.0.\n", - "Add `\"vendor\": true` to your `deno.json` or use the `--vendor` flag instead.\n", - "Download http://localhost:4545/vendor/npm_and_node_specifier.ts\n", - "Download http://localhost:4260/@denotest/esm-basic\n", - "Download http://localhost:4260/@denotest/esm-basic/1.0.0.tgz\n", - "{}\n", - "Initialize @denotest/esm-basic@1.0.0\n", - "{}\n\n", - "{}\n", - ), - vendored_text("1 module", "vendor/"), - vendored_npm_package_text("1 npm package"), - success_text_updated_deno_json("vendor/") - )); - let output = context.new_command().args("run -A -q my_app.ts").run(); - output.assert_matches_text("true 5\n"); - assert!(temp_dir.path().join("node_modules").exists()); - assert!(temp_dir.path().join("deno.lock").exists()); - - // now try re-vendoring with a lockfile - let output = context.new_command().args("vendor --force my_app.ts").run(); - output.assert_matches_text(format!( - "{}{}\n{}\n\n{}\n", - &DEPRECATION_NOTICE, - ignoring_import_map_text(), - vendored_text("1 module", "vendor/"), - success_text_updated_deno_json("vendor/"), - )); - - // delete the node_modules folder - temp_dir.remove_dir_all("node_modules"); - - // vendor with --node-modules-dir=false - let output = context - .new_command() - .args("vendor --node-modules-dir=false --force my_app.ts") - .run(); - output.assert_matches_text(format!( - "{}{}\n{}\n\n{}\n", - &DEPRECATION_NOTICE, - ignoring_import_map_text(), - vendored_text("1 module", "vendor/"), - success_text_updated_deno_json("vendor/") - )); - assert!(!temp_dir.path().join("node_modules").exists()); - - // delete the deno.json - temp_dir.remove_file("deno.json"); - - // vendor with --node-modules-dir - let output = context - .new_command() - .args("vendor --node-modules-dir --force my_app.ts") - .run(); - output.assert_matches_text(format!( - "{}Initialize @denotest/esm-basic@1.0.0\n{}\n\n{}\n", - &DEPRECATION_NOTICE, - vendored_text("1 module", "vendor/"), - use_import_map_text("vendor/") - )); -} - -#[test] -fn vendor_only_npm_specifiers() { - let context = TestContextBuilder::for_npm().use_temp_cwd().build(); - let temp_dir = context.temp_dir(); - temp_dir.write( - "my_app.ts", - concat!( - "import { getValue, setValue } from 'npm:@denotest/esm-basic';\n", - "setValue(5);\n", - "console.log(path.isAbsolute(Deno.cwd()), getValue());", - ), - ); - temp_dir.write("deno.json", "{}"); - - let output = context.new_command().args("vendor my_app.ts").run(); - output.assert_matches_text(format!( - concat!( - "⚠️ Warning: `deno vendor` is deprecated and will be removed in Deno 2.0.\n", - "Add `\"vendor\": true` to your `deno.json` or use the `--vendor` flag instead.\n", - "Download http://localhost:4260/@denotest/esm-basic\n", - "Download http://localhost:4260/@denotest/esm-basic/1.0.0.tgz\n", - "{}\n", - "Initialize @denotest/esm-basic@1.0.0\n", - "{}\n", - ), - vendored_text("0 modules", "vendor/"), - vendored_npm_package_text("1 npm package"), - )); -} - -fn success_text(module_count: &str, dir: &str, has_import_map: bool) -> String { - let mut text = format!("Vendored {module_count} into {dir} directory."); - if has_import_map { - write!(text, "\n\n{}", use_import_map_text(dir)).unwrap(); - } - text -} - -fn use_import_map_text(dir: &str) -> String { - format!( - concat!( - "To use vendored modules, specify the `--import-map {}import_map.json` flag when ", - r#"invoking Deno subcommands or add an `"importMap": ""` "#, - "entry to a deno.json file.", - ), - if dir != "vendor/" { - format!("{}{}", dir.trim_end_matches('/'), if cfg!(windows) { '\\' } else {'/'}) - } else { - dir.to_string() - } - ) -} - -fn vendored_text(module_count: &str, dir: &str) -> String { - format!("Vendored {} into {} directory.", module_count, dir) -} - -fn vendored_npm_package_text(package_count: &str) -> String { - format!( - concat!( - "Vendored {} into node_modules directory. Set `nodeModulesDir: false` ", - "in the Deno configuration file to disable vendoring npm packages in the future.", - ), - package_count - ) -} - -fn success_text_updated_deno_json(dir: &str) -> String { - format!( - concat!( - "Updated your local Deno configuration file with a reference to the ", - "new vendored import map at {}import_map.json. Invoking Deno subcommands will ", - "now automatically resolve using the vendored modules. You may override ", - "this by providing the `--import-map ` flag or by ", - "manually editing your Deno configuration file.", - ), - if dir != "vendor/" { - format!( - "{}{}", - dir.trim_end_matches('/'), - if cfg!(windows) { '\\' } else { '/' } - ) - } else { - dir.to_string() - } - ) -} - -fn ignoring_import_map_text() -> String { - format!( - concat!( - "Ignoring import map. Specifying an import map file ({}) in the deno ", - "vendor output directory is not supported. If you wish to use an ", - "import map while vendoring, please specify one located outside this ", - "directory.", - ), - PathBuf::from("vendor").join("import_map.json").display(), - ) -} diff --git a/tests/specs/vendor/removed/__test__.jsonc b/tests/specs/vendor/removed/__test__.jsonc new file mode 100644 index 00000000000000..abbcfe46530337 --- /dev/null +++ b/tests/specs/vendor/removed/__test__.jsonc @@ -0,0 +1,13 @@ +{ + "steps": [ + { + "args": "vendor", + "output": "vendor.out", + "exitCode": 1 + }, + { + "args": "vendor --help", + "output": "vendor_help.out" + } + ] +} diff --git a/tests/specs/vendor/removed/vendor.out b/tests/specs/vendor/removed/vendor.out new file mode 100644 index 00000000000000..ee7e9ca5562d0f --- /dev/null +++ b/tests/specs/vendor/removed/vendor.out @@ -0,0 +1,3 @@ +error: ⚠️ `deno vendor` was removed in Deno 2. + +See the Deno 1.x to 2.x Migration Guide for migration instructions: https://docs.deno.com/runtime/manual/advanced/migrate_deprecations diff --git a/tests/specs/vendor/removed/vendor_help.out b/tests/specs/vendor/removed/vendor_help.out new file mode 100644 index 00000000000000..176b2211af458b --- /dev/null +++ b/tests/specs/vendor/removed/vendor_help.out @@ -0,0 +1,10 @@ +⚠️ `deno vendor` was removed in Deno 2. + +See the Deno 1.x to 2.x Migration Guide for migration instructions: https://docs.deno.com/runtime/manual/advanced/migrate_deprecations + +Usage: deno vendor [OPTIONS] + +Options: + -q, --quiet Suppress diagnostic output + --unstable Enable all unstable features and APIs. Instead of using this flag, consider enabling individual unstable features + To view the list of individual unstable feature flags, run this command again with --help=unstable diff --git a/tests/testdata/vendor/dynamic.ts b/tests/testdata/vendor/dynamic.ts deleted file mode 100644 index e2cbb0e59e94ab..00000000000000 --- a/tests/testdata/vendor/dynamic.ts +++ /dev/null @@ -1,3 +0,0 @@ -const { Logger } = await import("./logger.ts"); - -export { Logger }; diff --git a/tests/testdata/vendor/dynamic_non_analyzable.ts b/tests/testdata/vendor/dynamic_non_analyzable.ts deleted file mode 100644 index 1847939f647c51..00000000000000 --- a/tests/testdata/vendor/dynamic_non_analyzable.ts +++ /dev/null @@ -1,4 +0,0 @@ -const value = (() => "./logger.ts")(); -const { Logger } = await import(value); - -export { Logger }; diff --git a/tests/testdata/vendor/dynamic_non_existent.ts b/tests/testdata/vendor/dynamic_non_existent.ts deleted file mode 100644 index a48e2accb0669d..00000000000000 --- a/tests/testdata/vendor/dynamic_non_existent.ts +++ /dev/null @@ -1,11 +0,0 @@ -// this should still vendor -// deno-lint-ignore no-constant-condition -if (false) { - await import("./non-existent.js"); -} - -export class Logger { - log(text: string) { - console.log(text); - } -} diff --git a/tests/testdata/vendor/dynamic_non_existent.ts.out b/tests/testdata/vendor/dynamic_non_existent.ts.out deleted file mode 100644 index 1bbd01f7b49810..00000000000000 --- a/tests/testdata/vendor/dynamic_non_existent.ts.out +++ /dev/null @@ -1,9 +0,0 @@ -⚠️ Warning: `deno vendor` is deprecated and will be removed in Deno 2.0. -Add `"vendor": true` to your `deno.json` or use the `--vendor` flag instead. -Download http://localhost:4545/vendor/dynamic_non_existent.ts -Download http://localhost:4545/vendor/non-existent.js -Ignoring: Dynamic import not found "http://localhost:4545/vendor/non-existent.js". - at http://localhost:4545/vendor/dynamic_non_existent.ts:4:16 -Vendored 1 module into vendor/ directory. - -To use vendored modules, specify the `--import-map vendor/import_map.json` flag when invoking Deno subcommands or add an `"importMap": ""` entry to a deno.json file. diff --git a/tests/testdata/vendor/logger.ts b/tests/testdata/vendor/logger.ts deleted file mode 100644 index 97f603a48ba307..00000000000000 --- a/tests/testdata/vendor/logger.ts +++ /dev/null @@ -1,5 +0,0 @@ -export class Logger { - log(text: string) { - console.log(text); - } -} diff --git a/tests/testdata/vendor/mod.ts b/tests/testdata/vendor/mod.ts deleted file mode 100644 index 8824d1b2ae1182..00000000000000 --- a/tests/testdata/vendor/mod.ts +++ /dev/null @@ -1 +0,0 @@ -export * from "./logger.ts"; diff --git a/tests/testdata/vendor/npm_and_node_specifier.ts b/tests/testdata/vendor/npm_and_node_specifier.ts deleted file mode 100644 index 61962e836be977..00000000000000 --- a/tests/testdata/vendor/npm_and_node_specifier.ts +++ /dev/null @@ -1,2 +0,0 @@ -export { default as path } from "node:path"; -export { getValue, setValue } from "npm:@denotest/esm-basic"; diff --git a/tests/testdata/vendor/query_reexport.ts b/tests/testdata/vendor/query_reexport.ts deleted file mode 100644 index 5dfafb53293f7f..00000000000000 --- a/tests/testdata/vendor/query_reexport.ts +++ /dev/null @@ -1 +0,0 @@ -export * from "./logger.ts?test";