@@ -69,287 +69,113 @@ export interface MonorepoPackage {
6969export class MonorepoUtils {
7070 /**
7171 * Check if a directory contains a monorepo configuration
72+ * Now determines monorepo by counting package.json files (>= 2 means monorepo)
7273 */
7374 static isMonorepo ( rootPath : string ) : boolean {
74- const edenConfigPath = path . join ( rootPath , 'eden.monorepo.json' ) ;
75- const pnpmWorkspacePath = path . join ( rootPath , 'pnpm-workspace.yaml' ) ;
76- // const yarnWorkspacePath = path.join(rootPath, 'yarn.lock');
77- const lernaConfigPath = path . join ( rootPath , 'lerna.json' ) ;
78-
79- return fs . existsSync ( edenConfigPath ) ||
80- fs . existsSync ( pnpmWorkspacePath ) ||
81- // fs.existsSync(yarnWorkspacePath) ||
82- fs . existsSync ( lernaConfigPath ) ;
83- }
84-
85- /**
86- * Detect monorepo type and return configuration file path
87- */
88- static detectMonorepoType ( rootPath : string ) : { type : string ; configPath : string } | null {
89- const edenConfigPath = path . join ( rootPath , 'eden.monorepo.json' ) ;
90- const pnpmWorkspacePath = path . join ( rootPath , 'pnpm-workspace.yaml' ) ;
91- // const yarnWorkspacePath = path.join(rootPath, 'yarn.lock');
92- const lernaConfigPath = path . join ( rootPath , 'lerna.json' ) ;
93-
94- if ( fs . existsSync ( edenConfigPath ) ) {
95- return { type : 'eden' , configPath : edenConfigPath } ;
96- }
97- if ( fs . existsSync ( pnpmWorkspacePath ) ) {
98- return { type : 'pnpm' , configPath : pnpmWorkspacePath } ;
99- }
100- // if (fs.existsSync(yarnWorkspacePath)) {
101- // return { type: 'yarn', configPath: yarnWorkspacePath };
102- // }
103- if ( fs . existsSync ( lernaConfigPath ) ) {
104- return { type : 'lerna' , configPath : lernaConfigPath } ;
105- }
106-
107- return null ;
75+ const packageJsonCount = this . countPackageJsonFiles ( rootPath ) ;
76+ return packageJsonCount >= 2 ;
10877 }
10978
11079 /**
111- * Parse Eden monorepo configuration
80+ * Count the number of package.json files in the directory tree
11281 */
113- static parseEdenMonorepoConfig ( configPath : string ) : EdenMonorepoConfig | null {
82+ private static countPackageJsonFiles ( rootPath : string ) : number {
11483 try {
115- if ( ! fs . existsSync ( configPath ) ) {
116- return null ;
84+ let count = 0 ;
85+ const items = fs . readdirSync ( rootPath ) ;
86+
87+ for ( const item of items ) {
88+ const fullPath = path . join ( rootPath , item ) ;
89+ const stat = fs . statSync ( fullPath ) ;
90+
91+ if ( stat . isDirectory ( ) ) {
92+ // Skip node_modules and hidden directories
93+ if ( item === 'node_modules' || item . startsWith ( '.' ) ) {
94+ continue ;
95+ }
96+
97+ // Check if this directory has a package.json
98+ const packageJsonPath = path . join ( fullPath , 'package.json' ) ;
99+ if ( fs . existsSync ( packageJsonPath ) ) {
100+ count ++ ;
101+ }
102+
103+ // Recursively count in subdirectories
104+ count += this . countPackageJsonFiles ( fullPath ) ;
105+ }
117106 }
118-
119- const configContent = fs . readFileSync ( configPath , 'utf-8' ) ;
120- const config : EdenMonorepoConfig = JSON . parse ( configContent ) ;
121-
122- return config ;
107+
108+ return count ;
123109 } catch ( error ) {
124- console . warn ( `Failed to parse Eden monorepo config at ${ configPath } :` , error ) ;
125- return null ;
110+ console . warn ( `Error counting package.json files in ${ rootPath } :` , error ) ;
111+ return 0 ;
126112 }
127113 }
128114
129115 /**
130- * Get packages from Eden monorepo configuration
131- * Supports packages array, workspaces array, and pnpmWorkspace formats
132- * pnpmWorkspace has the highest priority (emo >= 3.6.0)
116+ * Get all packages from a monorepo
117+ * Unified approach: discover packages by package.json files only
133118 */
134- static getEdenPackages ( rootPath : string , config : EdenMonorepoConfig ) : MonorepoPackage [ ] {
135- const packages : MonorepoPackage [ ] = [ ] ;
136-
137- if ( config . pnpmWorkspace && config . pnpmWorkspace . packages && config . pnpmWorkspace . packages . length > 0 ) {
138- for ( const workspace of config . pnpmWorkspace . packages ) {
139- const workspacePackages = this . expandWorkspacePattern ( rootPath , workspace ) ;
140- packages . push ( ...workspacePackages ) ;
141- }
142- return packages ; // Return early if pnpmWorkspace is configured
143- }
144-
145- // Handle new workspaces array format
146- if ( config . workspaces && config . workspaces . length > 0 ) {
147- for ( const workspace of config . workspaces ) {
148- const workspacePackages = this . expandWorkspacePattern ( rootPath , workspace ) ;
149- packages . push ( ...workspacePackages ) ;
150- }
151- }
152-
153- // Handle legacy packages array format
154- if ( config . packages && config . packages . length > 0 ) {
155- for ( const pkg of config . packages ) {
156- const absolutePath = path . resolve ( rootPath , pkg . path ) ;
119+ static getMonorepoPackages ( rootPath : string ) : MonorepoPackage [ ] {
120+ // Simply use generic package discovery, ignoring monorepo type
121+ return this . getGenericPackages ( rootPath ) ;
122+ }
157123
158- // Check if package directory exists
159- if ( fs . existsSync ( absolutePath ) ) {
160- // Try to get package name from package.json
161- let packageName : string | undefined ;
162- const packageJsonPath = path . join ( absolutePath , 'package.json' ) ;
163124
164- if ( fs . existsSync ( packageJsonPath ) ) {
165- try {
166- const packageJson = JSON . parse ( fs . readFileSync ( packageJsonPath , 'utf-8' ) ) ;
167- packageName = packageJson . name ;
168- } catch ( error ) {
169- console . warn ( `Failed to parse package.json at ${ packageJsonPath } :` , error ) ;
170- }
171- }
172-
173- packages . push ( {
174- path : pkg . path ,
175- absolutePath,
176- shouldPublish : pkg . shouldPublish ?? false ,
177- name : packageName
178- } ) ;
179- } else {
180- console . warn ( `Package directory does not exist: ${ absolutePath } ` ) ;
181- }
182- }
183- }
184125
126+ /**
127+ * Get packages by discovering package.json files (generic approach)
128+ */
129+ private static getGenericPackages ( rootPath : string ) : MonorepoPackage [ ] {
130+ const packages : MonorepoPackage [ ] = [ ] ;
131+ this . discoverPackagesRecursive ( rootPath , rootPath , packages ) ;
185132 return packages ;
186133 }
187134
188135 /**
189- * Expand workspace pattern to find actual packages
190- * Supports glob patterns like "packages/*", "apps/*", etc.
136+ * Recursively discover packages by finding package.json files
191137 */
192- private static expandWorkspacePattern ( rootPath : string , pattern : string ) : MonorepoPackage [ ] {
193- const packages : MonorepoPackage [ ] = [ ] ;
194-
138+ private static discoverPackagesRecursive ( rootPath : string , currentDir : string , packages : MonorepoPackage [ ] ) : void {
195139 try {
196- // Handle glob patterns
197- if ( pattern . includes ( '*' ) ) {
198- const basePath = pattern . replace ( '/*' , '' ) ;
199- const baseDir = path . resolve ( rootPath , basePath ) ;
200-
201- if ( fs . existsSync ( baseDir ) ) {
202- const entries = fs . readdirSync ( baseDir , { withFileTypes : true } ) ;
203-
204- for ( const entry of entries ) {
205- if ( entry . isDirectory ( ) ) {
206- const packagePath = path . join ( basePath , entry . name ) ;
207- const absolutePath = path . resolve ( rootPath , packagePath ) ;
208- const packageJsonPath = path . join ( absolutePath , 'package.json' ) ;
209-
210- // Only include directories that have package.json
211- if ( fs . existsSync ( packageJsonPath ) ) {
212- let packageName : string | undefined ;
213-
214- try {
215- const packageJson = JSON . parse ( fs . readFileSync ( packageJsonPath , 'utf-8' ) ) ;
216- packageName = packageJson . name ;
217- } catch ( error ) {
218- console . warn ( `Failed to parse package.json at ${ packageJsonPath } :` , error ) ;
219- }
220-
221- packages . push ( {
222- path : packagePath ,
223- absolutePath,
224- shouldPublish : false , // Default to false for workspace packages
225- name : packageName
226- } ) ;
227- }
228- }
229- }
140+ const items = fs . readdirSync ( currentDir , { withFileTypes : true } ) ;
141+
142+ for ( const item of items ) {
143+ if ( ! item . isDirectory ( ) ) {
144+ continue ;
230145 }
231- } else {
232- // Handle exact path
233- const absolutePath = path . resolve ( rootPath , pattern ) ;
234- const packageJsonPath = path . join ( absolutePath , 'package.json' ) ;
235-
146+
147+ const dirName = item . name ;
148+ const fullPath = path . join ( currentDir , dirName ) ;
149+
150+ // Skip node_modules and hidden directories
151+ if ( dirName === 'node_modules' || dirName . startsWith ( '.' ) ) {
152+ continue ;
153+ }
154+
155+ // Check if this directory has a package.json
156+ const packageJsonPath = path . join ( fullPath , 'package.json' ) ;
236157 if ( fs . existsSync ( packageJsonPath ) ) {
237- let packageName : string | undefined ;
238-
239158 try {
240159 const packageJson = JSON . parse ( fs . readFileSync ( packageJsonPath , 'utf-8' ) ) ;
241- packageName = packageJson . name ;
160+ const relativePath = path . relative ( rootPath , fullPath ) ;
161+
162+ packages . push ( {
163+ path : relativePath ,
164+ absolutePath : fullPath ,
165+ shouldPublish : false , // Default to false for generic discovery
166+ name : packageJson . name
167+ } ) ;
242168 } catch ( error ) {
243169 console . warn ( `Failed to parse package.json at ${ packageJsonPath } :` , error ) ;
244170 }
245-
246- packages . push ( {
247- path : pattern ,
248- absolutePath,
249- shouldPublish : false ,
250- name : packageName
251- } ) ;
252171 }
172+
173+ // Recursively search in subdirectories
174+ this . discoverPackagesRecursive ( rootPath , fullPath , packages ) ;
253175 }
254176 } catch ( error ) {
255- console . warn ( `Failed to expand workspace pattern " ${ pattern } " :` , error ) ;
177+ console . warn ( `Error discovering packages in ${ currentDir } :` , error ) ;
256178 }
257-
258- return packages ;
259179 }
260180
261- /**
262- * Get all packages from a monorepo
263- */
264- static getMonorepoPackages ( rootPath : string ) : MonorepoPackage [ ] {
265- const monorepoInfo = this . detectMonorepoType ( rootPath ) ;
266-
267- if ( ! monorepoInfo ) {
268- return [ ] ;
269- }
270-
271- switch ( monorepoInfo . type ) {
272- case 'eden' : {
273- const config = this . parseEdenMonorepoConfig ( monorepoInfo . configPath ) ;
274- if ( config ) {
275- return this . getEdenPackages ( rootPath , config ) ;
276- }
277- break ;
278- }
279- case 'pnpm' : {
280- const configContent = fs . readFileSync ( monorepoInfo . configPath , 'utf-8' ) ;
281- const packages : MonorepoPackage [ ] = [ ] ;
282- const lines = configContent . split ( '\n' ) ;
283- let inPackages = false ;
284- for ( const line of lines ) {
285- if ( line . startsWith ( 'packages:' ) ) {
286- inPackages = true ;
287- continue ;
288- }
289- if ( inPackages && line . trim ( ) . startsWith ( '-' ) ) {
290- let glob = line . trim ( ) . substring ( 1 ) . trim ( ) . replace ( / ' / g, '' ) . replace ( / " / g, '' ) ;
291- if ( glob . endsWith ( '/*' ) ) {
292- glob = glob . slice ( 0 , - 2 ) ;
293- }
294- let packageDir = path . join ( rootPath , glob ) ;
295- if ( fs . existsSync ( packageDir ) && fs . statSync ( packageDir ) . isDirectory ( ) ) {
296- const packageNames = fs . readdirSync ( packageDir ) ;
297- packageNames . push ( "." ) ;
298- for ( const pkgName of packageNames ) {
299- const pkgAbsolutePath = path . join ( packageDir , pkgName ) ;
300- if ( fs . statSync ( pkgAbsolutePath ) . isDirectory ( ) ) {
301- const pkgRelativePath = path . relative ( rootPath , pkgAbsolutePath ) ;
302- let packageName : string | undefined ;
303- const packageJsonPath = path . join ( pkgAbsolutePath , 'package.json' ) ;
304- if ( fs . existsSync ( packageJsonPath ) ) {
305- try {
306- const packageJson = JSON . parse ( fs . readFileSync ( packageJsonPath , 'utf-8' ) ) ;
307- packageName = packageJson . name ;
308- packages . push ( {
309- path : pkgRelativePath ,
310- absolutePath : pkgAbsolutePath ,
311- shouldPublish : false , // Cannot determine from pnpm-workspace.yaml
312- name : packageName
313- } ) ;
314- } catch ( error ) {
315- console . warn ( `Failed to parse package.json at ${ packageJsonPath } :` , error ) ;
316- }
317- }
318- }
319- }
320- }
321- } else if ( inPackages && ! / ^ \s * $ / . test ( line ) && ! / ^ \s + - / . test ( line ) ) {
322- // We are out of the packages section if the line is not empty and not a package entry
323- break ;
324- }
325- }
326- console . log ( 'pnpm packages:' , packages ) ;
327- return packages ;
328- }
329- // TODO: Add support for other monorepo types (yarn, lerna)
330- default :
331- console . warn ( `Monorepo type '${ monorepoInfo . type } ' is not yet supported` ) ;
332- break ;
333- }
334-
335- return [ ] ;
336- }
337-
338- /**
339- * Check if a path is within any of the monorepo packages
340- */
341- static findPackageForPath ( filePath : string , packages : MonorepoPackage [ ] ) : MonorepoPackage | null {
342- const absoluteFilePath = path . resolve ( filePath ) ;
343-
344- for ( const pkg of packages ) {
345- if ( absoluteFilePath . startsWith ( pkg . absolutePath + path . sep ) ||
346- absoluteFilePath === pkg . absolutePath ) {
347- return pkg ;
348- }
349- }
350-
351- return null ;
352- }
353-
354-
355181}
0 commit comments