@@ -82,9 +82,9 @@ export let createFileInTempDir = (extension = ""): NormalizedPath => {
8282 return path . join ( os . tmpdir ( ) , tempFileName ) as NormalizedPath ;
8383} ;
8484
85- let findProjectRootOfFileInDir = (
85+ function findProjectRootOfFileInDir (
8686 source : NormalizedPath ,
87- ) : NormalizedPath | null => {
87+ ) : NormalizedPath | null {
8888 const dir = normalizePath ( path . dirname ( source ) ) ;
8989 if ( dir == null ) {
9090 return null ;
@@ -102,18 +102,20 @@ let findProjectRootOfFileInDir = (
102102 return findProjectRootOfFileInDir ( dir ) ;
103103 }
104104 }
105- } ;
105+ }
106106
107- // TODO: races here?
108- export let findProjectRootOfFile = (
107+ /**
108+ * Searches for a project root path in projectsFiles that contains the given file path.
109+ * Excludes exact matches - the source must be a file inside a project, not the project root itself.
110+ */
111+ function findProjectRootContainingFile (
109112 source : NormalizedPath ,
110- allowDir ?: boolean ,
111- ) : NormalizedPath | null => {
112- // First look in project files (keys are already normalized)
113+ ) : NormalizedPath | null {
113114 let foundRootFromProjectFiles : NormalizedPath | null = null ;
114115 for ( const rootPath of projectsFiles . keys ( ) ) {
115116 // Both are normalized, so direct comparison works
116- if ( source . startsWith ( rootPath ) && ( ! allowDir || source !== rootPath ) ) {
117+ // For files, we exclude exact matches (source !== rootPath)
118+ if ( source . startsWith ( rootPath ) && source !== rootPath ) {
117119 // Prefer the longest path (most nested)
118120 if (
119121 foundRootFromProjectFiles == null ||
@@ -124,20 +126,71 @@ export let findProjectRootOfFile = (
124126 }
125127 }
126128
127- if ( foundRootFromProjectFiles != null ) {
128- return foundRootFromProjectFiles ;
129- } else {
130- const isDir = path . extname ( source ) === "" ;
131- const searchPath =
132- isDir && ! allowDir ? path . join ( source , "dummy.res" ) : source ;
133- const normalizedSearchPath = normalizePath ( searchPath ) ;
134- if ( normalizedSearchPath == null ) {
135- return null ;
129+ return foundRootFromProjectFiles ;
130+ }
131+
132+ /**
133+ * Searches for a project root path in projectsFiles that matches the given directory path.
134+ * Allows exact matches - the source can be the project root directory itself.
135+ */
136+ function findProjectRootMatchingDir (
137+ source : NormalizedPath ,
138+ ) : NormalizedPath | null {
139+ let foundRootFromProjectFiles : NormalizedPath | null = null ;
140+ for ( const rootPath of projectsFiles . keys ( ) ) {
141+ // Both are normalized, so direct comparison works
142+ // For directories, we allow exact matches
143+ if ( source . startsWith ( rootPath ) ) {
144+ // Prefer the longest path (most nested)
145+ if (
146+ foundRootFromProjectFiles == null ||
147+ rootPath . length > foundRootFromProjectFiles . length
148+ ) {
149+ foundRootFromProjectFiles = rootPath ;
150+ }
136151 }
137- const foundPath = findProjectRootOfFileInDir ( normalizedSearchPath ) ;
138- return foundPath ;
139152 }
140- } ;
153+
154+ return foundRootFromProjectFiles ;
155+ }
156+
157+ /**
158+ * Finds the project root for a given file path.
159+ * This is the main function used throughout the codebase for finding project roots.
160+ */
161+ export function findProjectRootOfFile (
162+ source : NormalizedPath ,
163+ ) : NormalizedPath | null {
164+ // First look in project files - exclude exact matches since we're looking for a file
165+ let foundRoot = findProjectRootContainingFile ( source ) ;
166+
167+ if ( foundRoot != null ) {
168+ return foundRoot ;
169+ }
170+
171+ // Fallback: search the filesystem
172+ const foundPath = findProjectRootOfFileInDir ( source ) ;
173+ return foundPath ;
174+ }
175+
176+ /**
177+ * Finds the project root for a given directory path.
178+ * This allows exact matches and is used for workspace/lockfile detection.
179+ */
180+ export function findProjectRootOfDir (
181+ source : NormalizedPath ,
182+ ) : NormalizedPath | null {
183+ // First look in project files - allow exact matches since we're looking for a directory
184+ let foundRoot = findProjectRootMatchingDir ( source ) ;
185+
186+ if ( foundRoot != null ) {
187+ return foundRoot ;
188+ }
189+
190+ // Fallback: search the filesystem
191+ const foundPath = findProjectRootOfFileInDir ( source ) ;
192+ return foundPath ;
193+ }
141194
142195/**
143196 * Gets the project file for a given project root path.
@@ -500,7 +553,7 @@ export function computeWorkspaceRootPathFromLockfile(
500553 // if we find a rewatch.lock in the project root, it's a compilation of a local package
501554 // in the workspace.
502555 return ! foundRewatchLockfileInProjectRoot
503- ? findProjectRootOfFile ( projectRootPath , true )
556+ ? findProjectRootOfDir ( projectRootPath )
504557 : null ;
505558}
506559
0 commit comments