mirror of
https://github.com/denoland/deno.git
synced 2024-11-21 15:04:11 -05:00
BREAKING: remove deno vendor
(#25343)
This commit is contained in:
parent
2533d68cab
commit
5f08d634f7
26 changed files with 33 additions and 4393 deletions
|
@ -412,13 +412,6 @@ pub struct UpgradeFlags {
|
||||||
pub version_or_hash_or_channel: Option<String>,
|
pub version_or_hash_or_channel: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, Eq, PartialEq)]
|
|
||||||
pub struct VendorFlags {
|
|
||||||
pub specifiers: Vec<String>,
|
|
||||||
pub output_path: Option<String>,
|
|
||||||
pub force: bool,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Debug, Eq, PartialEq)]
|
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||||
pub struct PublishFlags {
|
pub struct PublishFlags {
|
||||||
pub token: Option<String>,
|
pub token: Option<String>,
|
||||||
|
@ -463,7 +456,7 @@ pub enum DenoSubcommand {
|
||||||
Test(TestFlags),
|
Test(TestFlags),
|
||||||
Types,
|
Types,
|
||||||
Upgrade(UpgradeFlags),
|
Upgrade(UpgradeFlags),
|
||||||
Vendor(VendorFlags),
|
Vendor,
|
||||||
Publish(PublishFlags),
|
Publish(PublishFlags),
|
||||||
Help(HelpFlags),
|
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 {
|
fn vendor_subcommand() -> Command {
|
||||||
command("vendor",
|
command("vendor",
|
||||||
"⚠️ Warning: `deno vendor` is deprecated and will be removed in Deno 2.0.
|
"⚠️ `deno vendor` was removed in Deno 2.
|
||||||
Add `\"vendor\": true` to your `deno.json` or use the `--vendor` flag instead.
|
|
||||||
|
|
||||||
Vendor remote modules into a local directory.
|
See the Deno 1.x to 2.x Migration Guide for migration instructions: https://docs.deno.com/runtime/manual/advanced/migrate_deprecations",
|
||||||
|
|
||||||
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",
|
|
||||||
UnstableArgsConfig::ResolutionOnly
|
UnstableArgsConfig::ResolutionOnly
|
||||||
)
|
)
|
||||||
.hide(true)
|
.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 {
|
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) {
|
fn vendor_parse(flags: &mut Flags, _matches: &mut ArgMatches) {
|
||||||
unstable_args_parse(flags, matches, UnstableArgsConfig::ResolutionOnly);
|
flags.subcommand = DenoSubcommand::Vendor
|
||||||
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::<String>("specifiers")
|
|
||||||
.map(|p| p.collect())
|
|
||||||
.unwrap_or_default(),
|
|
||||||
output_path: matches.remove_one::<String>("output"),
|
|
||||||
force: matches.get_flag("force"),
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn publish_parse(flags: &mut Flags, matches: &mut ArgMatches) {
|
fn publish_parse(flags: &mut Flags, matches: &mut ArgMatches) {
|
||||||
|
@ -9671,57 +9604,6 @@ mod tests {
|
||||||
assert!(&error_message.contains("--watch[=<FILES>...]"));
|
assert!(&error_message.contains("--watch[=<FILES>...]"));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[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]
|
#[test]
|
||||||
fn task_subcommand() {
|
fn task_subcommand() {
|
||||||
let r = flags_from_vec(svec!["deno", "task", "build", "hello", "world",]);
|
let r = flags_from_vec(svec!["deno", "task", "build", "hello", "world",]);
|
||||||
|
|
|
@ -1217,11 +1217,6 @@ impl CliOptions {
|
||||||
NPM_PROCESS_STATE.is_some()
|
NPM_PROCESS_STATE.is_some()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Overrides the import map specifier to use.
|
|
||||||
pub fn set_import_map_specifier(&mut self, path: Option<ModuleSpecifier>) {
|
|
||||||
self.overrides.import_map_specifier = Some(path);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn has_node_modules_dir(&self) -> bool {
|
pub fn has_node_modules_dir(&self) -> bool {
|
||||||
self.maybe_node_modules_folder.is_some()
|
self.maybe_node_modules_folder.is_some()
|
||||||
}
|
}
|
||||||
|
@ -1230,21 +1225,6 @@ impl CliOptions {
|
||||||
self.maybe_node_modules_folder.as_ref()
|
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(
|
pub fn node_modules_dir(
|
||||||
&self,
|
&self,
|
||||||
) -> Result<Option<NodeModulesDirMode>, AnyError> {
|
) -> Result<Option<NodeModulesDirMode>, AnyError> {
|
||||||
|
|
|
@ -283,9 +283,7 @@ async fn run_subcommand(flags: Arc<Flags>) -> Result<i32, AnyError> {
|
||||||
"This deno was built without the \"upgrade\" feature. Please upgrade using the installation method originally used to install Deno.",
|
"This deno was built without the \"upgrade\" feature. Please upgrade using the installation method originally used to install Deno.",
|
||||||
1,
|
1,
|
||||||
),
|
),
|
||||||
DenoSubcommand::Vendor(vendor_flags) => spawn_subcommand(async {
|
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),
|
||||||
tools::vendor::vendor(flags, vendor_flags).await
|
|
||||||
}),
|
|
||||||
DenoSubcommand::Publish(publish_flags) => spawn_subcommand(async {
|
DenoSubcommand::Publish(publish_flags) => spawn_subcommand(async {
|
||||||
tools::registry::publish(flags, publish_flags).await
|
tools::registry::publish(flags, publish_flags).await
|
||||||
}),
|
}),
|
||||||
|
|
|
@ -19,4 +19,3 @@ pub mod serve;
|
||||||
pub mod task;
|
pub mod task;
|
||||||
pub mod test;
|
pub mod test;
|
||||||
pub mod upgrade;
|
pub mod upgrade;
|
||||||
pub mod vendor;
|
|
||||||
|
|
113
cli/tools/vendor/analyze.rs
vendored
113
cli/tools/vendor/analyze.rs
vendored
|
@ -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()
|
|
||||||
}
|
|
||||||
}
|
|
1330
cli/tools/vendor/build.rs
vendored
1330
cli/tools/vendor/build.rs
vendored
File diff suppressed because it is too large
Load diff
508
cli/tools/vendor/import_map.rs
vendored
508
cli/tools/vendor/import_map.rs
vendored
|
@ -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<String, ImportsBuilder<'a>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
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<String, String>,
|
|
||||||
}
|
|
||||||
|
|
||||||
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<String, AnyError> {
|
|
||||||
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<usize> {
|
|
||||||
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
|
|
||||||
}
|
|
255
cli/tools/vendor/mappings.rs
vendored
255
cli/tools/vendor/mappings.rs
vendored
|
@ -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<ModuleSpecifier, PathBuf>,
|
|
||||||
base_specifiers: Vec<ModuleSpecifier>,
|
|
||||||
proxies: HashMap<ModuleSpecifier, ProxiedModule>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Mappings {
|
|
||||||
pub fn from_remote_modules(
|
|
||||||
graph: &ModuleGraph,
|
|
||||||
remote_modules: &[&Module],
|
|
||||||
output_dir: &Path,
|
|
||||||
) -> Result<Self, AnyError> {
|
|
||||||
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<ModuleSpecifier> {
|
|
||||||
&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<PathBuf> {
|
|
||||||
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")
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
578
cli/tools/vendor/mod.rs
vendored
578
cli/tools/vendor/mod.rs
vendored
|
@ -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<Flags>,
|
|
||||||
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 <other-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": "<path_to_vendored_import_map>"` "#,
|
|
||||||
"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<ConfigFile>>,
|
|
||||||
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<ModuleSpecifier>,
|
|
||||||
try_add_node_modules_dir: bool,
|
|
||||||
) -> Result<ModifiedResult, AnyError> {
|
|
||||||
let config_path = specifier_to_file_path(&config_file.specifier)?;
|
|
||||||
let config_text = std::fs::read_to_string(&config_path)?;
|
|
||||||
let 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<String>,
|
|
||||||
}
|
|
||||||
|
|
||||||
fn update_config_text(
|
|
||||||
text: &str,
|
|
||||||
fmt_options: &FmtOptionsConfig,
|
|
||||||
import_map_specifier: Option<&str>,
|
|
||||||
try_add_node_modules_dir: bool,
|
|
||||||
) -> Result<ModifiedResult, AnyError> {
|
|
||||||
use jsonc_parser::ast::ObjectProp;
|
|
||||||
use jsonc_parser::ast::Value;
|
|
||||||
let 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<bool, AnyError> {
|
|
||||||
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<Vec<ModuleSpecifier>, AnyError> {
|
|
||||||
flags
|
|
||||||
.specifiers
|
|
||||||
.iter()
|
|
||||||
.map(|p| resolve_url_or_path(p, initial_cwd).map_err(|e| e.into()))
|
|
||||||
.collect::<Result<Vec<_>, _>>()
|
|
||||||
}
|
|
||||||
|
|
||||||
#[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);
|
|
||||||
}
|
|
||||||
}
|
|
208
cli/tools/vendor/specifiers.rs
vendored
208
cli/tools/vendor/specifiers.rs
vendored
|
@ -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<Item = &'a ModuleSpecifier>,
|
|
||||||
) -> BTreeMap<ModuleSpecifier, Vec<ModuleSpecifier>> {
|
|
||||||
let mut root_specifiers: BTreeMap<ModuleSpecifier, Vec<ModuleSpecifier>> =
|
|
||||||
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<String>,
|
|
||||||
) -> 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<String, AnyError> {
|
|
||||||
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::<Vec<_>>();
|
|
||||||
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::<Vec<_>>(),
|
|
||||||
)
|
|
||||||
})
|
|
||||||
.collect::<Vec<_>>();
|
|
||||||
let expected = expected
|
|
||||||
.into_iter()
|
|
||||||
.map(|(s, vec)| {
|
|
||||||
(
|
|
||||||
s.to_string(),
|
|
||||||
vec.into_iter().map(|s| s.to_string()).collect::<Vec<_>>(),
|
|
||||||
)
|
|
||||||
})
|
|
||||||
.collect::<Vec<_>>();
|
|
||||||
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")
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
357
cli/tools/vendor/test.rs
vendored
357
cli/tools/vendor/test.rs
vendored
|
@ -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<HashMap<String, String>>;
|
|
||||||
type RemoteFileResult = Result<(RemoteFileText, RemoteFileHeaders), String>;
|
|
||||||
|
|
||||||
#[derive(Clone, Default)]
|
|
||||||
pub struct TestLoader {
|
|
||||||
files: HashMap<ModuleSpecifier, RemoteFileResult>,
|
|
||||||
redirects: HashMap<ModuleSpecifier, ModuleSpecifier>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl TestLoader {
|
|
||||||
pub fn add(
|
|
||||||
&mut self,
|
|
||||||
path_or_specifier: impl AsRef<str>,
|
|
||||||
text: impl AsRef<str>,
|
|
||||||
) -> &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<str>,
|
|
||||||
message: impl AsRef<str>,
|
|
||||||
) -> &mut Self {
|
|
||||||
self.add_result(path_or_specifier, Err(message.as_ref().to_string()))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn add_result(
|
|
||||||
&mut self,
|
|
||||||
path_or_specifier: impl AsRef<str>,
|
|
||||||
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<str>,
|
|
||||||
text: impl AsRef<str>,
|
|
||||||
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<str>,
|
|
||||||
to: impl AsRef<str>,
|
|
||||||
) -> &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<HashSet<PathBuf>>,
|
|
||||||
files: RefCell<HashMap<PathBuf, String>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
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<serde_json::Value>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Default)]
|
|
||||||
pub struct VendorTestBuilder {
|
|
||||||
entry_points: Vec<ModuleSpecifier>,
|
|
||||||
loader: TestLoader,
|
|
||||||
maybe_original_import_map: Option<ImportMap>,
|
|
||||||
environment: TestVendorEnvironment,
|
|
||||||
jsx_import_source_config: Option<JsxImportSourceConfig>,
|
|
||||||
}
|
|
||||||
|
|
||||||
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<str>) -> &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<VendorOutput, AnyError> {
|
|
||||||
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::<Vec<_>>();
|
|
||||||
|
|
||||||
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<JsxImportSourceConfig>,
|
|
||||||
maybe_original_import_map: Option<ImportMap>,
|
|
||||||
) -> 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<ModuleSpecifier>,
|
|
||||||
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<P>(path: P) -> String
|
|
||||||
where
|
|
||||||
P: AsRef<Path>,
|
|
||||||
{
|
|
||||||
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()
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,6 +1,5 @@
|
||||||
// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license.
|
// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license.
|
||||||
|
|
||||||
use std::env::current_dir;
|
|
||||||
use std::fs::OpenOptions;
|
use std::fs::OpenOptions;
|
||||||
use std::io::Error;
|
use std::io::Error;
|
||||||
use std::io::ErrorKind;
|
use std::io::ErrorKind;
|
||||||
|
@ -18,7 +17,6 @@ use deno_config::glob::WalkEntry;
|
||||||
use deno_core::anyhow::anyhow;
|
use deno_core::anyhow::anyhow;
|
||||||
use deno_core::anyhow::Context;
|
use deno_core::anyhow::Context;
|
||||||
use deno_core::error::AnyError;
|
use deno_core::error::AnyError;
|
||||||
pub use deno_core::normalize_path;
|
|
||||||
use deno_core::unsync::spawn_blocking;
|
use deno_core::unsync::spawn_blocking;
|
||||||
use deno_core::ModuleSpecifier;
|
use deno_core::ModuleSpecifier;
|
||||||
use deno_runtime::deno_fs::FileSystem;
|
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<PathBuf, AnyError> {
|
|
||||||
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`.
|
/// 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.
|
/// Specifiers that start with http and https are left intact.
|
||||||
/// Note: This ignores all .git and node_modules folders.
|
/// Note: This ignores all .git and node_modules folders.
|
||||||
|
@ -715,30 +701,13 @@ pub fn specifier_from_file_path(
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
use deno_core::futures;
|
use deno_core::futures;
|
||||||
|
use deno_core::normalize_path;
|
||||||
use deno_core::parking_lot::Mutex;
|
use deno_core::parking_lot::Mutex;
|
||||||
use pretty_assertions::assert_eq;
|
use pretty_assertions::assert_eq;
|
||||||
use test_util::PathRef;
|
use test_util::PathRef;
|
||||||
use test_util::TempDir;
|
use test_util::TempDir;
|
||||||
use tokio::sync::Notify;
|
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]
|
#[test]
|
||||||
fn test_normalize_path() {
|
fn test_normalize_path() {
|
||||||
assert_eq!(normalize_path(Path::new("a/../b")), PathBuf::from("b"));
|
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]
|
#[test]
|
||||||
fn test_collect_specifiers() {
|
fn test_collect_specifiers() {
|
||||||
fn create_files(dir_path: &PathRef, files: &[&str]) {
|
fn create_files(dir_path: &PathRef, files: &[&str]) {
|
||||||
|
|
|
@ -145,34 +145,6 @@ pub fn relative_specifier(
|
||||||
Some(to_percent_decoded_str(&text))
|
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))]
|
#[cfg_attr(windows, allow(dead_code))]
|
||||||
pub fn relative_path(from: &Path, to: &Path) -> Option<PathBuf> {
|
pub fn relative_path(from: &Path, to: &Path) -> Option<PathBuf> {
|
||||||
pathdiff::diff_paths(to, from)
|
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]
|
#[test]
|
||||||
fn test_to_percent_decoded_str() {
|
fn test_to_percent_decoded_str() {
|
||||||
let str = to_percent_decoded_str("%F0%9F%A6%95");
|
let str = to_percent_decoded_str("%F0%9F%A6%95");
|
||||||
|
|
|
@ -68,8 +68,6 @@ mod task;
|
||||||
mod test;
|
mod test;
|
||||||
#[path = "upgrade_tests.rs"]
|
#[path = "upgrade_tests.rs"]
|
||||||
mod upgrade;
|
mod upgrade;
|
||||||
#[path = "vendor_tests.rs"]
|
|
||||||
mod vendor;
|
|
||||||
#[path = "watcher_tests.rs"]
|
#[path = "watcher_tests.rs"]
|
||||||
mod watcher;
|
mod watcher;
|
||||||
#[path = "worker_tests.rs"]
|
#[path = "worker_tests.rs"]
|
||||||
|
|
|
@ -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": "<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(),
|
|
||||||
)
|
|
||||||
}
|
|
13
tests/specs/vendor/removed/__test__.jsonc
vendored
Normal file
13
tests/specs/vendor/removed/__test__.jsonc
vendored
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
{
|
||||||
|
"steps": [
|
||||||
|
{
|
||||||
|
"args": "vendor",
|
||||||
|
"output": "vendor.out",
|
||||||
|
"exitCode": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"args": "vendor --help",
|
||||||
|
"output": "vendor_help.out"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
3
tests/specs/vendor/removed/vendor.out
vendored
Normal file
3
tests/specs/vendor/removed/vendor.out
vendored
Normal file
|
@ -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
|
10
tests/specs/vendor/removed/vendor_help.out
vendored
Normal file
10
tests/specs/vendor/removed/vendor_help.out
vendored
Normal file
|
@ -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
|
3
tests/testdata/vendor/dynamic.ts
vendored
3
tests/testdata/vendor/dynamic.ts
vendored
|
@ -1,3 +0,0 @@
|
||||||
const { Logger } = await import("./logger.ts");
|
|
||||||
|
|
||||||
export { Logger };
|
|
|
@ -1,4 +0,0 @@
|
||||||
const value = (() => "./logger.ts")();
|
|
||||||
const { Logger } = await import(value);
|
|
||||||
|
|
||||||
export { Logger };
|
|
11
tests/testdata/vendor/dynamic_non_existent.ts
vendored
11
tests/testdata/vendor/dynamic_non_existent.ts
vendored
|
@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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": "<path_to_vendored_import_map>"` entry to a deno.json file.
|
|
5
tests/testdata/vendor/logger.ts
vendored
5
tests/testdata/vendor/logger.ts
vendored
|
@ -1,5 +0,0 @@
|
||||||
export class Logger {
|
|
||||||
log(text: string) {
|
|
||||||
console.log(text);
|
|
||||||
}
|
|
||||||
}
|
|
1
tests/testdata/vendor/mod.ts
vendored
1
tests/testdata/vendor/mod.ts
vendored
|
@ -1 +0,0 @@
|
||||||
export * from "./logger.ts";
|
|
|
@ -1,2 +0,0 @@
|
||||||
export { default as path } from "node:path";
|
|
||||||
export { getValue, setValue } from "npm:@denotest/esm-basic";
|
|
1
tests/testdata/vendor/query_reexport.ts
vendored
1
tests/testdata/vendor/query_reexport.ts
vendored
|
@ -1 +0,0 @@
|
||||||
export * from "./logger.ts?test";
|
|
Loading…
Reference in a new issue