diff options
Diffstat (limited to 'tests/integration/vendor_tests.rs')
-rw-r--r-- | tests/integration/vendor_tests.rs | 704 |
1 files changed, 704 insertions, 0 deletions
diff --git a/tests/integration/vendor_tests.rs b/tests/integration/vendor_tests.rs new file mode 100644 index 000000000..c38fb653a --- /dev/null +++ b/tests/integration/vendor_tests.rs @@ -0,0 +1,704 @@ +// 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::TempDir; +use util::http_server; +use util::new_deno_dir; +use util::TestContextBuilder; + +#[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(), + concat!( + "error: Output directory was not empty. Please specify an empty ", + "directory or use --force to ignore this error and potentially ", + "overwrite its contents.", + ), + ); + 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(), + concat!( + "error: Output directory was not empty. Please specify an empty ", + "directory or use --force to ignore this error and potentially ", + "overwrite its contents.", + ), + ); + 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", + "{}", + ), + 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{}", + ), + 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", + "{}", + ), + 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(), + 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!( + concat!("Download http://localhost:4545/vendor/mod.ts\n", "{}",), + 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{}", + 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{}", + 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 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!( + "Download http://localhost:4545/vendor/npm_and_node_specifier.ts\n", + "Download http://localhost:4545/npm/registry/@denotest/esm-basic\n", + "Download http://localhost:4545/npm/registry/@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 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", + 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", + 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", + 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!( + "Download http://localhost:4545/npm/registry/@denotest/esm-basic\n", + "Download http://localhost:4545/npm/registry/@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": "<path_to_vendored_import_map>"` "#, + "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 <other-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(), + ) +} |