summaryrefslogtreecommitdiff
path: root/cli/graph_util.rs
diff options
context:
space:
mode:
Diffstat (limited to 'cli/graph_util.rs')
-rw-r--r--cli/graph_util.rs356
1 files changed, 237 insertions, 119 deletions
diff --git a/cli/graph_util.rs b/cli/graph_util.rs
index ed56cf9f7..81ae21d0d 100644
--- a/cli/graph_util.rs
+++ b/cli/graph_util.rs
@@ -18,6 +18,9 @@ use crate::tools::check;
use crate::tools::check::TypeChecker;
use crate::util::file_watcher::WatcherCommunicator;
use crate::util::fs::canonicalize_path;
+use deno_emit::LoaderChecksum;
+use deno_graph::JsrLoadError;
+use deno_graph::ModuleLoadError;
use deno_runtime::fs_util::specifier_to_file_path;
use deno_config::WorkspaceMemberConfig;
@@ -51,6 +54,9 @@ pub struct GraphValidOptions {
pub check_js: bool,
pub follow_type_only: bool,
pub is_vendoring: bool,
+ /// Whether to exit the process for lockfile errors.
+ /// Otherwise, surfaces lockfile errors as errors.
+ pub exit_lockfile_errors: bool,
}
/// Check if `roots` and their deps are available. Returns `Ok(())` if
@@ -62,10 +68,14 @@ pub struct GraphValidOptions {
/// for the CLI.
pub fn graph_valid(
graph: &ModuleGraph,
- fs: Arc<dyn FileSystem>,
+ fs: &Arc<dyn FileSystem>,
roots: &[ModuleSpecifier],
options: GraphValidOptions,
) -> Result<(), AnyError> {
+ if options.exit_lockfile_errors {
+ graph_exit_lock_errors(graph);
+ }
+
let mut errors = graph
.walk(
roots,
@@ -95,8 +105,10 @@ pub fn graph_valid(
enhanced_resolution_error_message(resolution_error)
)
}
- ModuleGraphError::ModuleError(e) => {
- enhanced_module_error_message(fs.clone(), e)
+ ModuleGraphError::ModuleError(error) => {
+ enhanced_lockfile_error_message(error)
+ .or_else(|| enhanced_sloppy_imports_error_message(fs, error))
+ .unwrap_or_else(|| format!("{}", error))
}
};
@@ -152,40 +164,16 @@ pub fn graph_valid(
}
}
-/// Checks the lockfile against the graph and exits on errors.
-pub fn graph_lock_or_exit(graph: &ModuleGraph, lockfile: &mut Lockfile) {
- for module in graph.modules() {
- let source = match module {
- Module::Js(module) if module.media_type.is_declaration() => continue, // skip declaration files
- Module::Js(module) => &module.source,
- Module::Json(module) => &module.source,
- Module::Node(_) | Module::Npm(_) | Module::External(_) => continue,
- };
-
- // skip over any specifiers in JSR packages because those
- // are enforced via the integrity
- if deno_graph::source::recommended_registry_package_url_to_nv(
- jsr_url(),
- module.specifier(),
- )
- .is_some()
- {
- continue;
- }
+pub fn graph_exit_lock_errors(graph: &ModuleGraph) {
+ for error in graph.module_errors() {
+ exit_for_lockfile_error(error);
+ }
+}
- if !lockfile.check_or_insert_remote(module.specifier().as_str(), source) {
- let err = format!(
- concat!(
- "The source code is invalid, as it does not match the expected hash in the lock file.\n",
- " Specifier: {}\n",
- " Lock file: {}",
- ),
- module.specifier(),
- lockfile.filename.display(),
- );
- log::error!("{} {}", colors::red("error:"), err);
- std::process::exit(10);
- }
+fn exit_for_lockfile_error(err: &ModuleError) {
+ if let Some(err_message) = enhanced_lockfile_error_message(err) {
+ log::error!("{} {}", colors::red("error:"), err_message);
+ std::process::exit(10);
}
}
@@ -201,7 +189,6 @@ pub struct ModuleGraphCreator {
options: Arc<CliOptions>,
npm_resolver: Arc<dyn CliNpmResolver>,
module_graph_builder: Arc<ModuleGraphBuilder>,
- lockfile: Option<Arc<Mutex<Lockfile>>>,
type_checker: Arc<TypeChecker>,
}
@@ -210,13 +197,11 @@ impl ModuleGraphCreator {
options: Arc<CliOptions>,
npm_resolver: Arc<dyn CliNpmResolver>,
module_graph_builder: Arc<ModuleGraphBuilder>,
- lockfile: Option<Arc<Mutex<Lockfile>>>,
type_checker: Arc<TypeChecker>,
) -> Self {
Self {
options,
npm_resolver,
- lockfile,
module_graph_builder,
type_checker,
}
@@ -317,9 +302,6 @@ impl ModuleGraphCreator {
.await?;
self.graph_valid(&graph)?;
- if let Some(lockfile) = &self.lockfile {
- graph_lock_or_exit(&graph, &mut lockfile.lock());
- }
if self.options.type_check_mode().is_true() {
// provide the graph to the type checker, then get it back after it's done
@@ -426,6 +408,67 @@ impl ModuleGraphBuilder {
}
}
+ struct LockfileLocker<'a>(&'a Mutex<Lockfile>);
+
+ impl<'a> deno_graph::source::Locker for LockfileLocker<'a> {
+ fn get_remote_checksum(
+ &self,
+ specifier: &deno_ast::ModuleSpecifier,
+ ) -> Option<LoaderChecksum> {
+ self
+ .0
+ .lock()
+ .remote()
+ .get(specifier.as_str())
+ .map(|s| LoaderChecksum::new(s.clone()))
+ }
+
+ fn has_remote_checksum(
+ &self,
+ specifier: &deno_ast::ModuleSpecifier,
+ ) -> bool {
+ self.0.lock().remote().contains_key(specifier.as_str())
+ }
+
+ fn set_remote_checksum(
+ &mut self,
+ specifier: &deno_ast::ModuleSpecifier,
+ checksum: LoaderChecksum,
+ ) {
+ self
+ .0
+ .lock()
+ .insert_remote(specifier.to_string(), checksum.into_string())
+ }
+
+ fn get_pkg_manifest_checksum(
+ &self,
+ package_nv: &PackageNv,
+ ) -> Option<LoaderChecksum> {
+ self
+ .0
+ .lock()
+ .content
+ .packages
+ .jsr
+ .get(&package_nv.to_string())
+ .map(|s| LoaderChecksum::new(s.integrity.clone()))
+ }
+
+ fn set_pkg_manifest_checksum(
+ &mut self,
+ package_nv: &PackageNv,
+ checksum: LoaderChecksum,
+ ) {
+ // a value would only exist in here if two workers raced
+ // to insert the same package manifest checksum
+ self
+ .0
+ .lock()
+ .insert_package(package_nv.to_string(), checksum.into_string());
+ }
+ }
+
let maybe_imports = self.options.to_maybe_imports()?;
let parser = self.parsed_source_cache.as_capturing_parser();
let analyzer = self.module_info_cache.as_module_analyzer(&parser);
@@ -442,6 +485,10 @@ impl ModuleGraphBuilder {
.map(|r| r.as_reporter());
let workspace_members =
self.options.resolve_deno_graph_workspace_members()?;
+ let mut locker = self
+ .lockfile
+ .as_ref()
+ .map(|lockfile| LockfileLocker(lockfile));
self
.build_graph_with_npm_resolution_and_build_options(
graph,
@@ -459,6 +506,7 @@ impl ModuleGraphBuilder {
module_analyzer: &analyzer,
reporter: maybe_file_watcher_reporter,
resolver: Some(graph_resolver),
+ locker: locker.as_mut().map(|l| l as _),
},
)
.await
@@ -468,7 +516,7 @@ impl ModuleGraphBuilder {
&self,
graph: &mut ModuleGraph,
roots: Vec<ModuleSpecifier>,
- loader: &mut dyn deno_graph::source::Loader,
+ loader: &'a mut dyn deno_graph::source::Loader,
options: deno_graph::BuildOptions<'a>,
) -> Result<(), AnyError> {
// ensure an "npm install" is done if the user has explicitly
@@ -479,8 +527,10 @@ impl ModuleGraphBuilder {
}
}
- // add the lockfile redirects to the graph if it's the first time executing
- if graph.redirects.is_empty() {
+ // fill the graph with the information from the lockfile
+ let is_first_execution = graph.roots.is_empty();
+ if is_first_execution {
+ // populate the information from the lockfile
if let Some(lockfile) = &self.lockfile {
let lockfile = lockfile.lock();
for (from, to) in &lockfile.content.redirects {
@@ -492,13 +542,6 @@ impl ModuleGraphBuilder {
}
}
}
- }
- }
-
- // add the jsr specifiers to the graph if it's the first time executing
- if graph.packages.is_empty() {
- if let Some(lockfile) = &self.lockfile {
- let lockfile = lockfile.lock();
for (key, value) in &lockfile.content.packages.specifiers {
if let Some(key) = key
.strip_prefix("jsr:")
@@ -512,59 +555,15 @@ impl ModuleGraphBuilder {
}
}
}
- for (nv, value) in &lockfile.content.packages.jsr {
- if let Ok(nv) = PackageNv::from_str(nv) {
- graph
- .packages
- .add_manifest_checksum(nv, value.integrity.clone())
- .map_err(|err| deno_lockfile::IntegrityCheckFailedError {
- package_display_id: format!("jsr:{}", err.nv),
- actual: err.actual,
- expected: err.expected,
- filename: lockfile.filename.display().to_string(),
- })?;
- }
- }
}
}
- graph.build(roots, loader, options).await;
-
- // add the redirects in the graph to the lockfile
- if !graph.redirects.is_empty() {
- if let Some(lockfile) = &self.lockfile {
- let graph_redirects = graph.redirects.iter().filter(|(from, _)| {
- !matches!(from.scheme(), "npm" | "file" | "deno")
- });
- let mut lockfile = lockfile.lock();
- for (from, to) in graph_redirects {
- lockfile.insert_redirect(from.to_string(), to.to_string());
- }
- }
- }
+ let initial_redirects_len = graph.redirects.len();
+ let initial_package_deps_len = graph.packages.package_deps_sum();
+ let initial_package_mappings_len = graph.packages.mappings().len();
+ let initial_npm_packages = graph.npm_packages.len();
- // add the jsr specifiers in the graph to the lockfile
- if !graph.packages.is_empty() {
- if let Some(lockfile) = &self.lockfile {
- let mappings = graph.packages.mappings();
- let mut lockfile = lockfile.lock();
- for (from, to) in mappings {
- lockfile.insert_package_specifier(
- format!("jsr:{}", from),
- format!("jsr:{}", to),
- );
- }
- for (name, checksum, deps) in
- graph.packages.packages_with_checksum_and_deps()
- {
- lockfile.insert_package(
- name.to_string(),
- checksum.clone(),
- deps.map(|s| s.to_string()),
- );
- }
- }
- }
+ graph.build(roots, loader, options).await;
if let Some(npm_resolver) = self.npm_resolver.as_managed() {
// ensure that the top level package.json is installed if a
@@ -578,6 +577,53 @@ impl ModuleGraphBuilder {
npm_resolver.resolve_pending().await?;
}
+ let has_redirects_changed = graph.redirects.len() != initial_redirects_len;
+ let has_jsr_package_deps_changed =
+ graph.packages.package_deps_sum() != initial_package_deps_len;
+ let has_jsr_package_mappings_changed =
+ graph.packages.mappings().len() != initial_package_mappings_len;
+ let has_npm_packages_changed =
+ graph.npm_packages.len() != initial_npm_packages;
+
+ if has_redirects_changed
+ || has_jsr_package_deps_changed
+ || has_jsr_package_mappings_changed
+ || has_npm_packages_changed
+ {
+ if let Some(lockfile) = &self.lockfile {
+ let mut lockfile = lockfile.lock();
+ // https redirects
+ if has_redirects_changed {
+ let graph_redirects = graph.redirects.iter().filter(|(from, _)| {
+ !matches!(from.scheme(), "npm" | "file" | "deno")
+ });
+ for (from, to) in graph_redirects {
+ lockfile.insert_redirect(from.to_string(), to.to_string());
+ }
+ }
+ // jsr package mappings
+ if has_jsr_package_mappings_changed {
+ for (from, to) in graph.packages.mappings() {
+ lockfile.insert_package_specifier(
+ format!("jsr:{}", from),
+ format!("jsr:{}", to),
+ );
+ }
+ }
+ // jsr packages
+ if has_jsr_package_deps_changed {
+ for (name, deps) in graph.packages.packages_with_deps() {
+ lockfile
+ .add_package_deps(&name.to_string(), deps.map(|s| s.to_string()));
+ }
+ }
+ // npm packages
+ if has_npm_packages_changed {
+ self.npm_resolver.as_managed().unwrap().lock(&mut lockfile);
+ }
+ }
+ }
+
Ok(())
}
@@ -658,12 +704,13 @@ impl ModuleGraphBuilder {
) -> Result<(), AnyError> {
graph_valid(
graph,
- self.fs.clone(),
+ &self.fs,
roots,
GraphValidOptions {
is_vendoring: false,
follow_type_only: self.options.type_check_mode().is_true(),
check_js: self.options.check_js(),
+ exit_lockfile_errors: true,
},
)
}
@@ -701,28 +748,99 @@ pub fn enhanced_resolution_error_message(error: &ResolutionError) -> String {
message
}
-pub fn enhanced_module_error_message(
- fs: Arc<dyn FileSystem>,
+fn enhanced_sloppy_imports_error_message(
+ fs: &Arc<dyn FileSystem>,
error: &ModuleError,
-) -> String {
- let additional_message = match error {
- ModuleError::LoadingErr(specifier, _, _) // ex. "Is a directory" error
+) -> Option<String> {
+ match error {
+ ModuleError::LoadingErr(specifier, _, ModuleLoadError::Loader(_)) // ex. "Is a directory" error
| ModuleError::Missing(specifier, _) => {
- SloppyImportsResolver::new(fs).resolve(
+ let additional_message = SloppyImportsResolver::new(fs.clone())
+ .resolve(specifier, ResolutionMode::Execution)
+ .as_suggestion_message()?;
+ Some(format!(
+ "{} {} or run with --unstable-sloppy-imports",
+ error,
+ additional_message,
+ ))
+ }
+ _ => None,
+ }
+}
+
+fn enhanced_lockfile_error_message(err: &ModuleError) -> Option<String> {
+ match err {
+ ModuleError::LoadingErr(
+ specifier,
+ _,
+ ModuleLoadError::Jsr(JsrLoadError::ContentChecksumIntegrity(
+ checksum_err,
+ )),
+ ) => {
+ Some(format!(
+ concat!(
+ "Integrity check failed in package. The package may have been tampered with.\n\n",
+ " Specifier: {}\n",
+ " Actual: {}\n",
+ " Expected: {}\n\n",
+ "If you modified your global cache, run again with the --reload flag to restore ",
+ "its state. If you want to modify dependencies locally run again with the ",
+ "--vendor flag or specify `\"vendor\": true` in a deno.json then modify the contents ",
+ "of the vendor/ folder."
+ ),
specifier,
- ResolutionMode::Execution,
- )
- .as_suggestion_message()
+ checksum_err.actual,
+ checksum_err.expected,
+ ))
+ }
+ ModuleError::LoadingErr(
+ _specifier,
+ _,
+ ModuleLoadError::Jsr(
+ JsrLoadError::PackageVersionManifestChecksumIntegrity(
+ package_nv,
+ checksum_err,
+ ),
+ ),
+ ) => {
+ Some(format!(
+ concat!(
+ "Integrity check failed for package. The source code is invalid, as it does not match the expected hash in the lock file.\n\n",
+ " Package: {}\n",
+ " Actual: {}\n",
+ " Expected: {}\n\n",
+ "This could be caused by:\n",
+ " * the lock file may be corrupt\n",
+ " * the source itself may be corrupt\n\n",
+ "Use the --lock-write flag to regenerate the lockfile or --reload to reload the source code from the server."
+ ),
+ package_nv,
+ checksum_err.actual,
+ checksum_err.expected,
+ ))
+ }
+ ModuleError::LoadingErr(
+ specifier,
+ _,
+ ModuleLoadError::HttpsChecksumIntegrity(checksum_err),
+ ) => {
+ Some(format!(
+ concat!(
+ "Integrity check failed for remote specifier. The source code is invalid, as it does not match the expected hash in the lock file.\n\n",
+ " Specifier: {}\n",
+ " Actual: {}\n",
+ " Expected: {}\n\n",
+ "This could be caused by:\n",
+ " * the lock file may be corrupt\n",
+ " * the source itself may be corrupt\n\n",
+ "Use the --lock-write flag to regenerate the lockfile or --reload to reload the source code from the server."
+ ),
+ specifier,
+ checksum_err.actual,
+ checksum_err.expected,
+ ))
}
_ => None,
- };
- if let Some(message) = additional_message {
- format!(
- "{} {} or run with --unstable-sloppy-imports",
- error, message
- )
- } else {
- format!("{}", error)
}
}