1
0
Fork 0
mirror of https://github.com/denoland/deno.git synced 2024-11-21 15:04:11 -05:00

feat(publish): support sloppy imports and bare node built-ins (#22588)

This commit is contained in:
Luca Casonato 2024-02-27 16:13:16 +01:00 committed by GitHub
parent 47c2a63d87
commit 8d5c231349
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
17 changed files with 316 additions and 130 deletions

View file

@ -44,10 +44,10 @@ use crate::resolver::SloppyImportsResolver;
use crate::standalone::DenoCompileBinaryWriter; use crate::standalone::DenoCompileBinaryWriter;
use crate::tools::check::TypeChecker; use crate::tools::check::TypeChecker;
use crate::tools::coverage::CoverageCollector; use crate::tools::coverage::CoverageCollector;
use crate::tools::registry::deno_json_deps;
use crate::tools::run::hmr::HmrRunner; use crate::tools::run::hmr::HmrRunner;
use crate::util::file_watcher::WatcherCommunicator; use crate::util::file_watcher::WatcherCommunicator;
use crate::util::fs::canonicalize_path_maybe_not_exists; use crate::util::fs::canonicalize_path_maybe_not_exists;
use crate::util::import_map::deno_json_deps;
use crate::util::progress_bar::ProgressBar; use crate::util::progress_bar::ProgressBar;
use crate::util::progress_bar::ProgressBarStyle; use crate::util::progress_bar::ProgressBarStyle;
use crate::worker::CliMainWorkerFactory; use crate::worker::CliMainWorkerFactory;

View file

@ -20,7 +20,7 @@ use deno_core::error::AnyError;
use deno_graph::FastCheckDiagnostic; use deno_graph::FastCheckDiagnostic;
use lsp_types::Url; use lsp_types::Url;
use crate::util::import_map::ImportMapUnfurlDiagnostic; use super::unfurl::SpecifierUnfurlerDiagnostic;
#[derive(Clone, Default)] #[derive(Clone, Default)]
pub struct PublishDiagnosticsCollector { pub struct PublishDiagnosticsCollector {
@ -74,7 +74,7 @@ impl PublishDiagnosticsCollector {
pub enum PublishDiagnostic { pub enum PublishDiagnostic {
FastCheck(FastCheckDiagnostic), FastCheck(FastCheckDiagnostic),
ImportMapUnfurl(ImportMapUnfurlDiagnostic), SpecifierUnfurl(SpecifierUnfurlerDiagnostic),
InvalidPath { InvalidPath {
path: PathBuf, path: PathBuf,
message: String, message: String,
@ -102,7 +102,7 @@ impl Diagnostic for PublishDiagnostic {
.. ..
}) => DiagnosticLevel::Warning, }) => DiagnosticLevel::Warning,
FastCheck(_) => DiagnosticLevel::Error, FastCheck(_) => DiagnosticLevel::Error,
ImportMapUnfurl(_) => DiagnosticLevel::Warning, SpecifierUnfurl(_) => DiagnosticLevel::Warning,
InvalidPath { .. } => DiagnosticLevel::Error, InvalidPath { .. } => DiagnosticLevel::Error,
DuplicatePath { .. } => DiagnosticLevel::Error, DuplicatePath { .. } => DiagnosticLevel::Error,
UnsupportedFileType { .. } => DiagnosticLevel::Warning, UnsupportedFileType { .. } => DiagnosticLevel::Warning,
@ -114,7 +114,7 @@ impl Diagnostic for PublishDiagnostic {
use PublishDiagnostic::*; use PublishDiagnostic::*;
match &self { match &self {
FastCheck(diagnostic) => diagnostic.code(), FastCheck(diagnostic) => diagnostic.code(),
ImportMapUnfurl(diagnostic) => Cow::Borrowed(diagnostic.code()), SpecifierUnfurl(diagnostic) => Cow::Borrowed(diagnostic.code()),
InvalidPath { .. } => Cow::Borrowed("invalid-path"), InvalidPath { .. } => Cow::Borrowed("invalid-path"),
DuplicatePath { .. } => Cow::Borrowed("case-insensitive-duplicate-path"), DuplicatePath { .. } => Cow::Borrowed("case-insensitive-duplicate-path"),
UnsupportedFileType { .. } => Cow::Borrowed("unsupported-file-type"), UnsupportedFileType { .. } => Cow::Borrowed("unsupported-file-type"),
@ -126,7 +126,7 @@ impl Diagnostic for PublishDiagnostic {
use PublishDiagnostic::*; use PublishDiagnostic::*;
match &self { match &self {
FastCheck(diagnostic) => diagnostic.message(), FastCheck(diagnostic) => diagnostic.message(),
ImportMapUnfurl(diagnostic) => Cow::Borrowed(diagnostic.message()), SpecifierUnfurl(diagnostic) => Cow::Borrowed(diagnostic.message()),
InvalidPath { message, .. } => Cow::Borrowed(message.as_str()), InvalidPath { message, .. } => Cow::Borrowed(message.as_str()),
DuplicatePath { .. } => { DuplicatePath { .. } => {
Cow::Borrowed("package path is a case insensitive duplicate of another path in the package") Cow::Borrowed("package path is a case insensitive duplicate of another path in the package")
@ -142,8 +142,8 @@ impl Diagnostic for PublishDiagnostic {
use PublishDiagnostic::*; use PublishDiagnostic::*;
match &self { match &self {
FastCheck(diagnostic) => diagnostic.location(), FastCheck(diagnostic) => diagnostic.location(),
ImportMapUnfurl(diagnostic) => match diagnostic { SpecifierUnfurl(diagnostic) => match diagnostic {
ImportMapUnfurlDiagnostic::UnanalyzableDynamicImport { SpecifierUnfurlerDiagnostic::UnanalyzableDynamicImport {
specifier, specifier,
text_info, text_info,
range, range,
@ -180,8 +180,8 @@ impl Diagnostic for PublishDiagnostic {
fn snippet(&self) -> Option<DiagnosticSnippet<'_>> { fn snippet(&self) -> Option<DiagnosticSnippet<'_>> {
match &self { match &self {
PublishDiagnostic::FastCheck(diagnostic) => diagnostic.snippet(), PublishDiagnostic::FastCheck(diagnostic) => diagnostic.snippet(),
PublishDiagnostic::ImportMapUnfurl(diagnostic) => match diagnostic { PublishDiagnostic::SpecifierUnfurl(diagnostic) => match diagnostic {
ImportMapUnfurlDiagnostic::UnanalyzableDynamicImport { SpecifierUnfurlerDiagnostic::UnanalyzableDynamicImport {
text_info, text_info,
range, range,
.. ..
@ -227,7 +227,7 @@ impl Diagnostic for PublishDiagnostic {
fn hint(&self) -> Option<Cow<'_, str>> { fn hint(&self) -> Option<Cow<'_, str>> {
match &self { match &self {
PublishDiagnostic::FastCheck(diagnostic) => diagnostic.hint(), PublishDiagnostic::FastCheck(diagnostic) => diagnostic.hint(),
PublishDiagnostic::ImportMapUnfurl(_) => None, PublishDiagnostic::SpecifierUnfurl(_) => None,
PublishDiagnostic::InvalidPath { .. } => Some( PublishDiagnostic::InvalidPath { .. } => Some(
Cow::Borrowed("rename or remove the file, or add it to 'publish.exclude' in the config file"), Cow::Borrowed("rename or remove the file, or add it to 'publish.exclude' in the config file"),
), ),
@ -250,11 +250,11 @@ impl Diagnostic for PublishDiagnostic {
PublishDiagnostic::FastCheck(diagnostic) => { PublishDiagnostic::FastCheck(diagnostic) => {
diagnostic.info() diagnostic.info()
} }
PublishDiagnostic::ImportMapUnfurl(diagnostic) => match diagnostic { PublishDiagnostic::SpecifierUnfurl(diagnostic) => match diagnostic {
ImportMapUnfurlDiagnostic::UnanalyzableDynamicImport { .. } => Cow::Borrowed(&[ SpecifierUnfurlerDiagnostic::UnanalyzableDynamicImport { .. } => Cow::Borrowed(&[
Cow::Borrowed("after publishing this package, imports from the local import map do not work"), Cow::Borrowed("after publishing this package, imports from the local import map / package.json do not work"),
Cow::Borrowed("dynamic imports that can not be analyzed at publish time will not be rewritten automatically"), Cow::Borrowed("dynamic imports that can not be analyzed at publish time will not be rewritten automatically"),
Cow::Borrowed("make sure the dynamic import is resolvable at runtime without an import map") Cow::Borrowed("make sure the dynamic import is resolvable at runtime without an import map / package.json")
]), ]),
}, },
PublishDiagnostic::InvalidPath { .. } => Cow::Borrowed(&[ PublishDiagnostic::InvalidPath { .. } => Cow::Borrowed(&[
@ -278,8 +278,8 @@ impl Diagnostic for PublishDiagnostic {
fn docs_url(&self) -> Option<Cow<'_, str>> { fn docs_url(&self) -> Option<Cow<'_, str>> {
match &self { match &self {
PublishDiagnostic::FastCheck(diagnostic) => diagnostic.docs_url(), PublishDiagnostic::FastCheck(diagnostic) => diagnostic.docs_url(),
PublishDiagnostic::ImportMapUnfurl(diagnostic) => match diagnostic { PublishDiagnostic::SpecifierUnfurl(diagnostic) => match diagnostic {
ImportMapUnfurlDiagnostic::UnanalyzableDynamicImport { .. } => None, SpecifierUnfurlerDiagnostic::UnanalyzableDynamicImport { .. } => None,
}, },
PublishDiagnostic::InvalidPath { .. } => { PublishDiagnostic::InvalidPath { .. } => {
Some(Cow::Borrowed("https://jsr.io/go/invalid-path")) Some(Cow::Borrowed("https://jsr.io/go/invalid-path"))

View file

@ -35,13 +35,13 @@ use crate::factory::CliFactory;
use crate::graph_util::ModuleGraphCreator; use crate::graph_util::ModuleGraphCreator;
use crate::http_util::HttpClient; use crate::http_util::HttpClient;
use crate::resolver::MappedSpecifierResolver; use crate::resolver::MappedSpecifierResolver;
use crate::resolver::SloppyImportsResolver;
use crate::tools::check::CheckOptions; use crate::tools::check::CheckOptions;
use crate::tools::lint::no_slow_types; use crate::tools::lint::no_slow_types;
use crate::tools::registry::diagnostics::PublishDiagnostic; use crate::tools::registry::diagnostics::PublishDiagnostic;
use crate::tools::registry::diagnostics::PublishDiagnosticsCollector; use crate::tools::registry::diagnostics::PublishDiagnosticsCollector;
use crate::tools::registry::graph::collect_invalid_external_imports; use crate::tools::registry::graph::collect_invalid_external_imports;
use crate::util::display::human_size; use crate::util::display::human_size;
use crate::util::import_map::ImportMapUnfurler;
mod api; mod api;
mod auth; mod auth;
@ -50,10 +50,13 @@ mod graph;
mod paths; mod paths;
mod publish_order; mod publish_order;
mod tar; mod tar;
mod unfurl;
use auth::get_auth_method; use auth::get_auth_method;
use auth::AuthMethod; use auth::AuthMethod;
use publish_order::PublishOrderGraph; use publish_order::PublishOrderGraph;
pub use unfurl::deno_json_deps;
use unfurl::SpecifierUnfurler;
use super::check::TypeChecker; use super::check::TypeChecker;
@ -81,12 +84,15 @@ impl PreparedPublishPackage {
static SUGGESTED_ENTRYPOINTS: [&str; 4] = static SUGGESTED_ENTRYPOINTS: [&str; 4] =
["mod.ts", "mod.js", "index.ts", "index.js"]; ["mod.ts", "mod.js", "index.ts", "index.js"];
#[allow(clippy::too_many_arguments)]
async fn prepare_publish( async fn prepare_publish(
package_name: &str, package_name: &str,
deno_json: &ConfigFile, deno_json: &ConfigFile,
source_cache: Arc<ParsedSourceCache>, source_cache: Arc<ParsedSourceCache>,
graph: Arc<deno_graph::ModuleGraph>, graph: Arc<deno_graph::ModuleGraph>,
mapped_resolver: Arc<MappedSpecifierResolver>, mapped_resolver: Arc<MappedSpecifierResolver>,
sloppy_imports_resolver: Option<SloppyImportsResolver>,
bare_node_builtins: bool,
diagnostics_collector: &PublishDiagnosticsCollector, diagnostics_collector: &PublishDiagnosticsCollector,
) -> Result<Rc<PreparedPublishPackage>, AnyError> { ) -> Result<Rc<PreparedPublishPackage>, AnyError> {
let config_path = deno_json.specifier.to_file_path().unwrap(); let config_path = deno_json.specifier.to_file_path().unwrap();
@ -132,7 +138,11 @@ async fn prepare_publish(
let diagnostics_collector = diagnostics_collector.clone(); let diagnostics_collector = diagnostics_collector.clone();
let tarball = deno_core::unsync::spawn_blocking(move || { let tarball = deno_core::unsync::spawn_blocking(move || {
let unfurler = ImportMapUnfurler::new(&mapped_resolver); let unfurler = SpecifierUnfurler::new(
&mapped_resolver,
sloppy_imports_resolver.as_ref(),
bare_node_builtins,
);
tar::create_gzipped_tarball( tar::create_gzipped_tarball(
&dir_path, &dir_path,
LazyGraphSourceParser::new(&source_cache, &graph), LazyGraphSourceParser::new(&source_cache, &graph),
@ -661,7 +671,9 @@ async fn prepare_packages_for_publishing(
let module_graph_creator = cli_factory.module_graph_creator().await?.as_ref(); let module_graph_creator = cli_factory.module_graph_creator().await?.as_ref();
let source_cache = cli_factory.parsed_source_cache(); let source_cache = cli_factory.parsed_source_cache();
let type_checker = cli_factory.type_checker().await?; let type_checker = cli_factory.type_checker().await?;
let fs = cli_factory.fs();
let cli_options = cli_factory.cli_options(); let cli_options = cli_factory.cli_options();
let bare_node_builtins = cli_options.unstable_bare_node_builtins();
if members.len() > 1 { if members.len() > 1 {
println!("Publishing a workspace..."); println!("Publishing a workspace...");
@ -686,6 +698,11 @@ async fn prepare_packages_for_publishing(
.into_iter() .into_iter()
.map(|member| { .map(|member| {
let mapped_resolver = mapped_resolver.clone(); let mapped_resolver = mapped_resolver.clone();
let sloppy_imports_resolver = if cli_options.unstable_sloppy_imports() {
Some(SloppyImportsResolver::new(fs.clone()))
} else {
None
};
let graph = graph.clone(); let graph = graph.clone();
async move { async move {
let package = prepare_publish( let package = prepare_publish(
@ -694,6 +711,8 @@ async fn prepare_packages_for_publishing(
source_cache.clone(), source_cache.clone(),
graph, graph,
mapped_resolver, mapped_resolver,
sloppy_imports_resolver,
bare_node_builtins,
diagnostics_collector, diagnostics_collector,
) )
.await .await

View file

@ -18,10 +18,10 @@ use tar::Header;
use crate::cache::LazyGraphSourceParser; use crate::cache::LazyGraphSourceParser;
use crate::tools::registry::paths::PackagePath; use crate::tools::registry::paths::PackagePath;
use crate::util::import_map::ImportMapUnfurler;
use super::diagnostics::PublishDiagnostic; use super::diagnostics::PublishDiagnostic;
use super::diagnostics::PublishDiagnosticsCollector; use super::diagnostics::PublishDiagnosticsCollector;
use super::unfurl::SpecifierUnfurler;
#[derive(Debug, Clone, PartialEq)] #[derive(Debug, Clone, PartialEq)]
pub struct PublishableTarballFile { pub struct PublishableTarballFile {
@ -40,7 +40,7 @@ pub fn create_gzipped_tarball(
dir: &Path, dir: &Path,
source_parser: LazyGraphSourceParser, source_parser: LazyGraphSourceParser,
diagnostics_collector: &PublishDiagnosticsCollector, diagnostics_collector: &PublishDiagnosticsCollector,
unfurler: &ImportMapUnfurler, unfurler: &SpecifierUnfurler,
file_patterns: Option<FilePatterns>, file_patterns: Option<FilePatterns>,
) -> Result<PublishableTarball, AnyError> { ) -> Result<PublishableTarball, AnyError> {
let mut tar = TarGzArchive::new(); let mut tar = TarGzArchive::new();
@ -192,7 +192,7 @@ pub fn create_gzipped_tarball(
fn resolve_content_maybe_unfurling( fn resolve_content_maybe_unfurling(
path: &Path, path: &Path,
specifier: &Url, specifier: &Url,
unfurler: &ImportMapUnfurler, unfurler: &SpecifierUnfurler,
source_parser: LazyGraphSourceParser, source_parser: LazyGraphSourceParser,
diagnostics_collector: &PublishDiagnosticsCollector, diagnostics_collector: &PublishDiagnosticsCollector,
) -> Result<Vec<u8>, AnyError> { ) -> Result<Vec<u8>, AnyError> {
@ -241,7 +241,7 @@ fn resolve_content_maybe_unfurling(
log::debug!("Unfurling {}", specifier); log::debug!("Unfurling {}", specifier);
let mut reporter = |diagnostic| { let mut reporter = |diagnostic| {
diagnostics_collector.push(PublishDiagnostic::ImportMapUnfurl(diagnostic)); diagnostics_collector.push(PublishDiagnostic::SpecifierUnfurl(diagnostic));
}; };
let content = unfurler.unfurl(specifier, &parsed_source, &mut reporter); let content = unfurler.unfurl(specifier, &parsed_source, &mut reporter);
Ok(content.into_bytes()) Ok(content.into_bytes())

View file

@ -11,11 +11,13 @@ use deno_graph::DefaultModuleAnalyzer;
use deno_graph::DependencyDescriptor; use deno_graph::DependencyDescriptor;
use deno_graph::DynamicTemplatePart; use deno_graph::DynamicTemplatePart;
use deno_graph::TypeScriptReference; use deno_graph::TypeScriptReference;
use deno_runtime::deno_node::is_builtin_node_module;
use deno_semver::jsr::JsrDepPackageReq; use deno_semver::jsr::JsrDepPackageReq;
use deno_semver::jsr::JsrPackageReqReference; use deno_semver::jsr::JsrPackageReqReference;
use deno_semver::npm::NpmPackageReqReference; use deno_semver::npm::NpmPackageReqReference;
use crate::resolver::MappedSpecifierResolver; use crate::resolver::MappedSpecifierResolver;
use crate::resolver::SloppyImportsResolver;
pub fn deno_json_deps( pub fn deno_json_deps(
config: &deno_config::ConfigFile, config: &deno_config::ConfigFile,
@ -61,7 +63,7 @@ fn values_to_set<'a>(
} }
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub enum ImportMapUnfurlDiagnostic { pub enum SpecifierUnfurlerDiagnostic {
UnanalyzableDynamicImport { UnanalyzableDynamicImport {
specifier: ModuleSpecifier, specifier: ModuleSpecifier,
text_info: SourceTextInfo, text_info: SourceTextInfo,
@ -69,7 +71,7 @@ pub enum ImportMapUnfurlDiagnostic {
}, },
} }
impl ImportMapUnfurlDiagnostic { impl SpecifierUnfurlerDiagnostic {
pub fn code(&self) -> &'static str { pub fn code(&self) -> &'static str {
match self { match self {
Self::UnanalyzableDynamicImport { .. } => "unanalyzable-dynamic-import", Self::UnanalyzableDynamicImport { .. } => "unanalyzable-dynamic-import",
@ -85,20 +87,153 @@ impl ImportMapUnfurlDiagnostic {
} }
} }
pub struct ImportMapUnfurler<'a> { pub struct SpecifierUnfurler<'a> {
import_map: &'a MappedSpecifierResolver, mapped_resolver: &'a MappedSpecifierResolver,
sloppy_imports_resolver: Option<&'a SloppyImportsResolver>,
bare_node_builtins: bool,
} }
impl<'a> ImportMapUnfurler<'a> { impl<'a> SpecifierUnfurler<'a> {
pub fn new(import_map: &'a MappedSpecifierResolver) -> Self { pub fn new(
Self { import_map } mapped_resolver: &'a MappedSpecifierResolver,
sloppy_imports_resolver: Option<&'a SloppyImportsResolver>,
bare_node_builtins: bool,
) -> Self {
Self {
mapped_resolver,
sloppy_imports_resolver,
bare_node_builtins,
}
}
fn unfurl_specifier(
&self,
referrer: &ModuleSpecifier,
specifier: &str,
) -> Option<String> {
let resolved =
if let Ok(resolved) = self.mapped_resolver.resolve(specifier, referrer) {
resolved.into_specifier()
} else {
None
};
let resolved = match resolved {
Some(resolved) => resolved,
None if self.bare_node_builtins && is_builtin_node_module(specifier) => {
format!("node:{specifier}").parse().unwrap()
}
None => ModuleSpecifier::options()
.base_url(Some(referrer))
.parse(specifier)
.ok()?,
};
// TODO(lucacasonato): this requires integration in deno_graph first
// let resolved = if let Ok(specifier) =
// NpmPackageReqReference::from_specifier(&resolved)
// {
// if let Some(scope_name) = specifier.req().name.strip_prefix("@jsr/") {
// let (scope, name) = scope_name.split_once("__")?;
// let new_specifier = JsrPackageReqReference::new(PackageReqReference {
// req: PackageReq {
// name: format!("@{scope}/{name}"),
// version_req: specifier.req().version_req.clone(),
// },
// sub_path: specifier.sub_path().map(ToOwned::to_owned),
// })
// .to_string();
// ModuleSpecifier::parse(&new_specifier).unwrap()
// } else {
// resolved
// }
// } else {
// resolved
// };
let resolved =
if let Some(sloppy_imports_resolver) = self.sloppy_imports_resolver {
sloppy_imports_resolver
.resolve(&resolved)
.as_specifier()
.clone()
} else {
resolved
};
relative_url(&resolved, referrer, specifier)
}
/// Attempts to unfurl the dynamic dependency returning `true` on success
/// or `false` when the import was not analyzable.
fn try_unfurl_dynamic_dep(
&self,
module_url: &lsp_types::Url,
parsed_source: &ParsedSource,
dep: &deno_graph::DynamicDependencyDescriptor,
text_changes: &mut Vec<deno_ast::TextChange>,
) -> bool {
match &dep.argument {
deno_graph::DynamicArgument::String(specifier) => {
let range = to_range(parsed_source, &dep.argument_range);
let maybe_relative_index =
parsed_source.text_info().text_str()[range.start..].find(specifier);
let Some(relative_index) = maybe_relative_index else {
return false;
};
let unfurled = self.unfurl_specifier(module_url, specifier);
let Some(unfurled) = unfurled else {
return false;
};
let start = range.start + relative_index;
text_changes.push(deno_ast::TextChange {
range: start..start + specifier.len(),
new_text: unfurled,
});
true
}
deno_graph::DynamicArgument::Template(parts) => match parts.first() {
Some(DynamicTemplatePart::String { value: specifier }) => {
// relative doesn't need to be modified
let is_relative =
specifier.starts_with("./") || specifier.starts_with("../");
if is_relative {
return true;
}
if !specifier.ends_with('/') {
return false;
}
let unfurled = self.unfurl_specifier(module_url, specifier);
let Some(unfurled) = unfurled else {
return false;
};
let range = to_range(parsed_source, &dep.argument_range);
let maybe_relative_index =
parsed_source.text_info().text_str()[range.start..].find(specifier);
let Some(relative_index) = maybe_relative_index else {
return false;
};
let start = range.start + relative_index;
text_changes.push(deno_ast::TextChange {
range: start..start + specifier.len(),
new_text: unfurled,
});
true
}
Some(DynamicTemplatePart::Expr) => {
false // failed analyzing
}
None => {
true // ignore
}
},
deno_graph::DynamicArgument::Expr => {
false // failed analyzing
}
}
} }
pub fn unfurl( pub fn unfurl(
&self, &self,
url: &ModuleSpecifier, url: &ModuleSpecifier,
parsed_source: &ParsedSource, parsed_source: &ParsedSource,
diagnostic_reporter: &mut dyn FnMut(ImportMapUnfurlDiagnostic), diagnostic_reporter: &mut dyn FnMut(SpecifierUnfurlerDiagnostic),
) -> String { ) -> String {
let mut text_changes = Vec::new(); let mut text_changes = Vec::new();
let module_info = DefaultModuleAnalyzer::module_info(parsed_source); let module_info = DefaultModuleAnalyzer::module_info(parsed_source);
@ -106,14 +241,11 @@ impl<'a> ImportMapUnfurler<'a> {
|specifier: &str, |specifier: &str,
range: &deno_graph::PositionRange, range: &deno_graph::PositionRange,
text_changes: &mut Vec<deno_ast::TextChange>| { text_changes: &mut Vec<deno_ast::TextChange>| {
let resolved = self.import_map.resolve(specifier, url); if let Some(unfurled) = self.unfurl_specifier(url, specifier) {
if let Ok(resolved) = resolved { text_changes.push(deno_ast::TextChange {
if let Some(resolved) = resolved.into_specifier() { range: to_range(parsed_source, range),
text_changes.push(deno_ast::TextChange { new_text: unfurled,
range: to_range(parsed_source, range), });
new_text: make_relative_to(url, &resolved),
});
}
} }
}; };
for dep in &module_info.dependencies { for dep in &module_info.dependencies {
@ -126,8 +258,7 @@ impl<'a> ImportMapUnfurler<'a> {
); );
} }
DependencyDescriptor::Dynamic(dep) => { DependencyDescriptor::Dynamic(dep) => {
let success = try_unfurl_dynamic_dep( let success = self.try_unfurl_dynamic_dep(
self.import_map,
url, url,
parsed_source, parsed_source,
dep, dep,
@ -144,7 +275,7 @@ impl<'a> ImportMapUnfurler<'a> {
.line_start(dep.argument_range.end.line) .line_start(dep.argument_range.end.line)
+ dep.argument_range.end.character; + dep.argument_range.end.character;
diagnostic_reporter( diagnostic_reporter(
ImportMapUnfurlDiagnostic::UnanalyzableDynamicImport { SpecifierUnfurlerDiagnostic::UnanalyzableDynamicImport {
specifier: url.to_owned(), specifier: url.to_owned(),
range: SourceRange::new(start_pos, end_pos), range: SourceRange::new(start_pos, end_pos),
text_info: parsed_source.text_info().clone(), text_info: parsed_source.text_info().clone(),
@ -188,85 +319,20 @@ impl<'a> ImportMapUnfurler<'a> {
} }
} }
fn make_relative_to(from: &ModuleSpecifier, to: &ModuleSpecifier) -> String { fn relative_url(
if to.scheme() == "file" { resolved: &ModuleSpecifier,
format!("./{}", from.make_relative(to).unwrap()) referrer: &ModuleSpecifier,
specifier: &str,
) -> Option<String> {
let new_specifier = if resolved.scheme() == "file" {
format!("./{}", referrer.make_relative(resolved).unwrap())
} else { } else {
to.to_string() resolved.to_string()
} };
} if new_specifier == specifier {
return None;
/// Attempts to unfurl the dynamic dependency returning `true` on success
/// or `false` when the import was not analyzable.
fn try_unfurl_dynamic_dep(
mapped_resolver: &MappedSpecifierResolver,
module_url: &lsp_types::Url,
parsed_source: &ParsedSource,
dep: &deno_graph::DynamicDependencyDescriptor,
text_changes: &mut Vec<deno_ast::TextChange>,
) -> bool {
match &dep.argument {
deno_graph::DynamicArgument::String(value) => {
let range = to_range(parsed_source, &dep.argument_range);
let maybe_relative_index =
parsed_source.text_info().text_str()[range.start..].find(value);
let Some(relative_index) = maybe_relative_index else {
return false;
};
let resolved = mapped_resolver.resolve(value, module_url);
let Ok(resolved) = resolved else {
return false;
};
let Some(resolved) = resolved.into_specifier() else {
return false;
};
let start = range.start + relative_index;
text_changes.push(deno_ast::TextChange {
range: start..start + value.len(),
new_text: make_relative_to(module_url, &resolved),
});
true
}
deno_graph::DynamicArgument::Template(parts) => match parts.first() {
Some(DynamicTemplatePart::String { value }) => {
// relative doesn't need to be modified
let is_relative = value.starts_with("./") || value.starts_with("../");
if is_relative {
return true;
}
if !value.ends_with('/') {
return false;
}
let Ok(resolved) = mapped_resolver.resolve(value, module_url) else {
return false;
};
let Some(resolved) = resolved.into_specifier() else {
return false;
};
let range = to_range(parsed_source, &dep.argument_range);
let maybe_relative_index =
parsed_source.text_info().text_str()[range.start..].find(value);
let Some(relative_index) = maybe_relative_index else {
return false;
};
let start = range.start + relative_index;
text_changes.push(deno_ast::TextChange {
range: start..start + value.len(),
new_text: make_relative_to(module_url, &resolved),
});
true
}
Some(DynamicTemplatePart::Expr) => {
false // failed analyzing
}
None => {
true // ignore
}
},
deno_graph::DynamicArgument::Expr => {
false // failed analyzing
}
} }
Some(new_specifier)
} }
fn to_range( fn to_range(
@ -290,6 +356,7 @@ fn to_range(
mod tests { mod tests {
use std::sync::Arc; use std::sync::Arc;
use crate::args::package_json::get_local_package_json_version_reqs;
use crate::args::PackageJsonDepsProvider; use crate::args::PackageJsonDepsProvider;
use super::*; use super::*;
@ -297,8 +364,12 @@ mod tests {
use deno_ast::ModuleSpecifier; use deno_ast::ModuleSpecifier;
use deno_core::serde_json::json; use deno_core::serde_json::json;
use deno_core::url::Url; use deno_core::url::Url;
use deno_runtime::deno_fs::RealFs;
use deno_runtime::deno_node::PackageJson;
use import_map::ImportMapWithDiagnostics; use import_map::ImportMapWithDiagnostics;
use indexmap::IndexMap;
use pretty_assertions::assert_eq; use pretty_assertions::assert_eq;
use test_util::testdata_path;
fn parse_ast(specifier: &Url, source_code: &str) -> ParsedSource { fn parse_ast(specifier: &Url, source_code: &str) -> ParsedSource {
let media_type = MediaType::from_specifier(specifier); let media_type = MediaType::from_specifier(specifier);
@ -315,22 +386,38 @@ mod tests {
#[test] #[test]
fn test_unfurling() { fn test_unfurling() {
let cwd = testdata_path().join("unfurl").to_path_buf();
let deno_json_url = let deno_json_url =
ModuleSpecifier::parse("file:///dev/deno.json").unwrap(); ModuleSpecifier::from_file_path(cwd.join("deno.json")).unwrap();
let value = json!({ let value = json!({
"imports": { "imports": {
"express": "npm:express@5", "express": "npm:express@5",
"lib/": "./lib/", "lib/": "./lib/",
"fizz": "./fizz/mod.ts" "fizz": "./fizz/mod.ts",
"@std/fs": "npm:@jsr/std__fs@1",
} }
}); });
let ImportMapWithDiagnostics { import_map, .. } = let ImportMapWithDiagnostics { import_map, .. } =
import_map::parse_from_value(deno_json_url, value).unwrap(); import_map::parse_from_value(deno_json_url, value).unwrap();
let mapped_resolved = MappedSpecifierResolver::new( let mut package_json = PackageJson::empty(cwd.join("package.json"));
package_json.dependencies =
Some(IndexMap::from([("chalk".to_string(), "5".to_string())]));
let mapped_resolver = MappedSpecifierResolver::new(
Some(Arc::new(import_map)), Some(Arc::new(import_map)),
Arc::new(PackageJsonDepsProvider::new(None)), Arc::new(PackageJsonDepsProvider::new(Some(
get_local_package_json_version_reqs(&package_json),
))),
);
let fs = Arc::new(RealFs);
let sloppy_imports_resolver = SloppyImportsResolver::new(fs);
let unfurler = SpecifierUnfurler::new(
&mapped_resolver,
Some(&sloppy_imports_resolver),
true,
); );
let unfurler = ImportMapUnfurler::new(&mapped_resolved);
// Unfurling TS file should apply changes. // Unfurling TS file should apply changes.
{ {
@ -338,6 +425,16 @@ mod tests {
import foo from "lib/foo.ts"; import foo from "lib/foo.ts";
import bar from "lib/bar.ts"; import bar from "lib/bar.ts";
import fizz from "fizz"; import fizz from "fizz";
import chalk from "chalk";
import baz from "./baz";
import b from "./b.js";
import b2 from "./b";
import url from "url";
// TODO: unfurl these to jsr
// import "npm:@jsr/std__fs@1/file";
// import "npm:@jsr/std__fs@1";
// import "npm:@jsr/std__fs";
// import "@std/fs";
const test1 = await import("lib/foo.ts"); const test1 = await import("lib/foo.ts");
const test2 = await import(`lib/foo.ts`); const test2 = await import(`lib/foo.ts`);
@ -347,7 +444,8 @@ const test4 = await import(`./lib/${expr}`);
const test5 = await import(`lib${expr}`); const test5 = await import(`lib${expr}`);
const test6 = await import(`${expr}`); const test6 = await import(`${expr}`);
"#; "#;
let specifier = ModuleSpecifier::parse("file:///dev/mod.ts").unwrap(); let specifier =
ModuleSpecifier::from_file_path(cwd.join("mod.ts")).unwrap();
let source = parse_ast(&specifier, source_code); let source = parse_ast(&specifier, source_code);
let mut d = Vec::new(); let mut d = Vec::new();
let mut reporter = |diagnostic| d.push(diagnostic); let mut reporter = |diagnostic| d.push(diagnostic);
@ -356,7 +454,7 @@ const test6 = await import(`${expr}`);
assert!( assert!(
matches!( matches!(
d[0], d[0],
ImportMapUnfurlDiagnostic::UnanalyzableDynamicImport { .. } SpecifierUnfurlerDiagnostic::UnanalyzableDynamicImport { .. }
), ),
"{:?}", "{:?}",
d[0] d[0]
@ -364,7 +462,7 @@ const test6 = await import(`${expr}`);
assert!( assert!(
matches!( matches!(
d[1], d[1],
ImportMapUnfurlDiagnostic::UnanalyzableDynamicImport { .. } SpecifierUnfurlerDiagnostic::UnanalyzableDynamicImport { .. }
), ),
"{:?}", "{:?}",
d[1] d[1]
@ -373,6 +471,16 @@ const test6 = await import(`${expr}`);
import foo from "./lib/foo.ts"; import foo from "./lib/foo.ts";
import bar from "./lib/bar.ts"; import bar from "./lib/bar.ts";
import fizz from "./fizz/mod.ts"; import fizz from "./fizz/mod.ts";
import chalk from "npm:chalk@5";
import baz from "./baz/index.js";
import b from "./b.ts";
import b2 from "./b.ts";
import url from "node:url";
// TODO: unfurl these to jsr
// import "npm:@jsr/std__fs@1/file";
// import "npm:@jsr/std__fs@1";
// import "npm:@jsr/std__fs";
// import "@std/fs";
const test1 = await import("./lib/foo.ts"); const test1 = await import("./lib/foo.ts");
const test2 = await import(`./lib/foo.ts`); const test2 = await import(`./lib/foo.ts`);

View file

@ -8,7 +8,6 @@ pub mod display;
pub mod draw_thread; pub mod draw_thread;
pub mod file_watcher; pub mod file_watcher;
pub mod fs; pub mod fs;
pub mod import_map;
pub mod logger; pub mod logger;
pub mod path; pub mod path;
pub mod progress_bar; pub mod progress_bar;

View file

@ -224,6 +224,22 @@ itest!(config_flag {
http_server: true, http_server: true,
}); });
itest!(bare_node_builtins {
args: "publish --token 'sadfasdf' --dry-run --unstable-bare-node-builtins",
output: "publish/bare_node_builtins.out",
cwd: Some("publish/bare_node_builtins"),
envs: env_vars_for_jsr_npm_tests(),
http_server: true,
});
itest!(sloppy_imports {
args: "publish --token 'sadfasdf' --dry-run --unstable-sloppy-imports",
output: "publish/sloppy_imports.out",
cwd: Some("publish/sloppy_imports"),
envs: env_vars_for_jsr_tests(),
http_server: true,
});
itest!(jsr_jsonc { itest!(jsr_jsonc {
args: "publish --token 'sadfasdf'", args: "publish --token 'sadfasdf'",
cwd: Some("publish/jsr_jsonc"), cwd: Some("publish/jsr_jsonc"),

View file

@ -0,0 +1,11 @@
Warning: Resolving "url" as "node:url" at file:///[WILDCARD]/publish/bare_node_builtins/mod.ts:1:22. If you want to use a built-in Node module, add a "node:" prefix.
Warning: Resolving "url" as "node:url" at file:///[WILDCARD]/publish/bare_node_builtins/mod.ts:1:22. If you want to use a built-in Node module, add a "node:" prefix.
Download http://localhost:4545/npm/registry/@types/node
Download http://localhost:4545/npm/registry/@types/node/node-18.8.2.tgz
Check file:///[WILDCARD]/publish/bare_node_builtins/mod.ts
Checking for slow types in the public API...
Check file:///[WILDCARD]/publish/bare_node_builtins/mod.ts
Simulating publish of @foo/bar@1.0.0 with files:
file:///[WILDCARD]/publish/bare_node_builtins/deno.json (87B)
file:///[WILDCARD]/publish/bare_node_builtins/mod.ts (121B)
Warning Aborting due to --dry-run

View file

@ -0,0 +1,7 @@
{
"name": "@foo/bar",
"version": "1.0.0",
"exports": {
".": "./mod.ts"
}
}

View file

@ -0,0 +1,5 @@
import * as url from "url";
export function foobar(): { href: string } {
return url.pathToFileURL("/foo/bar");
}

View file

@ -0,0 +1,12 @@
Warning Sloppy module resolution (hint: specify path to index.ts file in directory instead)
at file:///[WILDCARD]/publish/sloppy_imports/mod.ts:1:20
Warning Sloppy module resolution (hint: specify path to index.ts file in directory instead)
at file:///[WILDCARD]/publish/sloppy_imports/mod.ts:1:20
Check file:///[WILDCARD]/publish/sloppy_imports/mod.ts
Checking for slow types in the public API...
Check file:///[WILDCARD]/publish/sloppy_imports/mod.ts
Simulating publish of @foo/bar@1.0.0 with files:
file:///[WILDCARD]/publish/sloppy_imports/b/index.ts (27B)
file:///[WILDCARD]/publish/sloppy_imports/deno.json (87B)
file:///[WILDCARD]/publish/sloppy_imports/mod.ts (35B)
Warning Aborting due to --dry-run

View file

@ -0,0 +1 @@
export const PI = Math.PI;

View file

@ -0,0 +1,7 @@
{
"name": "@foo/bar",
"version": "1.0.0",
"exports": {
".": "./mod.ts"
}
}

View file

@ -0,0 +1 @@
export { PI } from "./b";

View file

@ -7,9 +7,9 @@ warning[unanalyzable-dynamic-import]: unable to analyze dynamic import
2 | await import("asd " + asd); 2 | await import("asd " + asd);
| ^^^^^^^^^^^^ the unanalyzable dynamic import | ^^^^^^^^^^^^ the unanalyzable dynamic import
info: after publishing this package, imports from the local import map do not work info: after publishing this package, imports from the local import map / package.json do not work
info: dynamic imports that can not be analyzed at publish time will not be rewritten automatically info: dynamic imports that can not be analyzed at publish time will not be rewritten automatically
info: make sure the dynamic import is resolvable at runtime without an import map info: make sure the dynamic import is resolvable at runtime without an import map / package.json
Publishing @foo/bar@1.0.0 ... Publishing @foo/bar@1.0.0 ...
Successfully published @foo/bar@1.0.0 Successfully published @foo/bar@1.0.0

0
tests/testdata/unfurl/b.ts vendored Normal file
View file

0
tests/testdata/unfurl/baz/index.js vendored Normal file
View file