Repository: vercel/next.js
Pull request: Turbopack: correctly trace files with npm

The `.contains("/node_modules/")` condition didn't work for npm, where the path might be `"node_modules/.prisma/client"`

This fixes cases of the following error with Prisma and npm
```
Prisma Client could not locate the Query Engine for runtime "rhel-openssl-3.0.x".

We detected that you are using Next.js, learn how to fix this: https://pris.ly/d/engine-not-found-nextjs.

This is likely caused by tooling that has not copied "libquery_engine-rhel-openssl-3.0.x.so.node" to the deployment folder.
Ensure that you ran `prisma generate` and that "libquery_engine-rhel-openssl-3.0.x.so.node" has been copied to "node_modules/.prisma/client".

We would appreciate if you could take the time to share some information with us.
Please help us by answering a few questions: https://pris.ly/engine-not-found-tooling-investigation

The following locations have been searched:
  /var/task/node_modules/.prisma/client
  /var/task/node_modules/@prisma/client
  /vercel/path0/node_modules/@prisma/client
  /tmp/prisma-engines

```

Changed files:
filename: crates/next-api/src/module_graph.rs
patch: @@ -568,14 +568,7 @@ async fn validate_pages_css_imports(
         .map(async |issue| {
             // We allow imports of global CSS files which are inside of `node_modules`.
             Ok(
-                if !issue
-                    .module
-                    .ident()
-                    .path()
-                    .await?
-                    .path
-                    .contains("/node_modules/")
-                {
+                if !issue.module.ident().path().await?.is_in_node_modules() {
                     Some(issue)
                 } else {
                     None

filename: turbopack/crates/turbo-tasks-fs/src/lib.rs
patch: @@ -1239,6 +1239,10 @@ impl FileSystemPath {
         self.path.is_empty()
     }
 
+    pub fn is_in_node_modules(&self) -> bool {
+        self.path.starts_with("node_modules/") || self.path.contains("/node_modules/")
+    }
+
     /// Returns the path of `inner` relative to `self`.
     ///
     /// Note: this method always strips the leading `/` from the result.

filename: turbopack/crates/turbopack-ecmascript/src/references/mod.rs
patch: @@ -974,12 +974,7 @@ async fn analyze_ecmascript_module_internal(
             origin,
             compile_time_info,
             var_graph: &var_graph,
-            allow_project_root_tracing: !source
-                .ident()
-                .path()
-                .await?
-                .path
-                .contains("/node_modules/"),
+            allow_project_root_tracing: !source.ident().path().await?.is_in_node_modules(),
             fun_args_values: Default::default(),
             var_cache: Default::default(),
             first_import_meta: true,
@@ -1742,15 +1737,21 @@ async fn handle_call<G: Fn(Vec<Effect>) + Send + Sync>(
         return Ok(());
     }
 
-    let get_traced_project_dir = async || {
+    let get_traced_project_dir = async || -> Result<FileSystemPath> {
         // readFileSync("./foo") should always be relative to the project root, but this is
-        // dangerous inside of node_modules as it can cause a lot of false positives in the
-        // tracing, if some package does `path.join(dynamic)`, it would include everything from
-        // the project root as well.
-        if allow_project_root_tracing {
-            compile_time_info.environment().cwd().owned().await
+        // dangerous inside of node_modules as it can cause a lot of false positives in the tracing,
+        // if some package does `path.join(dynamic)`, it would include everything from the project
+        // root as well.
+        //
+        // Also, when there's no cwd set (i.e. in a tracing-specific module context, as we shouldn't
+        // assume a `process.cwd()` for all of node_modules), fallback to the source file directory.
+        // This still allows relative file accesses, just not from the project root.
+        if allow_project_root_tracing
+            && let Some(cwd) = compile_time_info.environment().cwd().owned().await?
+        {
+            Ok(cwd)
         } else {
-            Ok(Some(source.ident().path().await?.parent()))
+            Ok(source.ident().path().await?.parent())
         }
     };
 
@@ -1982,18 +1983,16 @@ async fn handle_call<G: Fn(Vec<Effect>) + Send + Sync>(
                         return Ok(());
                     }
                 }
-                if let Some(context_dir) = get_traced_project_dir().await? {
-                    analysis.add_reference(
-                        FileSourceReference::new(
-                            context_dir,
-                            Pattern::new(pat),
-                            collect_affecting_sources,
-                            make_issue_source(),
-                        )
-                        .to_resolved()
-                        .await?,
-                    );
-                }
+                analysis.add_reference(
+                    FileSourceReference::new(
+                        get_traced_project_dir().await?,
+                        Pattern::new(pat),
+                        collect_affecting_sources,
+                        make_issue_source(),
+                    )
+                    .to_resolved()
+                    .await?,
+                );
                 return Ok(());
             }
             let (args, hints) = explain_args(&args);
@@ -2038,13 +2037,15 @@ async fn handle_call<G: Fn(Vec<Effect>) + Send + Sync>(
                     return Ok(());
                 }
             }
-            if let Some(context_dir) = get_traced_project_dir().await? {
-                analysis.add_reference(
-                    DirAssetReference::new(context_dir, Pattern::new(pat), make_issue_source())
-                        .to_resolved()
-                        .await?,
-                );
-            }
+            analysis.add_reference(
+                DirAssetReference::new(
+                    get_traced_project_dir().await?,
+                    Pattern::new(pat),
+                    make_issue_source(),
+                )
+                .to_resolved()
+                .await?,
+            );
             return Ok(());
         }
 
@@ -2080,13 +2081,15 @@ async fn handle_call<G: Fn(Vec<Effect>) + Send + Sync>(
                     return Ok(());
                 }
             }
-            if let Some(context_dir) = get_traced_project_dir().await? {
-                analysis.add_reference(
-                    DirAssetReference::new(context_dir, Pattern::new(pat), make_issue_source())
-                        .to_resolved()
-                        .await?,
-                );
-            }
+            analysis.add_reference(
+                DirAssetReference::new(
+                    get_traced_project_dir().await?,
+                    Pattern::new(pat),
+                    make_issue_source(),
+                )
+                .to_resolved()
+                .await?,
+            );
             return Ok(());
         }
         JsValue::WellKnownFunction(WellKnownFunctionKind::ChildProcessSpawnMethod(name))
@@ -2130,12 +2133,10 @@ async fn handle_call<G: Fn(Vec<Effect>) + Send + Sync>(
                 if dynamic {
                     show_dynamic_warning = true;
                 }
-                if (!dynamic || !ignore_dynamic_requests)
-                    && let Some(context_dir) = get_traced_project_dir().await?
-                {
+                if !dynamic || !ignore_dynamic_requests {
                     analysis.add_reference(
                         FileSourceReference::new(
-                            context_dir,
+                            get_traced_project_dir().await?,
                             Pattern::new(pat),
                             collect_affecting_sources,
                             IssueSource::from_swc_offsets(
@@ -2371,17 +2372,15 @@ async fn handle_call<G: Fn(Vec<Effect>) + Send + Sync>(
                                     .await?;
                                 js_value_to_pattern(&linked_func_call)
                             };
-                            if let Some(context_dir) = get_traced_project_dir().await? {
-                                analysis.add_reference(
-                                    DirAssetReference::new(
-                                        context_dir,
-                                        Pattern::new(abs_pattern),
-                                        make_issue_source(),
-                                    )
-                                    .to_resolved()
-                                    .await?,
-                                );
-                            }
+                            analysis.add_reference(
+                                DirAssetReference::new(
+                                    get_traced_project_dir().await?,
+                                    Pattern::new(abs_pattern),
+                                    make_issue_source(),
+                                )
+                                .to_resolved()
+                                .await?,
+                            );
                             return Ok(());
                         }
                     }
@@ -2440,17 +2439,15 @@ async fn handle_call<G: Fn(Vec<Effect>) + Send + Sync>(
                         .await?;
                     js_value_to_pattern(&linked_func_call)
                 };
-                if let Some(context_dir) = get_traced_project_dir().await? {
-                    analysis.add_reference(
-                        DirAssetReference::new(
-                            context_dir,
-                            Pattern::new(abs_pattern),
-                            make_issue_source(),
-                        )
-                        .to_resolved()
-                        .await?,
-                    );
-                }
+                analysis.add_reference(
+                    DirAssetReference::new(
+                        get_traced_project_dir().await?,
+                        Pattern::new(abs_pattern),
+                        make_issue_source(),
+                    )
+                    .to_resolved()
+                    .await?,
+                );
                 return Ok(());
             }
             let (args, hints) = explain_args(&args);
@@ -2498,33 +2495,32 @@ async fn handle_call<G: Fn(Vec<Effect>) + Send + Sync>(
             if args.len() == 2
                 && let Some(JsValue::Object { parts, .. }) = args.get(1)
             {
-                if let Some(context_dir) = get_traced_project_dir().await? {
-                    let resolved_dirs = parts
-                        .iter()
-                        .filter_map(|object_part| match object_part {
-                            ObjectPart::KeyValue(
-                                JsValue::Constant(key),
-                                JsValue::Array { items: dirs, .. },
-                            ) if key.as_str() == Some("includeDirs") => {
-                                Some(dirs.iter().filter_map(|dir| dir.as_str()))
-                            }
-                            _ => None,
-                        })
-                        .flatten()
-                        .map(|dir| {
-                            DirAssetReference::new(
-                                context_dir.clone(),
-                                Pattern::new(Pattern::Constant(dir.into())),
-                                make_issue_source(),
-                            )
-                            .to_resolved()
-                        })
-                        .try_join()
-                        .await?;
+                let context_dir = get_traced_project_dir().await?;
+                let resolved_dirs = parts
+                    .iter()
+                    .filter_map(|object_part| match object_part {
+                        ObjectPart::KeyValue(
+                            JsValue::Constant(key),
+                            JsValue::Array { items: dirs, .. },
+                        ) if key.as_str() == Some("includeDirs") => {
+                            Some(dirs.iter().filter_map(|dir| dir.as_str()))
+                        }
+                        _ => None,
+                    })
+                    .flatten()
+                    .map(|dir| {
+                        DirAssetReference::new(
+                            context_dir.clone(),
+                            Pattern::new(Pattern::Constant(dir.into())),
+                            make_issue_source(),
+                        )
+                        .to_resolved()
+                    })
+                    .try_join()
+                    .await?;
 
-                    for resolved_dir_ref in resolved_dirs {
-                        analysis.add_reference(resolved_dir_ref);
-                    }
+                for resolved_dir_ref in resolved_dirs {
+                    analysis.add_reference(resolved_dir_ref);
                 }
 
                 return Ok(());