codebase-map
Version:
A lightweight TypeScript/JavaScript code indexer that generates comprehensive project maps for LLMs
305 lines (300 loc) • 14.6 kB
JavaScript
/**
* Monorepo Integration Tests
*
* Tests complex monorepo scenarios with nested patterns,
* workspace configurations, and cross-package dependencies.
*/
import { describe, it, expect, beforeEach, afterEach } from 'vitest';
import { TestProjectBuilder, runCLI, loadIndex } from './test-integration.js';
describe('Monorepo Integration Tests', () => {
let testProject;
let projectPath;
beforeEach(() => {
testProject = new TestProjectBuilder();
});
afterEach(() => {
if (testProject) {
testProject.cleanup();
}
});
describe('Multi-Package Structures', () => {
it('should handle monorepo with complex nested patterns', () => {
projectPath = testProject
.addFile('package.json', JSON.stringify({
name: 'monorepo-root',
workspaces: ['packages/*', 'apps/*']
}, null, 2))
.addFile('packages/core/package.json', JSON.stringify({
name: '@monorepo/core',
main: 'dist/index.js',
types: 'dist/index.d.ts'
}, null, 2))
.addFile('packages/core/src/index.ts', `
export class CoreService {
getMessage(): string {
return 'Hello from core';
}
}
`)
.addFile('packages/core/src/utils/helpers.ts', `
export function formatMessage(msg: string): string {
return \`[CORE] \${msg}\`;
}
`)
.addFile('packages/ui/package.json', JSON.stringify({
name: '@monorepo/ui',
dependencies: { '@monorepo/core': 'workspace:*' }
}, null, 2))
.addFile('packages/ui/src/components/Button.tsx', `
import { CoreService } from '@monorepo/core';
export function Button() {
const core = new CoreService();
return <button>{core.getMessage()}</button>;
}
`)
.addFile('packages/ui/src/components/Input.tsx', `
export function Input() {
return <input placeholder="Enter text" />;
}
`)
.addFile('apps/web/package.json', JSON.stringify({
name: 'web-app',
dependencies: {
'@monorepo/core': 'workspace:*',
'@monorepo/ui': 'workspace:*'
}
}, null, 2))
.addFile('apps/web/src/App.tsx', `
import { Button } from '@monorepo/ui';
import { CoreService } from '@monorepo/core';
export function App() {
return <div><Button /></div>;
}
`)
.addFile('apps/mobile/src/App.tsx', `
export function MobileApp() {
return <div>Mobile App</div>;
}
`)
.build();
// Test package-specific filtering
const result = runCLI([
'scan',
'--include', 'packages/*/src/**/*.{ts,tsx}',
'--exclude', '**/*.test.*',
'--verbose'
], projectPath);
expect(result.exitCode).toBe(0);
const index = loadIndex(result.indexPath);
// Should include packages but not apps
expect(index.nodes).toContain('packages/core/src/index.ts');
expect(index.nodes).toContain('packages/core/src/utils/helpers.ts');
expect(index.nodes).toContain('packages/ui/src/components/Button.tsx');
expect(index.nodes).toContain('packages/ui/src/components/Input.tsx');
// Should exclude apps
expect(index.nodes).not.toContain('apps/web/src/App.tsx');
expect(index.nodes).not.toContain('apps/mobile/src/App.tsx');
});
it('should handle selective workspace inclusion', () => {
projectPath = testProject
.addFile('package.json', JSON.stringify({
name: 'monorepo-root',
workspaces: ['packages/*']
}, null, 2))
.addFile('packages/core/src/index.ts', 'export const core = true;')
.addFile('packages/ui/src/index.ts', 'export const ui = true;')
.addFile('packages/utils/src/index.ts', 'export const utils = true;')
.addFile('packages/legacy/src/index.ts', 'export const legacy = true;')
.build();
// Include only specific packages
const result = runCLI([
'scan',
'--include', 'packages/{core,ui}/src/**/*.ts'
], projectPath);
expect(result.exitCode).toBe(0);
const index = loadIndex(result.indexPath);
// Should include core and ui
expect(index.nodes).toContain('packages/core/src/index.ts');
expect(index.nodes).toContain('packages/ui/src/index.ts');
// Should exclude utils and legacy
expect(index.nodes).not.toContain('packages/utils/src/index.ts');
expect(index.nodes).not.toContain('packages/legacy/src/index.ts');
});
it('should handle cross-package dependency tracking', () => {
projectPath = testProject
.addFile('packages/core/src/index.ts', `
export class BaseService {
protected name = 'base';
}
`)
.addFile('packages/auth/src/auth-service.ts', `
import { BaseService } from '@monorepo/core';
export class AuthService extends BaseService {
login() {
return \`\${this.name} auth\`;
}
}
`)
.addFile('packages/api/src/api-service.ts', `
import { AuthService } from '@monorepo/auth';
export class ApiService {
private auth = new AuthService();
makeRequest() {
return this.auth.login();
}
}
`)
.build();
const result = runCLI([
'scan',
'--include', 'packages/*/src/**/*.ts'
], projectPath);
expect(result.exitCode).toBe(0);
const index = loadIndex(result.indexPath);
// Verify cross-package dependencies are tracked
expect(index.files['packages/auth/src/auth-service.ts']?.dependencies).toEqual([]);
expect(index.files['packages/api/src/api-service.ts']?.dependencies).toEqual([]);
// Should have all files
expect(index.nodes).toHaveLength(3);
});
});
describe('Complex Pattern Combinations', () => {
it('should handle nested include/exclude for monorepo', () => {
projectPath = testProject
.addFile('packages/core/src/index.ts', 'export const core = true;')
.addFile('packages/core/src/utils.ts', 'export const utils = true;')
.addFile('packages/core/src/__tests__/core.test.ts', 'test core')
.addFile('packages/core/src/stories/core.stories.ts', 'stories')
.addFile('packages/ui/src/Button.tsx', 'export function Button() {}')
.addFile('packages/ui/src/Input.tsx', 'export function Input() {}')
.addFile('packages/ui/src/__tests__/Button.test.tsx', 'test button')
.addFile('packages/ui/dist/bundle.js', 'compiled output')
.addFile('packages/ui/node_modules/react/index.js', 'dependency')
.build();
const result = runCLI([
'scan',
'--include', 'packages/*/src/**/*.{ts,tsx}',
'--exclude',
'**/__tests__/**',
'**/stories/**',
'**/dist/**',
'**/node_modules/**',
'--verbose'
], projectPath);
expect(result.exitCode).toBe(0);
const index = loadIndex(result.indexPath);
// Should include source files
expect(index.nodes).toContain('packages/core/src/index.ts');
expect(index.nodes).toContain('packages/core/src/utils.ts');
expect(index.nodes).toContain('packages/ui/src/Button.tsx');
expect(index.nodes).toContain('packages/ui/src/Input.tsx');
// Should exclude test files and build artifacts
expect(index.nodes).not.toContain('packages/core/src/__tests__/core.test.ts');
expect(index.nodes).not.toContain('packages/core/src/stories/core.stories.ts');
expect(index.nodes).not.toContain('packages/ui/src/__tests__/Button.test.tsx');
expect(index.nodes).not.toContain('packages/ui/dist/bundle.js');
expect(index.nodes).not.toContain('packages/ui/node_modules/react/index.js');
});
it('should optimize patterns for monorepo development workflow', () => {
projectPath = testProject
.addFile('packages/core/src/index.ts', 'export const core = true;')
.addFile('packages/core/src/types.ts', 'export interface CoreTypes {}')
.addFile('packages/core/lib/compiled.js', 'compiled core')
.addFile('packages/ui/src/components/Button.tsx', 'export function Button() {}')
.addFile('packages/ui/src/hooks/useButton.ts', 'export function useButton() {}')
.addFile('packages/ui/lib/compiled.js', 'compiled ui')
.addFile('packages/utils/src/helpers.ts', 'export const helpers = {};')
.addFile('tools/build.js', 'build script')
.addFile('docs/README.md', 'documentation')
.build();
// Development mode: include source files only
const result = runCLI([
'scan',
'--include', 'packages/*/src/**/*.{ts,tsx}',
'--exclude', '**/lib/**', '**/tools/**'
], projectPath);
expect(result.exitCode).toBe(0);
const index = loadIndex(result.indexPath);
// Should include all source files
expect(index.nodes).toContain('packages/core/src/index.ts');
expect(index.nodes).toContain('packages/core/src/types.ts');
expect(index.nodes).toContain('packages/ui/src/components/Button.tsx');
expect(index.nodes).toContain('packages/ui/src/hooks/useButton.ts');
expect(index.nodes).toContain('packages/utils/src/helpers.ts');
// Should exclude compiled and tool files
expect(index.nodes).not.toContain('packages/core/lib/compiled.js');
expect(index.nodes).not.toContain('packages/ui/lib/compiled.js');
expect(index.nodes).not.toContain('tools/build.js');
expect(index.nodes).not.toContain('docs/README.md');
});
});
describe('Workspace-Specific Configuration', () => {
it('should handle workspace-specific pattern optimization', () => {
projectPath = testProject
.addFile('package.json', JSON.stringify({
name: 'monorepo',
workspaces: {
packages: ['packages/*'],
nohoist: ['**/react', '**/react-dom']
}
}, null, 2))
.addFile('packages/frontend/src/App.tsx', 'export function App() {}')
.addFile('packages/frontend/src/components/Header.tsx', 'export function Header() {}')
.addFile('packages/frontend/node_modules/react/index.js', 'nohoist react')
.addFile('packages/backend/src/server.ts', 'export const server = {};')
.addFile('packages/backend/src/routes/api.ts', 'export const routes = {};')
.addFile('packages/shared/src/types.ts', 'export interface Shared {}')
.addFile('packages/shared/src/utils.ts', 'export const utils = {};')
.build();
// Frontend-focused scan
const frontendResult = runCLI([
'scan',
'--include', 'packages/frontend/src/**/*.{ts,tsx}', 'packages/shared/src/**/*.ts',
'--exclude', '**/node_modules/**'
], projectPath);
expect(frontendResult.exitCode).toBe(0);
const frontendIndex = loadIndex(frontendResult.indexPath);
// Should include frontend and shared
expect(frontendIndex.nodes).toContain('packages/frontend/src/App.tsx');
expect(frontendIndex.nodes).toContain('packages/frontend/src/components/Header.tsx');
expect(frontendIndex.nodes).toContain('packages/shared/src/types.ts');
expect(frontendIndex.nodes).toContain('packages/shared/src/utils.ts');
// Should exclude backend and node_modules
expect(frontendIndex.nodes).not.toContain('packages/backend/src/server.ts');
expect(frontendIndex.nodes).not.toContain('packages/frontend/node_modules/react/index.js');
});
it('should handle performance optimization for large monorepo', () => {
// Create a larger monorepo structure
const builder = testProject
.addFile('package.json', JSON.stringify({ workspaces: ['packages/*'] }, null, 2));
// Add multiple packages with files
for (let i = 0; i < 5; i++) {
builder
.addFile(`packages/package${i}/src/index.ts`, `export const package${i} = true;`)
.addFile(`packages/package${i}/src/utils.ts`, `export const utils${i} = {};`)
.addFile(`packages/package${i}/src/service.ts`, `export class Service${i} {}`)
.addFile(`packages/package${i}/src/types.ts`, `export interface Types${i} {}`)
.addFile(`packages/package${i}/test/index.test.ts`, `test package ${i}`)
.addFile(`packages/package${i}/dist/index.js`, `compiled package ${i}`)
.addFile(`packages/package${i}/node_modules/dep/index.js`, `dependency ${i}`);
}
projectPath = builder.build();
const result = runCLI([
'scan',
'--include', 'packages/*/src/**/*.ts',
'--exclude', '**/test/**', '**/dist/**', '**/node_modules/**',
'--verbose'
], projectPath);
expect(result.exitCode).toBe(0);
expect(result.stdout).toContain('Pattern effectiveness');
const index = loadIndex(result.indexPath);
// Should have 4 files per package * 5 packages = 20 files
expect(index.nodes).toHaveLength(20);
// Verify performance stats are reasonable
expect(result.stdout).toContain('Files processed: 20');
expect(result.stdout).toMatch(/Processing time: \d+\.\d+s/);
});
});
});
export {};
//# sourceMappingURL=test-integration-monorepo.js.map