mirror of
https://github.com/denoland/deno.git
synced 2024-12-22 07:14:47 -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>,
|
||||
}
|
||||
|
||||
#[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)]
|
||||
pub struct PublishFlags {
|
||||
pub token: Option<String>,
|
||||
|
@ -463,7 +456,7 @@ pub enum DenoSubcommand {
|
|||
Test(TestFlags),
|
||||
Types,
|
||||
Upgrade(UpgradeFlags),
|
||||
Vendor(VendorFlags),
|
||||
Vendor,
|
||||
Publish(PublishFlags),
|
||||
Help(HelpFlags),
|
||||
}
|
||||
|
@ -3008,58 +3001,14 @@ update to a different location, use the --output flag:
|
|||
})
|
||||
}
|
||||
|
||||
// TODO(bartlomieju): this subcommand is now deprecated, remove it in Deno 2.
|
||||
fn vendor_subcommand() -> Command {
|
||||
command("vendor",
|
||||
"⚠️ Warning: `deno vendor` is deprecated and will be removed in Deno 2.0.
|
||||
Add `\"vendor\": true` to your `deno.json` or use the `--vendor` flag instead.
|
||||
"⚠️ `deno vendor` was removed in Deno 2.
|
||||
|
||||
Vendor remote modules into a local directory.
|
||||
|
||||
Analyzes the provided modules along with their dependencies, downloads
|
||||
remote modules to the output directory, and produces an import map that
|
||||
maps remote specifiers to the downloaded files.
|
||||
deno vendor main.ts
|
||||
deno run --import-map vendor/import_map.json main.ts
|
||||
|
||||
Remote modules and multiple modules may also be specified:
|
||||
deno vendor main.ts test.deps.ts jsr:@std/path",
|
||||
See the Deno 1.x to 2.x Migration Guide for migration instructions: https://docs.deno.com/runtime/manual/advanced/migrate_deprecations",
|
||||
UnstableArgsConfig::ResolutionOnly
|
||||
)
|
||||
.hide(true)
|
||||
.defer(|cmd| cmd
|
||||
.arg(
|
||||
Arg::new("specifiers")
|
||||
.num_args(1..)
|
||||
.action(ArgAction::Append)
|
||||
.required_unless_present("help"),
|
||||
)
|
||||
.arg(
|
||||
Arg::new("output")
|
||||
.long("output")
|
||||
.help("The directory to output the vendored modules to")
|
||||
.value_parser(value_parser!(String))
|
||||
.value_hint(ValueHint::DirPath),
|
||||
)
|
||||
.arg(
|
||||
Arg::new("force")
|
||||
.long("force")
|
||||
.short('f')
|
||||
.help(
|
||||
"Forcefully overwrite conflicting files in existing output directory",
|
||||
)
|
||||
.action(ArgAction::SetTrue),
|
||||
)
|
||||
.arg(no_config_arg())
|
||||
.arg(config_arg())
|
||||
.arg(import_map_arg())
|
||||
.arg(lock_arg())
|
||||
.arg(node_modules_dir_arg())
|
||||
.arg(vendor_arg())
|
||||
.arg(reload_arg())
|
||||
.arg(ca_file_arg())
|
||||
.arg(unsafely_ignore_certificate_errors_arg())
|
||||
)
|
||||
}
|
||||
|
||||
fn publish_subcommand() -> Command {
|
||||
|
@ -4751,24 +4700,8 @@ fn upgrade_parse(flags: &mut Flags, matches: &mut ArgMatches) {
|
|||
});
|
||||
}
|
||||
|
||||
fn vendor_parse(flags: &mut Flags, matches: &mut ArgMatches) {
|
||||
unstable_args_parse(flags, matches, UnstableArgsConfig::ResolutionOnly);
|
||||
ca_file_arg_parse(flags, matches);
|
||||
unsafely_ignore_certificate_errors_parse(flags, matches);
|
||||
config_args_parse(flags, matches);
|
||||
import_map_arg_parse(flags, matches);
|
||||
lock_arg_parse(flags, matches);
|
||||
node_modules_and_vendor_dir_arg_parse(flags, matches);
|
||||
reload_arg_parse(flags, matches);
|
||||
|
||||
flags.subcommand = DenoSubcommand::Vendor(VendorFlags {
|
||||
specifiers: matches
|
||||
.remove_many::<String>("specifiers")
|
||||
.map(|p| p.collect())
|
||||
.unwrap_or_default(),
|
||||
output_path: matches.remove_one::<String>("output"),
|
||||
force: matches.get_flag("force"),
|
||||
});
|
||||
fn vendor_parse(flags: &mut Flags, _matches: &mut ArgMatches) {
|
||||
flags.subcommand = DenoSubcommand::Vendor
|
||||
}
|
||||
|
||||
fn publish_parse(flags: &mut Flags, matches: &mut ArgMatches) {
|
||||
|
@ -9671,57 +9604,6 @@ mod tests {
|
|||
assert!(&error_message.contains("--watch[=<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]
|
||||
fn task_subcommand() {
|
||||
let r = flags_from_vec(svec!["deno", "task", "build", "hello", "world",]);
|
||||
|
|
|
@ -1217,11 +1217,6 @@ impl CliOptions {
|
|||
NPM_PROCESS_STATE.is_some()
|
||||
}
|
||||
|
||||
/// Overrides the import map specifier to use.
|
||||
pub fn set_import_map_specifier(&mut self, path: Option<ModuleSpecifier>) {
|
||||
self.overrides.import_map_specifier = Some(path);
|
||||
}
|
||||
|
||||
pub fn has_node_modules_dir(&self) -> bool {
|
||||
self.maybe_node_modules_folder.is_some()
|
||||
}
|
||||
|
@ -1230,21 +1225,6 @@ impl CliOptions {
|
|||
self.maybe_node_modules_folder.as_ref()
|
||||
}
|
||||
|
||||
pub fn with_node_modules_dir_path(&self, path: PathBuf) -> Self {
|
||||
Self {
|
||||
flags: self.flags.clone(),
|
||||
initial_cwd: self.initial_cwd.clone(),
|
||||
maybe_node_modules_folder: Some(path),
|
||||
npmrc: self.npmrc.clone(),
|
||||
maybe_lockfile: self.maybe_lockfile.clone(),
|
||||
start_dir: self.start_dir.clone(),
|
||||
overrides: self.overrides.clone(),
|
||||
disable_deprecated_api_warning: self.disable_deprecated_api_warning,
|
||||
verbose_deprecated_api_warning: self.verbose_deprecated_api_warning,
|
||||
deno_dir_provider: self.deno_dir_provider.clone(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn node_modules_dir(
|
||||
&self,
|
||||
) -> Result<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.",
|
||||
1,
|
||||
),
|
||||
DenoSubcommand::Vendor(vendor_flags) => spawn_subcommand(async {
|
||||
tools::vendor::vendor(flags, vendor_flags).await
|
||||
}),
|
||||
DenoSubcommand::Vendor => exit_with_message("⚠️ `deno vendor` was removed in Deno 2.\n\nSee the Deno 1.x to 2.x Migration Guide for migration instructions: https://docs.deno.com/runtime/manual/advanced/migrate_deprecations", 1),
|
||||
DenoSubcommand::Publish(publish_flags) => spawn_subcommand(async {
|
||||
tools::registry::publish(flags, publish_flags).await
|
||||
}),
|
||||
|
|
|
@ -19,4 +19,3 @@ pub mod serve;
|
|||
pub mod task;
|
||||
pub mod test;
|
||||
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.
|
||||
|
||||
use std::env::current_dir;
|
||||
use std::fs::OpenOptions;
|
||||
use std::io::Error;
|
||||
use std::io::ErrorKind;
|
||||
|
@ -18,7 +17,6 @@ use deno_config::glob::WalkEntry;
|
|||
use deno_core::anyhow::anyhow;
|
||||
use deno_core::anyhow::Context;
|
||||
use deno_core::error::AnyError;
|
||||
pub use deno_core::normalize_path;
|
||||
use deno_core::unsync::spawn_blocking;
|
||||
use deno_core::ModuleSpecifier;
|
||||
use deno_runtime::deno_fs::FileSystem;
|
||||
|
@ -255,18 +253,6 @@ fn canonicalize_path_maybe_not_exists_with_custom_fn(
|
|||
}
|
||||
}
|
||||
|
||||
pub fn resolve_from_cwd(path: &Path) -> Result<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`.
|
||||
/// Specifiers that start with http and https are left intact.
|
||||
/// Note: This ignores all .git and node_modules folders.
|
||||
|
@ -715,30 +701,13 @@ pub fn specifier_from_file_path(
|
|||
mod tests {
|
||||
use super::*;
|
||||
use deno_core::futures;
|
||||
use deno_core::normalize_path;
|
||||
use deno_core::parking_lot::Mutex;
|
||||
use pretty_assertions::assert_eq;
|
||||
use test_util::PathRef;
|
||||
use test_util::TempDir;
|
||||
use tokio::sync::Notify;
|
||||
|
||||
#[test]
|
||||
fn resolve_from_cwd_child() {
|
||||
let cwd = current_dir().unwrap();
|
||||
assert_eq!(resolve_from_cwd(Path::new("a")).unwrap(), cwd.join("a"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn resolve_from_cwd_dot() {
|
||||
let cwd = current_dir().unwrap();
|
||||
assert_eq!(resolve_from_cwd(Path::new(".")).unwrap(), cwd);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn resolve_from_cwd_parent() {
|
||||
let cwd = current_dir().unwrap();
|
||||
assert_eq!(resolve_from_cwd(Path::new("a/..")).unwrap(), cwd);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_normalize_path() {
|
||||
assert_eq!(normalize_path(Path::new("a/../b")), PathBuf::from("b"));
|
||||
|
@ -756,14 +725,6 @@ mod tests {
|
|||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn resolve_from_cwd_absolute() {
|
||||
let expected = Path::new("a");
|
||||
let cwd = current_dir().unwrap();
|
||||
let absolute_expected = cwd.join(expected);
|
||||
assert_eq!(resolve_from_cwd(expected).unwrap(), absolute_expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_collect_specifiers() {
|
||||
fn create_files(dir_path: &PathRef, files: &[&str]) {
|
||||
|
|
|
@ -145,34 +145,6 @@ pub fn relative_specifier(
|
|||
Some(to_percent_decoded_str(&text))
|
||||
}
|
||||
|
||||
/// Gets a path with the specified file stem suffix.
|
||||
///
|
||||
/// Ex. `file.ts` with suffix `_2` returns `file_2.ts`
|
||||
pub fn path_with_stem_suffix(path: &Path, suffix: &str) -> PathBuf {
|
||||
if let Some(file_name) = path.file_name().map(|f| f.to_string_lossy()) {
|
||||
if let Some(file_stem) = path.file_stem().map(|f| f.to_string_lossy()) {
|
||||
if let Some(ext) = path.extension().map(|f| f.to_string_lossy()) {
|
||||
return if file_stem.to_lowercase().ends_with(".d") {
|
||||
path.with_file_name(format!(
|
||||
"{}{}.{}.{}",
|
||||
&file_stem[..file_stem.len() - ".d".len()],
|
||||
suffix,
|
||||
// maintain casing
|
||||
&file_stem[file_stem.len() - "d".len()..],
|
||||
ext
|
||||
))
|
||||
} else {
|
||||
path.with_file_name(format!("{file_stem}{suffix}.{ext}"))
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
path.with_file_name(format!("{file_name}{suffix}"))
|
||||
} else {
|
||||
path.with_file_name(suffix)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg_attr(windows, allow(dead_code))]
|
||||
pub fn relative_path(from: &Path, to: &Path) -> Option<PathBuf> {
|
||||
pathdiff::diff_paths(to, from)
|
||||
|
@ -405,46 +377,6 @@ mod test {
|
|||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_path_with_stem_suffix() {
|
||||
assert_eq!(
|
||||
path_with_stem_suffix(&PathBuf::from("/"), "_2"),
|
||||
PathBuf::from("/_2")
|
||||
);
|
||||
assert_eq!(
|
||||
path_with_stem_suffix(&PathBuf::from("/test"), "_2"),
|
||||
PathBuf::from("/test_2")
|
||||
);
|
||||
assert_eq!(
|
||||
path_with_stem_suffix(&PathBuf::from("/test.txt"), "_2"),
|
||||
PathBuf::from("/test_2.txt")
|
||||
);
|
||||
assert_eq!(
|
||||
path_with_stem_suffix(&PathBuf::from("/test/subdir"), "_2"),
|
||||
PathBuf::from("/test/subdir_2")
|
||||
);
|
||||
assert_eq!(
|
||||
path_with_stem_suffix(&PathBuf::from("/test/subdir.other.txt"), "_2"),
|
||||
PathBuf::from("/test/subdir.other_2.txt")
|
||||
);
|
||||
assert_eq!(
|
||||
path_with_stem_suffix(&PathBuf::from("/test.d.ts"), "_2"),
|
||||
PathBuf::from("/test_2.d.ts")
|
||||
);
|
||||
assert_eq!(
|
||||
path_with_stem_suffix(&PathBuf::from("/test.D.TS"), "_2"),
|
||||
PathBuf::from("/test_2.D.TS")
|
||||
);
|
||||
assert_eq!(
|
||||
path_with_stem_suffix(&PathBuf::from("/test.d.mts"), "_2"),
|
||||
PathBuf::from("/test_2.d.mts")
|
||||
);
|
||||
assert_eq!(
|
||||
path_with_stem_suffix(&PathBuf::from("/test.d.cts"), "_2"),
|
||||
PathBuf::from("/test_2.d.cts")
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_to_percent_decoded_str() {
|
||||
let str = to_percent_decoded_str("%F0%9F%A6%95");
|
||||
|
|
|
@ -68,8 +68,6 @@ mod task;
|
|||
mod test;
|
||||
#[path = "upgrade_tests.rs"]
|
||||
mod upgrade;
|
||||
#[path = "vendor_tests.rs"]
|
||||
mod vendor;
|
||||
#[path = "watcher_tests.rs"]
|
||||
mod watcher;
|
||||
#[path = "worker_tests.rs"]
|
||||
|
|
|
@ -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