summaryrefslogtreecommitdiff
path: root/cli/tools
diff options
context:
space:
mode:
Diffstat (limited to 'cli/tools')
-rw-r--r--cli/tools/check.rs108
-rw-r--r--cli/tools/registry/graph.rs154
-rw-r--r--cli/tools/registry/mod.rs113
-rw-r--r--cli/tools/registry/publish_order.rs89
4 files changed, 345 insertions, 119 deletions
diff --git a/cli/tools/check.rs b/cli/tools/check.rs
index bde9a7612..7133f4d3f 100644
--- a/cli/tools/check.rs
+++ b/cli/tools/check.rs
@@ -1,6 +1,7 @@
// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license.
use std::collections::HashSet;
+use std::collections::VecDeque;
use std::sync::Arc;
use deno_ast::MediaType;
@@ -23,6 +24,7 @@ use crate::cache::FastInsecureHasher;
use crate::cache::TypeCheckCache;
use crate::npm::CliNpmResolver;
use crate::tsc;
+use crate::tsc::Diagnostics;
use crate::version;
/// Options for performing a check of a module graph. Note that the decision to
@@ -68,6 +70,23 @@ impl TypeChecker {
graph: Arc<ModuleGraph>,
options: CheckOptions,
) -> Result<(), AnyError> {
+ let diagnostics = self.check_diagnostics(graph, options).await?;
+ if diagnostics.is_empty() {
+ Ok(())
+ } else {
+ Err(diagnostics.into())
+ }
+ }
+
+ /// Type check the module graph returning its diagnostics.
+ ///
+ /// It is expected that it is determined if a check and/or emit is validated
+ /// before the function is called.
+ pub async fn check_diagnostics(
+ &self,
+ graph: Arc<ModuleGraph>,
+ options: CheckOptions,
+ ) -> Result<Diagnostics, AnyError> {
// node built-in specifiers use the @types/node package to determine
// types, so inject that now (the caller should do this after the lockfile
// has been written)
@@ -100,7 +119,7 @@ impl TypeChecker {
type_check_mode,
&ts_config,
) {
- CheckHashResult::NoFiles => return Ok(()),
+ CheckHashResult::NoFiles => return Ok(Default::default()),
CheckHashResult::Hash(hash) => Some(hash),
}
}
@@ -111,7 +130,7 @@ impl TypeChecker {
if !options.reload {
if let Some(check_hash) = maybe_check_hash {
if cache.has_check_hash(check_hash) {
- return Ok(());
+ return Ok(Default::default());
}
}
}
@@ -152,7 +171,7 @@ impl TypeChecker {
check_mode: type_check_mode,
})?;
- let diagnostics = if type_check_mode == TypeCheckMode::Local {
+ let mut diagnostics = if type_check_mode == TypeCheckMode::Local {
response.diagnostics.filter(|d| {
if let Some(file_name) = &d.file_name {
if !file_name.starts_with("http") {
@@ -175,6 +194,8 @@ impl TypeChecker {
response.diagnostics
};
+ diagnostics.apply_fast_check_source_maps(&graph);
+
if let Some(tsbuildinfo) = response.maybe_tsbuildinfo {
cache.set_tsbuildinfo(&graph.roots[0], &tsbuildinfo);
}
@@ -187,11 +208,7 @@ impl TypeChecker {
log::debug!("{}", response.stats);
- if diagnostics.is_empty() {
- Ok(())
- } else {
- Err(diagnostics.into())
- }
+ Ok(diagnostics)
}
}
@@ -256,7 +273,12 @@ fn get_check_hash(
}
hasher.write_str(module.specifier.as_str());
- hasher.write_str(&module.source);
+ hasher.write_str(
+ module
+ .fast_check_module()
+ .map(|s| s.source.as_ref())
+ .unwrap_or(&module.source),
+ );
}
Module::Node(_) => {
// the @types/node package will be in the resolved
@@ -345,21 +367,18 @@ fn get_tsc_roots(
));
}
- let mut seen_roots =
- HashSet::with_capacity(graph.imports.len() + graph.roots.len());
+ let mut seen =
+ HashSet::with_capacity(graph.imports.len() + graph.specifiers_count());
+ let mut pending = VecDeque::new();
// put in the global types first so that they're resolved before anything else
for import in graph.imports.values() {
for dep in import.dependencies.values() {
let specifier = dep.get_type().or_else(|| dep.get_code());
if let Some(specifier) = &specifier {
- if seen_roots.insert(*specifier) {
- let maybe_entry = graph
- .get(specifier)
- .and_then(|m| maybe_get_check_entry(m, check_js));
- if let Some(entry) = maybe_entry {
- result.push(entry);
- }
+ let specifier = graph.resolve(specifier);
+ if seen.insert(specifier.clone()) {
+ pending.push_back(specifier);
}
}
}
@@ -367,23 +386,50 @@ fn get_tsc_roots(
// then the roots
for root in &graph.roots {
- if let Some(module) = graph.get(root) {
- if seen_roots.insert(root) {
- if let Some(entry) = maybe_get_check_entry(module, check_js) {
- result.push(entry);
- }
- }
+ let specifier = graph.resolve(root);
+ if seen.insert(specifier.clone()) {
+ pending.push_back(specifier);
}
}
- // now the rest
- result.extend(graph.modules().filter_map(|module| {
- if seen_roots.contains(module.specifier()) {
- None
- } else {
- maybe_get_check_entry(module, check_js)
+ // now walk the graph that only includes the fast check dependencies
+ while let Some(specifier) = pending.pop_front() {
+ let Some(module) = graph.get(&specifier) else {
+ continue;
+ };
+ if let Some(entry) = maybe_get_check_entry(module, check_js) {
+ result.push(entry);
+ }
+ if let Some(module) = module.esm() {
+ let deps = module.dependencies_prefer_fast_check();
+ for dep in deps.values() {
+ // walk both the code and type dependencies
+ if let Some(specifier) = dep.get_code() {
+ let specifier = graph.resolve(specifier);
+ if seen.insert(specifier.clone()) {
+ pending.push_back(specifier);
+ }
+ }
+ if let Some(specifier) = dep.get_type() {
+ let specifier = graph.resolve(specifier);
+ if seen.insert(specifier.clone()) {
+ pending.push_back(specifier);
+ }
+ }
+ }
+
+ if let Some(dep) = module
+ .maybe_types_dependency
+ .as_ref()
+ .and_then(|d| d.dependency.ok())
+ {
+ let specifier = graph.resolve(&dep.specifier);
+ if seen.insert(specifier.clone()) {
+ pending.push_back(specifier);
+ }
+ }
}
- }));
+ }
result
}
diff --git a/cli/tools/registry/graph.rs b/cli/tools/registry/graph.rs
new file mode 100644
index 000000000..04b37508c
--- /dev/null
+++ b/cli/tools/registry/graph.rs
@@ -0,0 +1,154 @@
+// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license.
+
+use std::collections::HashSet;
+use std::collections::VecDeque;
+
+use deno_ast::ModuleSpecifier;
+use deno_config::ConfigFile;
+use deno_config::WorkspaceConfig;
+use deno_core::anyhow::bail;
+use deno_core::anyhow::Context;
+use deno_core::error::AnyError;
+use deno_graph::FastCheckDiagnostic;
+use deno_graph::ModuleGraph;
+
+#[derive(Debug)]
+pub struct MemberRoots {
+ pub name: String,
+ pub dir_url: ModuleSpecifier,
+ pub exports: Vec<ModuleSpecifier>,
+}
+
+pub fn get_workspace_member_roots(
+ config: &WorkspaceConfig,
+) -> Result<Vec<MemberRoots>, AnyError> {
+ let mut members = Vec::with_capacity(config.members.len());
+ let mut seen_names = HashSet::with_capacity(config.members.len());
+ for member in &config.members {
+ if !seen_names.insert(&member.package_name) {
+ bail!(
+ "Cannot have two workspace packages with the same name ('{}' at {})",
+ member.package_name,
+ member.path.display(),
+ );
+ }
+ members.push(MemberRoots {
+ name: member.package_name.clone(),
+ dir_url: member.config_file.specifier.join("./").unwrap().clone(),
+ exports: resolve_config_file_roots_from_exports(&member.config_file)?,
+ });
+ }
+ Ok(members)
+}
+
+pub fn resolve_config_file_roots_from_exports(
+ config_file: &ConfigFile,
+) -> Result<Vec<ModuleSpecifier>, AnyError> {
+ let exports_config = config_file
+ .to_exports_config()
+ .with_context(|| {
+ format!("Failed to parse exports at {}", config_file.specifier)
+ })?
+ .into_map();
+ let mut exports = Vec::with_capacity(exports_config.len());
+ for (_, value) in exports_config {
+ let entry_point =
+ config_file.specifier.join(&value).with_context(|| {
+ format!("Failed to join {} with {}", config_file.specifier, value)
+ })?;
+ exports.push(entry_point);
+ }
+ Ok(exports)
+}
+
+pub fn surface_fast_check_type_graph_errors(
+ graph: &ModuleGraph,
+ packages: &[MemberRoots],
+) -> Result<(), AnyError> {
+ let mut diagnostic_count = 0;
+ let mut seen_diagnostics = HashSet::new();
+ let mut seen_modules = HashSet::with_capacity(graph.specifiers_count());
+ for package in packages {
+ let mut pending = VecDeque::new();
+ for export in &package.exports {
+ if seen_modules.insert(export.clone()) {
+ pending.push_back(export.clone());
+ }
+ }
+
+ 'analyze_package: while let Some(specifier) = pending.pop_front() {
+ let Ok(Some(module)) = graph.try_get_prefer_types(&specifier) else {
+ continue;
+ };
+ let Some(esm_module) = module.esm() else {
+ continue;
+ };
+ if let Some(diagnostic) = esm_module.fast_check_diagnostic() {
+ for diagnostic in diagnostic.flatten_multiple() {
+ if matches!(
+ diagnostic,
+ FastCheckDiagnostic::UnsupportedJavaScriptEntrypoint { .. }
+ ) {
+ // ignore JS packages for fast check
+ log::warn!(
+ concat!(
+ "{} Package '{}' is a JavaScript package without a corresponding ",
+ "declaration file. This may lead to a non-optimal experience for ",
+ "users of your package. For performance reasons, it's recommended ",
+ "to ship a corresponding TypeScript declaration file or to ",
+ "convert to TypeScript.",
+ ),
+ deno_runtime::colors::yellow("Warning"),
+ package.name,
+ );
+ break 'analyze_package; // no need to keep analyzing this package
+ } else {
+ let message = diagnostic.message_with_range();
+ if !seen_diagnostics.insert(message.clone()) {
+ continue;
+ }
+
+ log::error!("\n{}", message);
+ diagnostic_count += 1;
+ }
+ }
+ }
+
+ // analyze the next dependencies
+ for dep in esm_module.dependencies_prefer_fast_check().values() {
+ let Some(specifier) = graph.resolve_dependency_from_dep(dep, true)
+ else {
+ continue;
+ };
+
+ let dep_in_same_package =
+ specifier.as_str().starts_with(package.dir_url.as_str());
+ if dep_in_same_package {
+ let is_new = seen_modules.insert(specifier.clone());
+ if is_new {
+ pending.push_back(specifier.clone());
+ }
+ }
+ }
+ }
+ }
+
+ if diagnostic_count > 0 {
+ // for the time being, tell the user why we have these errors and the benefit they bring
+ log::error!(
+ concat!(
+ "\nFixing these fast check errors is required to make the code fast check compatible ",
+ "which enables type checking your package's TypeScript code with the same ",
+ "performance as if you had distributed declaration files. Do any of these ",
+ "errors seem too restrictive or incorrect? Please open an issue if so to ",
+ "help us improve: https://github.com/denoland/deno/issues\n",
+ )
+ );
+ bail!(
+ "Had {} fast check error{}.",
+ diagnostic_count,
+ if diagnostic_count == 1 { "" } else { "s" }
+ )
+ }
+ Ok(())
+}
diff --git a/cli/tools/registry/mod.rs b/cli/tools/registry/mod.rs
index 36d5b4a7d..8293a87fe 100644
--- a/cli/tools/registry/mod.rs
+++ b/cli/tools/registry/mod.rs
@@ -24,16 +24,24 @@ use sha2::Digest;
use crate::args::deno_registry_api_url;
use crate::args::deno_registry_url;
+use crate::args::CliOptions;
use crate::args::Flags;
use crate::args::PublishFlags;
use crate::factory::CliFactory;
+use crate::graph_util::ModuleGraphBuilder;
use crate::http_util::HttpClient;
+use crate::tools::check::CheckOptions;
+use crate::tools::registry::graph::get_workspace_member_roots;
+use crate::tools::registry::graph::resolve_config_file_roots_from_exports;
+use crate::tools::registry::graph::surface_fast_check_type_graph_errors;
+use crate::tools::registry::graph::MemberRoots;
use crate::util::display::human_size;
use crate::util::glob::PathOrPatternSet;
use crate::util::import_map::ImportMapUnfurler;
mod api;
mod auth;
+mod graph;
mod publish_order;
mod tar;
@@ -41,6 +49,8 @@ use auth::get_auth_method;
use auth::AuthMethod;
use publish_order::PublishOrderGraph;
+use super::check::TypeChecker;
+
use self::tar::PublishableTarball;
fn ring_bell() {
@@ -64,6 +74,15 @@ impl PreparedPublishPackage {
static SUGGESTED_ENTRYPOINTS: [&str; 4] =
["mod.ts", "mod.js", "index.ts", "index.js"];
+fn get_deno_json_package_name(
+ deno_json: &ConfigFile,
+) -> Result<String, AnyError> {
+ match deno_json.json.name.clone() {
+ Some(name) => Ok(name),
+ None => bail!("{} is missing 'name' field", deno_json.specifier),
+ }
+}
+
async fn prepare_publish(
deno_json: &ConfigFile,
import_map: Arc<ImportMap>,
@@ -73,9 +92,7 @@ async fn prepare_publish(
let Some(version) = deno_json.json.version.clone() else {
bail!("{} is missing 'version' field", deno_json.specifier);
};
- let Some(name) = deno_json.json.name.clone() else {
- bail!("{} is missing 'name' field", deno_json.specifier);
- };
+ let name = get_deno_json_package_name(deno_json)?;
if deno_json.json.exports.is_none() {
let mut suggested_entrypoint = None;
@@ -122,6 +139,8 @@ async fn prepare_publish(
})
.await??;
+ log::debug!("Tarball size ({}): {}", name, tarball.bytes.len());
+
Ok(Rc::new(PreparedPublishPackage {
scope: scope.to_string(),
package: package_name.to_string(),
@@ -665,11 +684,26 @@ async fn prepare_packages_for_publishing(
AnyError,
> {
let maybe_workspace_config = deno_json.to_workspace_config()?;
+ let module_graph_builder = cli_factory.module_graph_builder().await?.as_ref();
+ let type_checker = cli_factory.type_checker().await?;
+ let cli_options = cli_factory.cli_options();
let Some(workspace_config) = maybe_workspace_config else {
+ let roots = resolve_config_file_roots_from_exports(&deno_json)?;
+ build_and_check_graph_for_publish(
+ module_graph_builder,
+ type_checker,
+ cli_options,
+ &[MemberRoots {
+ name: get_deno_json_package_name(&deno_json)?,
+ dir_url: deno_json.specifier.join("./").unwrap().clone(),
+ exports: roots,
+ }],
+ )
+ .await?;
let mut prepared_package_by_name = HashMap::with_capacity(1);
let package = prepare_publish(&deno_json, import_map).await?;
- let package_name = package.package.clone();
+ let package_name = format!("@{}/{}", package.scope, package.package);
let publish_order_graph =
PublishOrderGraph::new_single(package_name.clone());
prepared_package_by_name.insert(package_name, package);
@@ -677,14 +711,21 @@ async fn prepare_packages_for_publishing(
};
println!("Publishing a workspace...");
- let mut prepared_package_by_name =
- HashMap::with_capacity(workspace_config.members.len());
- let publish_order_graph = publish_order::build_publish_graph(
- &workspace_config,
- cli_factory.module_graph_builder().await?.as_ref(),
+ // create the module graph
+ let roots = get_workspace_member_roots(&workspace_config)?;
+ let graph = build_and_check_graph_for_publish(
+ module_graph_builder,
+ type_checker,
+ cli_options,
+ &roots,
)
.await?;
+ let mut prepared_package_by_name =
+ HashMap::with_capacity(workspace_config.members.len());
+ let publish_order_graph =
+ publish_order::build_publish_order_graph(&graph, &roots)?;
+
let results =
workspace_config
.members
@@ -712,6 +753,55 @@ async fn prepare_packages_for_publishing(
Ok((publish_order_graph, prepared_package_by_name))
}
+async fn build_and_check_graph_for_publish(
+ module_graph_builder: &ModuleGraphBuilder,
+ type_checker: &TypeChecker,
+ cli_options: &CliOptions,
+ packages: &[MemberRoots],
+) -> Result<Arc<deno_graph::ModuleGraph>, deno_core::anyhow::Error> {
+ let graph = Arc::new(
+ module_graph_builder
+ .create_graph_with_options(crate::graph_util::CreateGraphOptions {
+ // All because we're going to use this same graph to determine the publish order later
+ graph_kind: deno_graph::GraphKind::All,
+ roots: packages
+ .iter()
+ .flat_map(|r| r.exports.iter())
+ .cloned()
+ .collect(),
+ workspace_fast_check: true,
+ loader: None,
+ })
+ .await?,
+ );
+ graph.valid()?;
+ log::info!("Checking fast check type graph for errors...");
+ surface_fast_check_type_graph_errors(&graph, packages)?;
+ log::info!("Ensuring type checks...");
+ let diagnostics = type_checker
+ .check_diagnostics(
+ graph.clone(),
+ CheckOptions {
+ lib: cli_options.ts_type_lib_window(),
+ log_ignored_options: false,
+ reload: cli_options.reload_flag(),
+ },
+ )
+ .await?;
+ if !diagnostics.is_empty() {
+ bail!(
+ concat!(
+ "{:#}\n\n",
+ "You may have discovered a bug in Deno's fast check implementation. ",
+ "Fast check is still early days and we would appreciate if you log a ",
+ "bug if you believe this is one: https://github.com/denoland/deno/issues/"
+ ),
+ diagnostics
+ );
+ }
+ Ok(graph)
+}
+
pub async fn publish(
flags: Flags,
publish_flags: PublishFlags,
@@ -728,10 +818,7 @@ pub async fn publish(
Arc::new(ImportMap::new(Url::parse("file:///dev/null").unwrap()))
});
- let initial_cwd =
- std::env::current_dir().with_context(|| "Failed getting cwd.")?;
-
- let directory_path = initial_cwd.join(publish_flags.directory);
+ let directory_path = cli_factory.cli_options().initial_cwd();
// TODO: doesn't handle jsonc
let deno_json_path = directory_path.join("deno.json");
let deno_json = ConfigFile::read(&deno_json_path).with_context(|| {
diff --git a/cli/tools/registry/publish_order.rs b/cli/tools/registry/publish_order.rs
index 965da8341..4071c42ca 100644
--- a/cli/tools/registry/publish_order.rs
+++ b/cli/tools/registry/publish_order.rs
@@ -4,13 +4,11 @@ use std::collections::HashMap;
use std::collections::HashSet;
use std::collections::VecDeque;
-use deno_ast::ModuleSpecifier;
-use deno_config::WorkspaceConfig;
use deno_core::anyhow::bail;
-use deno_core::anyhow::Context;
use deno_core::error::AnyError;
+use deno_graph::ModuleGraph;
-use crate::graph_util::ModuleGraphBuilder;
+use super::graph::MemberRoots;
pub struct PublishOrderGraph {
packages: HashMap<String, HashSet<String>>,
@@ -122,80 +120,21 @@ impl PublishOrderGraph {
}
}
-pub async fn build_publish_graph(
- workspace_config: &WorkspaceConfig,
- module_graph_builder: &ModuleGraphBuilder,
+pub fn build_publish_order_graph(
+ graph: &ModuleGraph,
+ roots: &[MemberRoots],
) -> Result<PublishOrderGraph, AnyError> {
- let roots = get_workspace_roots(workspace_config)?;
- let graph = module_graph_builder
- .create_graph(
- deno_graph::GraphKind::All,
- roots.iter().flat_map(|r| r.exports.clone()).collect(),
- )
- .await?;
- graph.valid()?;
-
let packages = build_pkg_deps(graph, roots);
- Ok(build_graph(packages))
-}
-
-#[derive(Debug)]
-struct MemberRoots {
- name: String,
- dir_url: ModuleSpecifier,
- exports: Vec<ModuleSpecifier>,
-}
-
-fn get_workspace_roots(
- config: &WorkspaceConfig,
-) -> Result<Vec<MemberRoots>, AnyError> {
- let mut members = Vec::with_capacity(config.members.len());
- let mut seen_names = HashSet::with_capacity(config.members.len());
- for member in &config.members {
- let exports_config = member
- .config_file
- .to_exports_config()
- .with_context(|| {
- format!(
- "Failed to parse exports at {}",
- member.config_file.specifier
- )
- })?
- .into_map();
- if !seen_names.insert(&member.package_name) {
- bail!(
- "Cannot have two workspace packages with the same name ('{}' at {})",
- member.package_name,
- member.path.display(),
- );
- }
- let mut member_root = MemberRoots {
- name: member.package_name.clone(),
- dir_url: member.config_file.specifier.join("./").unwrap().clone(),
- exports: Vec::with_capacity(exports_config.len()),
- };
- for (_, value) in exports_config {
- let entry_point =
- member.config_file.specifier.join(&value).with_context(|| {
- format!(
- "Failed to join {} with {}",
- member.config_file.specifier, value
- )
- })?;
- member_root.exports.push(entry_point);
- }
- members.push(member_root);
- }
- Ok(members)
+ Ok(build_publish_order_graph_from_pkgs_deps(packages))
}
fn build_pkg_deps(
- graph: deno_graph::ModuleGraph,
- roots: Vec<MemberRoots>,
+ graph: &deno_graph::ModuleGraph,
+ roots: &[MemberRoots],
) -> HashMap<String, HashSet<String>> {
let mut members = HashMap::with_capacity(roots.len());
let mut seen_modules = HashSet::with_capacity(graph.modules().count());
- for root in &roots {
+ for root in roots {
let mut deps = HashSet::new();
let mut pending = VecDeque::new();
pending.extend(root.exports.clone());
@@ -243,7 +182,7 @@ fn build_pkg_deps(
members
}
-fn build_graph(
+fn build_publish_order_graph_from_pkgs_deps(
packages: HashMap<String, HashSet<String>>,
) -> PublishOrderGraph {
let mut in_degree = HashMap::new();
@@ -273,7 +212,7 @@ mod test {
#[test]
fn test_graph_no_deps() {
- let mut graph = build_graph(HashMap::from([
+ let mut graph = build_publish_order_graph_from_pkgs_deps(HashMap::from([
("a".to_string(), HashSet::new()),
("b".to_string(), HashSet::new()),
("c".to_string(), HashSet::new()),
@@ -293,7 +232,7 @@ mod test {
#[test]
fn test_graph_single_dep() {
- let mut graph = build_graph(HashMap::from([
+ let mut graph = build_publish_order_graph_from_pkgs_deps(HashMap::from([
("a".to_string(), HashSet::from(["b".to_string()])),
("b".to_string(), HashSet::from(["c".to_string()])),
("c".to_string(), HashSet::new()),
@@ -310,7 +249,7 @@ mod test {
#[test]
fn test_graph_multiple_dep() {
- let mut graph = build_graph(HashMap::from([
+ let mut graph = build_publish_order_graph_from_pkgs_deps(HashMap::from([
(
"a".to_string(),
HashSet::from(["b".to_string(), "c".to_string()]),
@@ -342,7 +281,7 @@ mod test {
#[test]
fn test_graph_circular_dep() {
- let mut graph = build_graph(HashMap::from([
+ let mut graph = build_publish_order_graph_from_pkgs_deps(HashMap::from([
("a".to_string(), HashSet::from(["b".to_string()])),
("b".to_string(), HashSet::from(["c".to_string()])),
("c".to_string(), HashSet::from(["a".to_string()])),