diff --git a/ts-parser/src/utils/monorepo.ts b/ts-parser/src/utils/monorepo.ts index 440b01e..93617cd 100644 --- a/ts-parser/src/utils/monorepo.ts +++ b/ts-parser/src/utils/monorepo.ts @@ -69,287 +69,113 @@ export interface MonorepoPackage { export class MonorepoUtils { /** * Check if a directory contains a monorepo configuration + * Now determines monorepo by counting package.json files (>= 2 means monorepo) */ static isMonorepo(rootPath: string): boolean { - const edenConfigPath = path.join(rootPath, 'eden.monorepo.json'); - const pnpmWorkspacePath = path.join(rootPath, 'pnpm-workspace.yaml'); - // const yarnWorkspacePath = path.join(rootPath, 'yarn.lock'); - const lernaConfigPath = path.join(rootPath, 'lerna.json'); - - return fs.existsSync(edenConfigPath) || - fs.existsSync(pnpmWorkspacePath) || - // fs.existsSync(yarnWorkspacePath) || - fs.existsSync(lernaConfigPath); - } - - /** - * Detect monorepo type and return configuration file path - */ - static detectMonorepoType(rootPath: string): { type: string; configPath: string } | null { - const edenConfigPath = path.join(rootPath, 'eden.monorepo.json'); - const pnpmWorkspacePath = path.join(rootPath, 'pnpm-workspace.yaml'); - // const yarnWorkspacePath = path.join(rootPath, 'yarn.lock'); - const lernaConfigPath = path.join(rootPath, 'lerna.json'); - - if (fs.existsSync(edenConfigPath)) { - return { type: 'eden', configPath: edenConfigPath }; - } - if (fs.existsSync(pnpmWorkspacePath)) { - return { type: 'pnpm', configPath: pnpmWorkspacePath }; - } - // if (fs.existsSync(yarnWorkspacePath)) { - // return { type: 'yarn', configPath: yarnWorkspacePath }; - // } - if (fs.existsSync(lernaConfigPath)) { - return { type: 'lerna', configPath: lernaConfigPath }; - } - - return null; + const packageJsonCount = this.countPackageJsonFiles(rootPath); + return packageJsonCount >= 2; } /** - * Parse Eden monorepo configuration + * Count the number of package.json files in the directory tree */ - static parseEdenMonorepoConfig(configPath: string): EdenMonorepoConfig | null { + private static countPackageJsonFiles(rootPath: string): number { try { - if (!fs.existsSync(configPath)) { - return null; + let count = 0; + const items = fs.readdirSync(rootPath); + + for (const item of items) { + const fullPath = path.join(rootPath, item); + const stat = fs.statSync(fullPath); + + if (stat.isDirectory()) { + // Skip node_modules and hidden directories + if (item === 'node_modules' || item.startsWith('.')) { + continue; + } + + // Check if this directory has a package.json + const packageJsonPath = path.join(fullPath, 'package.json'); + if (fs.existsSync(packageJsonPath)) { + count++; + } + + // Recursively count in subdirectories + count += this.countPackageJsonFiles(fullPath); + } } - - const configContent = fs.readFileSync(configPath, 'utf-8'); - const config: EdenMonorepoConfig = JSON.parse(configContent); - - return config; + + return count; } catch (error) { - console.warn(`Failed to parse Eden monorepo config at ${configPath}:`, error); - return null; + console.warn(`Error counting package.json files in ${rootPath}:`, error); + return 0; } } /** - * Get packages from Eden monorepo configuration - * Supports packages array, workspaces array, and pnpmWorkspace formats - * pnpmWorkspace has the highest priority (emo >= 3.6.0) + * Get all packages from a monorepo + * Unified approach: discover packages by package.json files only */ - static getEdenPackages(rootPath: string, config: EdenMonorepoConfig): MonorepoPackage[] { - const packages: MonorepoPackage[] = []; - - if (config.pnpmWorkspace && config.pnpmWorkspace.packages && config.pnpmWorkspace.packages.length > 0) { - for (const workspace of config.pnpmWorkspace.packages) { - const workspacePackages = this.expandWorkspacePattern(rootPath, workspace); - packages.push(...workspacePackages); - } - return packages; // Return early if pnpmWorkspace is configured - } - - // Handle new workspaces array format - if (config.workspaces && config.workspaces.length > 0) { - for (const workspace of config.workspaces) { - const workspacePackages = this.expandWorkspacePattern(rootPath, workspace); - packages.push(...workspacePackages); - } - } - - // Handle legacy packages array format - if (config.packages && config.packages.length > 0) { - for (const pkg of config.packages) { - const absolutePath = path.resolve(rootPath, pkg.path); + static getMonorepoPackages(rootPath: string): MonorepoPackage[] { + // Simply use generic package discovery, ignoring monorepo type + return this.getGenericPackages(rootPath); + } - // Check if package directory exists - if (fs.existsSync(absolutePath)) { - // Try to get package name from package.json - let packageName: string | undefined; - const packageJsonPath = path.join(absolutePath, 'package.json'); - if (fs.existsSync(packageJsonPath)) { - try { - const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf-8')); - packageName = packageJson.name; - } catch (error) { - console.warn(`Failed to parse package.json at ${packageJsonPath}:`, error); - } - } - - packages.push({ - path: pkg.path, - absolutePath, - shouldPublish: pkg.shouldPublish ?? false, - name: packageName - }); - } else { - console.warn(`Package directory does not exist: ${absolutePath}`); - } - } - } + /** + * Get packages by discovering package.json files (generic approach) + */ + private static getGenericPackages(rootPath: string): MonorepoPackage[] { + const packages: MonorepoPackage[] = []; + this.discoverPackagesRecursive(rootPath, rootPath, packages); return packages; } /** - * Expand workspace pattern to find actual packages - * Supports glob patterns like "packages/*", "apps/*", etc. + * Recursively discover packages by finding package.json files */ - private static expandWorkspacePattern(rootPath: string, pattern: string): MonorepoPackage[] { - const packages: MonorepoPackage[] = []; - + private static discoverPackagesRecursive(rootPath: string, currentDir: string, packages: MonorepoPackage[]): void { try { - // Handle glob patterns - if (pattern.includes('*')) { - const basePath = pattern.replace('/*', ''); - const baseDir = path.resolve(rootPath, basePath); - - if (fs.existsSync(baseDir)) { - const entries = fs.readdirSync(baseDir, { withFileTypes: true }); - - for (const entry of entries) { - if (entry.isDirectory()) { - const packagePath = path.join(basePath, entry.name); - const absolutePath = path.resolve(rootPath, packagePath); - const packageJsonPath = path.join(absolutePath, 'package.json'); - - // Only include directories that have package.json - if (fs.existsSync(packageJsonPath)) { - let packageName: string | undefined; - - try { - const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf-8')); - packageName = packageJson.name; - } catch (error) { - console.warn(`Failed to parse package.json at ${packageJsonPath}:`, error); - } - - packages.push({ - path: packagePath, - absolutePath, - shouldPublish: false, // Default to false for workspace packages - name: packageName - }); - } - } - } + const items = fs.readdirSync(currentDir, { withFileTypes: true }); + + for (const item of items) { + if (!item.isDirectory()) { + continue; } - } else { - // Handle exact path - const absolutePath = path.resolve(rootPath, pattern); - const packageJsonPath = path.join(absolutePath, 'package.json'); - + + const dirName = item.name; + const fullPath = path.join(currentDir, dirName); + + // Skip node_modules and hidden directories + if (dirName === 'node_modules' || dirName.startsWith('.')) { + continue; + } + + // Check if this directory has a package.json + const packageJsonPath = path.join(fullPath, 'package.json'); if (fs.existsSync(packageJsonPath)) { - let packageName: string | undefined; - try { const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf-8')); - packageName = packageJson.name; + const relativePath = path.relative(rootPath, fullPath); + + packages.push({ + path: relativePath, + absolutePath: fullPath, + shouldPublish: false, // Default to false for generic discovery + name: packageJson.name + }); } catch (error) { console.warn(`Failed to parse package.json at ${packageJsonPath}:`, error); } - - packages.push({ - path: pattern, - absolutePath, - shouldPublish: false, - name: packageName - }); } + + // Recursively search in subdirectories + this.discoverPackagesRecursive(rootPath, fullPath, packages); } } catch (error) { - console.warn(`Failed to expand workspace pattern "${pattern}":`, error); + console.warn(`Error discovering packages in ${currentDir}:`, error); } - - return packages; } - /** - * Get all packages from a monorepo - */ - static getMonorepoPackages(rootPath: string): MonorepoPackage[] { - const monorepoInfo = this.detectMonorepoType(rootPath); - - if (!monorepoInfo) { - return []; - } - - switch (monorepoInfo.type) { - case 'eden': { - const config = this.parseEdenMonorepoConfig(monorepoInfo.configPath); - if (config) { - return this.getEdenPackages(rootPath, config); - } - break; - } - case 'pnpm': { - const configContent = fs.readFileSync(monorepoInfo.configPath, 'utf-8'); - const packages: MonorepoPackage[] = []; - const lines = configContent.split('\n'); - let inPackages = false; - for (const line of lines) { - if (line.startsWith('packages:')) { - inPackages = true; - continue; - } - if (inPackages && line.trim().startsWith('-')) { - let glob = line.trim().substring(1).trim().replace(/'/g, '').replace(/"/g, ''); - if (glob.endsWith('/*')) { - glob = glob.slice(0, -2); - } - let packageDir = path.join(rootPath, glob); - if (fs.existsSync(packageDir) && fs.statSync(packageDir).isDirectory()) { - const packageNames = fs.readdirSync(packageDir); - packageNames.push("."); - for (const pkgName of packageNames) { - const pkgAbsolutePath = path.join(packageDir, pkgName); - if (fs.statSync(pkgAbsolutePath).isDirectory()) { - const pkgRelativePath = path.relative(rootPath, pkgAbsolutePath); - let packageName: string | undefined; - const packageJsonPath = path.join(pkgAbsolutePath, 'package.json'); - if (fs.existsSync(packageJsonPath)) { - try { - const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf-8')); - packageName = packageJson.name; - packages.push({ - path: pkgRelativePath, - absolutePath: pkgAbsolutePath, - shouldPublish: false, // Cannot determine from pnpm-workspace.yaml - name: packageName - }); - } catch (error) { - console.warn(`Failed to parse package.json at ${packageJsonPath}:`, error); - } - } - } - } - } - } else if (inPackages && !/^\s*$/.test(line) && !/^\s+-/.test(line)) { - // We are out of the packages section if the line is not empty and not a package entry - break; - } - } - console.log('pnpm packages:', packages); - return packages; - } - // TODO: Add support for other monorepo types (yarn, lerna) - default: - console.warn(`Monorepo type '${monorepoInfo.type}' is not yet supported`); - break; - } - - return []; - } - - /** - * Check if a path is within any of the monorepo packages - */ - static findPackageForPath(filePath: string, packages: MonorepoPackage[]): MonorepoPackage | null { - const absoluteFilePath = path.resolve(filePath); - - for (const pkg of packages) { - if (absoluteFilePath.startsWith(pkg.absolutePath + path.sep) || - absoluteFilePath === pkg.absolutePath) { - return pkg; - } - } - - return null; - } - - } \ No newline at end of file diff --git a/ts-parser/src/utils/test/monorepo.test.ts b/ts-parser/src/utils/test/monorepo.test.ts index 83205d0..ebedfbf 100644 --- a/ts-parser/src/utils/test/monorepo.test.ts +++ b/ts-parser/src/utils/test/monorepo.test.ts @@ -13,628 +13,63 @@ describe('MonorepoUtils', () => { describe('isMonorepo', () => { it('should return true for Eden monorepo', () => { - const testProject = createEdenMonorepoProject([]); - expect(MonorepoUtils.isMonorepo(testProject.rootDir)).toBe(true); - testProject.cleanup(); - }); - - it('should return true for pnpm workspace', () => { - const testProject = createPnpmWorkspaceProject([]); - expect(MonorepoUtils.isMonorepo(testProject.rootDir)).toBe(true); - testProject.cleanup(); - }); - - it('should return true for lerna monorepo', () => { - const testProject = createLernaMonorepoProject([]); - expect(MonorepoUtils.isMonorepo(testProject.rootDir)).toBe(true); - testProject.cleanup(); - }); - - it('should return false for non-monorepo directory', () => { - const testProject = createEdenMonorepoProject([]); - // Remove the eden.monorepo.json to make it a non-monorepo - fs.unlinkSync(path.join(testProject.rootDir, 'eden.monorepo.json')); - expect(MonorepoUtils.isMonorepo(testProject.rootDir)).toBe(false); - testProject.cleanup(); - }); - }); - - describe('detectMonorepoType', () => { - it('should detect Eden monorepo type', () => { - const testProject = createEdenMonorepoProject([]); - const edenConfigPath = path.join(testProject.rootDir, 'eden.monorepo.json'); - - const result = MonorepoUtils.detectMonorepoType(testProject.rootDir); - expect(result).toEqual({ - type: 'eden', - configPath: edenConfigPath - }); - - testProject.cleanup(); - }); - - it('should detect pnpm workspace type', () => { - const testProject = createPnpmWorkspaceProject([]); - const pnpmWorkspacePath = path.join(testProject.rootDir, 'pnpm-workspace.yaml'); - - const result = MonorepoUtils.detectMonorepoType(testProject.rootDir); - expect(result).toEqual({ - type: 'pnpm', - configPath: pnpmWorkspacePath - }); - - testProject.cleanup(); - }); - - it('should detect lerna monorepo type', () => { - const testProject = createLernaMonorepoProject([]); - const lernaConfigPath = path.join(testProject.rootDir, 'lerna.json'); - - const result = MonorepoUtils.detectMonorepoType(testProject.rootDir); - expect(result).toEqual({ - type: 'lerna', - configPath: lernaConfigPath - }); - - testProject.cleanup(); - }); - - it('should return null for non-monorepo directory', () => { - const testProject = createEdenMonorepoProject([]); - // Remove the eden.monorepo.json to make it a non-monorepo - fs.unlinkSync(path.join(testProject.rootDir, 'eden.monorepo.json')); - - const result = MonorepoUtils.detectMonorepoType(testProject.rootDir); - expect(result).toBeNull(); - - testProject.cleanup(); - }); - - it('should prioritize Eden over other types', () => { - const testProject = createEdenMonorepoProject([]); - const edenConfigPath = path.join(testProject.rootDir, 'eden.monorepo.json'); - - // Add pnpm-workspace.yaml to test priority - const pnpmWorkspacePath = path.join(testProject.rootDir, 'pnpm-workspace.yaml'); - fs.writeFileSync(pnpmWorkspacePath, 'packages:\n - "packages/*"'); - - const result = MonorepoUtils.detectMonorepoType(testProject.rootDir); - expect(result).toEqual({ - type: 'eden', - configPath: edenConfigPath - }); - - testProject.cleanup(); - }); - }); - - describe('parseEdenMonorepoConfig', () => { - it('should parse valid Eden monorepo config', () => { const testProject = createEdenMonorepoProject([ - { path: 'packages/core', shouldPublish: true }, - { path: 'packages/utils' } - ]); - - const configPath = path.join(testProject.rootDir, 'eden.monorepo.json'); - const result = MonorepoUtils.parseEdenMonorepoConfig(configPath); - - expect(result).toEqual({ - packages: [ - { path: 'packages/core', shouldPublish: true }, - { path: 'packages/utils', shouldPublish: false } - ] - }); - - testProject.cleanup(); - }); - - it('should return null for non-existent config file', () => { - const testProject = createEdenMonorepoProject([]); - const configPath = path.join(testProject.rootDir, 'non-existent.json'); - - const result = MonorepoUtils.parseEdenMonorepoConfig(configPath); - expect(result).toBeNull(); - - testProject.cleanup(); - }); - - it('should return null for invalid JSON', () => { - const testProject = createEdenMonorepoProject([]); - const configPath = path.join(testProject.rootDir, 'invalid.json'); - fs.writeFileSync(configPath, 'invalid json content'); - - const consoleSpy = jest.spyOn(console, 'warn').mockImplementation(() => {}); - const result = MonorepoUtils.parseEdenMonorepoConfig(configPath); - - expect(result).toBeNull(); - expect(consoleSpy).toHaveBeenCalledWith( - expect.stringContaining('Failed to parse Eden monorepo config'), - expect.any(Error) - ); - - consoleSpy.mockRestore(); - testProject.cleanup(); - }); - - it('should get packages from workspaces glob patterns', () => { - const testProject = createEdenWorkspacesProject([ - { - path: 'apps/web', - packageJson: { - name: '@test/web', - version: '1.0.0' - } - }, - { - path: 'apps/mobile', - packageJson: { - name: '@test/mobile', - version: '1.0.0' - } - }, { path: 'packages/core', - packageJson: { - name: '@test/core', - version: '1.0.0' - } + packageJson: { name: '@test/core', version: '1.0.0' } }, { path: 'packages/utils', - packageJson: { - name: '@test/utils', - version: '1.0.0' - } - } - ], [ - 'apps/*', - 'packages/*' - ]); - - const config: EdenMonorepoConfig = { - workspaces: [ - 'apps/*', - 'packages/*' - ] - }; - - const result = MonorepoUtils.getEdenPackages(testProject.rootDir, config); - - expect(result).toHaveLength(4); - - // Check apps - const webApp = result.find(pkg => pkg.name === '@test/web'); - expect(webApp).toBeDefined(); - expect(webApp?.path).toBe('apps/web'); - expect(webApp?.shouldPublish).toBe(false); - - const mobileApp = result.find(pkg => pkg.name === '@test/mobile'); - expect(mobileApp).toBeDefined(); - expect(mobileApp?.path).toBe('apps/mobile'); - - // Check packages - const corePackage = result.find(pkg => pkg.name === '@test/core'); - expect(corePackage).toBeDefined(); - expect(corePackage?.path).toBe('packages/core'); - - const utilsPackage = result.find(pkg => pkg.name === '@test/utils'); - expect(utilsPackage).toBeDefined(); - expect(utilsPackage?.path).toBe('packages/utils'); - - testProject.cleanup(); - }); - - it('should handle nested workspace patterns', () => { - const testProject = createEdenWorkspacesProject([ - { - path: 'packages/ulink/core', - packageJson: { - name: '@ulink/core', - version: '1.0.0' - } - }, - { - path: 'packages/ulink/utils', - packageJson: { - name: '@ulink/utils', - version: '1.0.0' - } - } - ], [ - 'packages/ulink/*' - ]); - - const config: EdenMonorepoConfig = { - workspaces: [ - 'packages/ulink/*' - ] - }; - - const result = MonorepoUtils.getEdenPackages(testProject.rootDir, config); - - expect(result).toHaveLength(2); - expect(result[0].path).toBe('packages/ulink/core'); - expect(result[1].path).toBe('packages/ulink/utils'); - - testProject.cleanup(); - }); - - it('should handle exact workspace paths', () => { - const testProject = createEdenWorkspacesProject([ - { - path: 'libs/shared', - packageJson: { - name: '@test/shared', - version: '1.0.0' - } - } - ], [ - 'libs/shared' - ]); - - const config: EdenMonorepoConfig = { - workspaces: [ - 'libs/shared' - ] - }; - - const result = MonorepoUtils.getEdenPackages(testProject.rootDir, config); - - expect(result).toHaveLength(1); - expect(result[0].path).toBe('libs/shared'); - expect(result[0].name).toBe('@test/shared'); - expect(result[0].shouldPublish).toBe(false); - - testProject.cleanup(); - }); - - it('should combine packages and workspaces formats', () => { - const testProject = createEdenWorkspacesProject([ - { - path: 'legacy/old-package', - packageJson: { - name: '@test/old-package', - version: '1.0.0' - } - }, - { - path: 'apps/new-app', - packageJson: { - name: '@test/new-app', - version: '1.0.0' - } - } - ], [ - 'apps/*' - ]); - - const config: EdenMonorepoConfig = { - packages: [ - { path: 'legacy/old-package', shouldPublish: true } - ], - workspaces: [ - 'apps/*' - ] - }; - - const result = MonorepoUtils.getEdenPackages(testProject.rootDir, config); - - expect(result).toHaveLength(2); - - const oldPackage = result.find(pkg => pkg.name === '@test/old-package'); - expect(oldPackage).toBeDefined(); - expect(oldPackage?.shouldPublish).toBe(true); - - const newApp = result.find(pkg => pkg.name === '@test/new-app'); - expect(newApp).toBeDefined(); - expect(newApp?.shouldPublish).toBe(false); - - testProject.cleanup(); - }); - - it('should skip directories without package.json in workspaces', () => { - const testProject = createEdenWorkspacesProject([ - { - path: 'packages/with-package-json', - packageJson: { - name: '@test/with-package-json', - version: '1.0.0' - } - } - ], [ - 'packages/*' - ]); - - // Create a directory without package.json - const withoutPackageJsonDir = path.join(testProject.rootDir, 'packages', 'without-package-json'); - fs.mkdirSync(withoutPackageJsonDir, { recursive: true }); - - const config: EdenMonorepoConfig = { - workspaces: [ - 'packages/*' - ] - }; - - const result = MonorepoUtils.getEdenPackages(testProject.rootDir, config); - - expect(result).toHaveLength(1); - expect(result[0].name).toBe('@test/with-package-json'); - - testProject.cleanup(); - }); - - it('should handle non-existent workspace base directories', () => { - const testProject = createEdenWorkspacesProject([], []); - - const config: EdenMonorepoConfig = { - workspaces: [ - 'non-existent/*' - ] - }; - - const result = MonorepoUtils.getEdenPackages(testProject.rootDir, config); - - expect(result).toHaveLength(0); - - testProject.cleanup(); - }); - - it('should handle invalid package.json in workspace packages', () => { - const testProject = createEdenWorkspacesProject([ - { - path: 'packages/valid', - packageJson: { - name: '@test/valid', - version: '1.0.0' - } + packageJson: { name: '@test/utils', version: '1.0.0' } } - ], [ - 'packages/*' ]); - - // Create a package with invalid package.json - const invalidDir = path.join(testProject.rootDir, 'packages', 'invalid'); - fs.mkdirSync(invalidDir, { recursive: true }); - fs.writeFileSync(path.join(invalidDir, 'package.json'), 'invalid json'); - - const config: EdenMonorepoConfig = { - workspaces: [ - 'packages/*' - ] - }; - - const consoleSpy = jest.spyOn(console, 'warn').mockImplementation(() => {}); - const result = MonorepoUtils.getEdenPackages(testProject.rootDir, config); - - expect(result).toHaveLength(2); - expect(result.find(pkg => pkg.name === '@test/valid')).toBeDefined(); - expect(result.find(pkg => pkg.path === 'packages/invalid')).toBeDefined(); - expect(result.find(pkg => pkg.path === 'packages/invalid')?.name).toBeUndefined(); - - expect(consoleSpy).toHaveBeenCalledWith( - expect.stringContaining('Failed to parse package.json'), - expect.any(Error) - ); - - consoleSpy.mockRestore(); + expect(MonorepoUtils.isMonorepo(testProject.rootDir)).toBe(true); testProject.cleanup(); }); - it('should handle complex workspace patterns like in real Eden config', () => { - const testProject = createEdenWorkspacesProject([ - { - path: 'apps/web', - packageJson: { name: '@test/web', version: '1.0.0' } - }, + it('should return true for pnpm workspace', () => { + const testProject = createPnpmWorkspaceProject([ { path: 'packages/core', packageJson: { name: '@test/core', version: '1.0.0' } }, { - path: 'packages/ulink/auth', - packageJson: { name: '@ulink/auth', version: '1.0.0' } - }, - { - path: 'packages/config/eslint/plugins/custom', - packageJson: { name: '@config/eslint-plugin-custom', version: '1.0.0' } - }, - { - path: 'libs/shared', - packageJson: { name: '@test/shared', version: '1.0.0' } + path: 'packages/utils', + packageJson: { name: '@test/utils', version: '1.0.0' } } - ], [ - 'apps/*', - 'packages/*', - 'packages/ulink/*', - 'packages/config/eslint/plugins/*', - 'libs/*' ]); - - const config: EdenMonorepoConfig = { - $schema: 'https://sf-unpkg-src.bytedance.net/@ies/eden-monorepo@3.1.0/lib/monorepo.schema.json', - config: { - cache: false, - infraDir: '', - pnpmVersion: '9.14.4', - edenMonoVersion: '3.5.0', - scriptName: { - test: ['test'], - build: ['build'], - start: ['build:watch', 'dev', 'start', 'serve'] - }, - pluginsDir: 'packages/plugins/emo', - plugins: ['./packages/config/emo/kesong-build.ts', '@ulike/emo-plugin-ci', '@ulike/emo-plugin-lint-assist'], - autoInstallDepsForPlugins: false - }, - workspaces: [ - 'apps/*', - 'packages/*', - 'packages/ulink/*', - 'packages/config/eslint/plugins/*', - 'libs/*' - ] - }; - - const result = MonorepoUtils.getEdenPackages(testProject.rootDir, config); - - expect(result).toHaveLength(5); - expect(result.find(pkg => pkg.name === '@test/web')).toBeDefined(); - expect(result.find(pkg => pkg.name === '@test/core')).toBeDefined(); - expect(result.find(pkg => pkg.name === '@ulink/auth')).toBeDefined(); - expect(result.find(pkg => pkg.name === '@config/eslint-plugin-custom')).toBeDefined(); - expect(result.find(pkg => pkg.name === '@test/shared')).toBeDefined(); - + expect(MonorepoUtils.isMonorepo(testProject.rootDir)).toBe(true); testProject.cleanup(); }); - it('should handle workspace pattern expansion errors gracefully', () => { - const testProject = createEdenWorkspacesProject([], []); - - const config: EdenMonorepoConfig = { - workspaces: [ - 'packages/*' - ] - }; - - // Test with a workspace pattern that points to a non-directory file - const packagesFile = path.join(testProject.rootDir, 'packages'); - fs.writeFileSync(packagesFile, 'not a directory'); - - const consoleSpy = jest.spyOn(console, 'warn').mockImplementation(() => {}); - const result = MonorepoUtils.getEdenPackages(testProject.rootDir, config); - - expect(result).toHaveLength(0); - // The function should handle the error gracefully and return empty array - - consoleSpy.mockRestore(); - testProject.cleanup(); - }); - }); - - describe('getEdenPackages', () => { - it('should get packages from Eden config', () => { - const testProject = createEdenMonorepoProject([ + it('should return true for lerna monorepo', () => { + const testProject = createLernaMonorepoProject([ { - path: 'packages/core', - shouldPublish: true, - packageJson: { - name: '@test/core', - version: '1.0.0' - } + path: 'packages/core', + packageJson: { name: '@test/core', version: '1.0.0' } }, { - path: 'packages/utils', - shouldPublish: false, - packageJson: { - name: '@test/utils', - version: '1.0.0' - } + path: 'packages/utils', + packageJson: { name: '@test/utils', version: '1.0.0' } } ]); - - const config: EdenMonorepoConfig = { - packages: [ - { path: 'packages/core', shouldPublish: true }, - { path: 'packages/utils', shouldPublish: false } - ] - }; - - const result = MonorepoUtils.getEdenPackages(testProject.rootDir, config); - - expect(result).toHaveLength(2); - expect(result[0]).toEqual({ - path: 'packages/core', - absolutePath: path.join(testProject.rootDir, 'packages/core'), - shouldPublish: true, - name: '@test/core' - }); - expect(result[1]).toEqual({ - path: 'packages/utils', - absolutePath: path.join(testProject.rootDir, 'packages/utils'), - shouldPublish: false, - name: '@test/utils' - }); - - testProject.cleanup(); - }); - - it('should handle packages without package.json', () => { - const testProject = createEdenMonorepoProject([ - { path: 'packages/core', shouldPublish: true } - ]); - - const config: EdenMonorepoConfig = { - packages: [ - { path: 'packages/core', shouldPublish: true } - ] - }; - - const result = MonorepoUtils.getEdenPackages(testProject.rootDir, config); - - expect(result).toHaveLength(1); - expect(result[0]).toEqual({ - path: 'packages/core', - absolutePath: path.join(testProject.rootDir, 'packages/core'), - shouldPublish: true, - name: undefined - }); - + expect(MonorepoUtils.isMonorepo(testProject.rootDir)).toBe(true); testProject.cleanup(); }); - it('should skip non-existent package directories', () => { + it('should return false for non-monorepo directory', () => { const testProject = createEdenMonorepoProject([]); - - const config: EdenMonorepoConfig = { - packages: [ - { path: 'packages/non-existent', shouldPublish: true } - ] - }; - - const consoleSpy = jest.spyOn(console, 'warn').mockImplementation(() => {}); - const result = MonorepoUtils.getEdenPackages(testProject.rootDir, config); - - expect(result).toHaveLength(0); - expect(consoleSpy).toHaveBeenCalledWith( - expect.stringContaining('Package directory does not exist') - ); - - consoleSpy.mockRestore(); + // Remove the eden.monorepo.json to make it a non-monorepo + fs.unlinkSync(path.join(testProject.rootDir, 'eden.monorepo.json')); + expect(MonorepoUtils.isMonorepo(testProject.rootDir)).toBe(false); testProject.cleanup(); }); + }); - it('should handle invalid package.json', () => { - const testProject = createEdenMonorepoProject([ - { path: 'packages/core', shouldPublish: true } - ]); - - // Write invalid JSON to the package.json file - const coreDir = path.join(testProject.rootDir, 'packages', 'core'); - fs.writeFileSync(path.join(coreDir, 'package.json'), 'invalid json'); - - const config: EdenMonorepoConfig = { - packages: [ - { path: 'packages/core', shouldPublish: true } - ] - }; - const consoleSpy = jest.spyOn(console, 'warn').mockImplementation(() => {}); - const result = MonorepoUtils.getEdenPackages(testProject.rootDir, config); - - expect(result).toHaveLength(1); - expect(result[0].name).toBeUndefined(); - expect(consoleSpy).toHaveBeenCalledWith( - expect.stringContaining('Failed to parse package.json'), - expect.any(Error) - ); - - consoleSpy.mockRestore(); - testProject.cleanup(); - }); - }); describe('getMonorepoPackages', () => { - it('should get packages from Eden monorepo', () => { + it('should get packages via generic discovery from Eden monorepo', () => { const testProject = createEdenMonorepoProject([ { path: 'packages/core', @@ -652,7 +87,7 @@ describe('MonorepoUtils', () => { expect(result[0]).toEqual({ path: 'packages/core', absolutePath: path.join(testProject.rootDir, 'packages/core'), - shouldPublish: true, + shouldPublish: false, // shouldPublish is always false now name: '@test/core' }); @@ -694,167 +129,26 @@ describe('MonorepoUtils', () => { testProject.cleanup(); }); - it('should handle unsupported monorepo types', () => { - const testProject = createLernaMonorepoProject([], { packages: [] }); + it('should handle all monorepo types via generic discovery', () => { + const testProject = createLernaMonorepoProject([ + { + path: 'packages/core', + packageJson: { + name: '@test/core', + version: '1.0.0' + } + } + ], { packages: [] }); - const consoleSpy = jest.spyOn(console, 'warn').mockImplementation(() => {}); const result = MonorepoUtils.getMonorepoPackages(testProject.rootDir); - expect(result).toEqual([]); - expect(consoleSpy).toHaveBeenCalledWith( - expect.stringContaining("Monorepo type 'lerna' is not yet supported") - ); + // All monorepo types now use generic discovery + expect(result).toHaveLength(1); + expect(result[0].name).toBe('@test/core'); - consoleSpy.mockRestore(); testProject.cleanup(); }); }); - describe('pnpmWorkspace support', () => { - describe('getEdenPackages with pnpmWorkspace', () => { - it('should parse pnpmWorkspace configuration and extract packages', () => { - const testProject = createPnpmWorkspaceProject([ - { - path: 'packages/core', - packageJson: { - name: '@test/core', - version: '1.0.0', - dependencies: { - 'react': '^18.0.0', - 'lodash': '^4.17.21' - } - } - }, - { - path: 'packages/utils', - packageJson: { - name: '@test/utils', - version: '1.0.0', - dependencies: { - 'typescript': '^5.0.0' - } - } - } - ], ['packages/*']); - - const config: EdenMonorepoConfig = { - pnpmWorkspace: { - packages: ['packages/*'] - } - }; - - const result = MonorepoUtils.getEdenPackages(testProject.rootDir, config); - - expect(result).toHaveLength(2); - - const corePackage = result.find(pkg => pkg.name === '@test/core'); - expect(corePackage).toBeDefined(); - expect(corePackage?.path).toBe('packages/core'); - - const utilsPackage = result.find(pkg => pkg.name === '@test/utils'); - expect(utilsPackage).toBeDefined(); - expect(utilsPackage?.path).toBe('packages/utils'); - - testProject.cleanup(); - }); - - it('should prioritize pnpmWorkspace over workspaces and packages', () => { - const testProject = createPnpmWorkspaceProject([ - { - path: 'packages/core', - packageJson: { - name: '@test/core', - version: '1.0.0' - } - } - ], ['packages/*']); - - const config: EdenMonorepoConfig = { - pnpmWorkspace: { - packages: ['packages/*'] - }, - workspaces: ['apps/*'], - packages: [ - { path: 'legacy/*', shouldPublish: true } - ] - }; - - const result = MonorepoUtils.getEdenPackages(testProject.rootDir, config); - - expect(result).toHaveLength(1); - expect(result[0].name).toBe('@test/core'); - expect(result[0].path).toBe('packages/core'); - - testProject.cleanup(); - }); - - it('should handle pnpmWorkspace without catalog configuration', () => { - const testProject = createPnpmWorkspaceProject([ - { - path: 'apps/web', - packageJson: { - name: '@test/web', - version: '1.0.0' - } - } - ], ['apps/*']); - - const config: EdenMonorepoConfig = { - pnpmWorkspace: { - packages: ['apps/*'] - } - }; - - const result = MonorepoUtils.getEdenPackages(testProject.rootDir, config); - - expect(result).toHaveLength(1); - expect(result[0].name).toBe('@test/web'); - - testProject.cleanup(); - }); - }); - - - }); - - describe('findPackageForPath', () => { - const packages: MonorepoPackage[] = [ - { - path: 'packages/core', - absolutePath: '/test/packages/core', - shouldPublish: true, - name: '@test/core' - }, - { - path: 'packages/utils', - absolutePath: '/test/packages/utils', - shouldPublish: false, - name: '@test/utils' - } - ]; - - it('should find package for file within package directory', () => { - const filePath = '/test/packages/core/src/index.ts'; - const result = MonorepoUtils.findPackageForPath(filePath, packages); - expect(result).toEqual(packages[0]); - }); - - it('should find package for package root directory', () => { - const filePath = '/test/packages/core'; - const result = MonorepoUtils.findPackageForPath(filePath, packages); - expect(result).toEqual(packages[0]); - }); - - it('should return null for file outside package directories', () => { - const filePath = '/test/other/file.ts'; - const result = MonorepoUtils.findPackageForPath(filePath, packages); - expect(result).toBeNull(); - }); - it('should return null for empty packages array', () => { - const filePath = '/test/packages/core/src/index.ts'; - const result = MonorepoUtils.findPackageForPath(filePath, []); - expect(result).toBeNull(); - }); - }); }); \ No newline at end of file