Skip to content
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion ts-parser/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ program
.option('--no-dist', 'Ignore dist folder and its contents', false)
.option('--pretty', 'Pretty print JSON output', false)
.option('--src <dirs>', 'Directory paths to include (comma-separated)', (value) => value.split(','))
.option('--monorepo-mode <mode>', '"combined"(output entrie monorep repository) "separate"(output each app)', 'combined')
.action(async (directory, options) => {
try {
const repoPath = path.resolve(directory);
Expand All @@ -36,7 +37,8 @@ program
const repository = await parser.parseRepository(repoPath, {
loadExternalSymbols: false,
noDist: options.noDist,
srcPatterns: options.src
srcPatterns: options.src,
monorepoMode: options.monorepoMode as 'combined' | 'separate'
});

const outputPath = path.resolve(options.output);
Expand Down
152 changes: 98 additions & 54 deletions ts-parser/src/parser/RepositoryParser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ export class RepositoryParser {
this.tsConfigPath = tsConfigPath;
}

async parseRepository(repoPath: string, options: { loadExternalSymbols?: boolean, noDist?: boolean, srcPatterns?: string[] } = {}): Promise<Repository> {
async parseRepository(repoPath: string, options: { loadExternalSymbols?: boolean, noDist?: boolean, srcPatterns?: string[], monorepoMode?: 'combined' | 'separate' } = {}): Promise<Repository> {
const absolutePath = path.resolve(repoPath);

const repository: Repository = {
Expand All @@ -33,32 +33,80 @@ export class RepositoryParser {

if (isMonorepo) {
const packages = MonorepoUtils.getMonorepoPackages(absolutePath);
console.log(`Monorepo detected. Found ${packages.length} packages.`);

const monorepoMode = options.monorepoMode || 'combined';
// Using separate output mode - each package will be written to individual JSON files
if (monorepoMode === 'separate') {

for (const pkg of packages) {
const packageTsConfigPath = path.join(pkg.absolutePath, 'tsconfig.json');
try {
let project: Project;
for (const pkg of packages) {
const packageTsConfigPath = path.join(pkg.absolutePath, 'tsconfig.json');
if (fs.existsSync(packageTsConfigPath)) {
console.log(`Parsing package ${pkg.name || pkg.path} with tsconfig ${packageTsConfigPath}`);
project = new Project({
tsConfigFilePath: packageTsConfigPath,
compilerOptions: {
allowJs: true,
skipLibCheck: true,
forceConsistentCasingInFileNames: true
}
});
try {
const project = new Project({
tsConfigFilePath: packageTsConfigPath,
compilerOptions: {
allowJs: true,
skipLibCheck: true,
forceConsistentCasingInFileNames: true
}
});
const moduleParser = new ModuleParser(project, this.projectRoot);
const module = await moduleParser.parseModule(pkg.absolutePath, pkg.path, options);

// Add module to main repository for combined output
repository.Modules[module.Name] = module;

// Create a separate repository for each package
const packageRepository: Repository = {
ASTVersion: "v0.1.3",
id: pkg.name || path.basename(pkg.absolutePath),
Modules: { [module.Name]: module },
Graph: {}
};

this.buildGlobalGraph(packageRepository);

// Write JSON file for this package
const sanitizedPackageName = (pkg.name || path.basename(pkg.absolutePath)).replace(/[\/\\:*?"<>|@]/g, '_');
const outputPath = path.join(process.cwd(), `${sanitizedPackageName}.json`);
const jsonOutput = JSON.stringify(packageRepository, null, 2);
fs.writeFileSync(outputPath, jsonOutput);

console.log(`Package ${pkg.name || pkg.path} written to: ${outputPath}`);
} catch (error) {
console.warn(`Failed to parse package ${pkg.name || pkg.path}:`, error);
}
} else {
console.log(`No tsconfig.json found for package ${pkg.name || pkg.path}, using default configuration.`);
project = this.createProjectWithDefaultConfig();
Comment thread
yewanting marked this conversation as resolved.
console.log(`No tsconfig.json found for package ${pkg.name || pkg.path}, skipping.`);
}
}

console.log(`All packages have been written to separate files`);
console.log(`Total packages processed: ${packages.length}`);
} else {
// Using combined output mode - all packages will be merged into one JSON file
for (const pkg of packages) {
const packageTsConfigPath = path.join(pkg.absolutePath, 'tsconfig.json');
if (fs.existsSync(packageTsConfigPath)) {
try {
const project = new Project({
tsConfigFilePath: packageTsConfigPath,
compilerOptions: {
allowJs: true,
skipLibCheck: true,
forceConsistentCasingInFileNames: true
}
});
const moduleParser = new ModuleParser(project, this.projectRoot);
const module = await moduleParser.parseModule(pkg.absolutePath, pkg.path, options);
repository.Modules[module.Name] = module;
} catch (error) {
console.warn(`Failed to parse package ${pkg.name || pkg.path}:`, error);
}
} else {
console.log(`No tsconfig.json found for package ${pkg.name || pkg.path}, skipping.`);
}

const moduleParser = new ModuleParser(project, this.projectRoot);
const module = await moduleParser.parseModule(pkg.absolutePath, pkg.path, options);
repository.Modules[module.Name] = module;
} catch (error) {
console.warn(`Failed to parse package ${pkg.name || pkg.path}:`, error);
}
}
} else {
Expand Down Expand Up @@ -137,41 +185,37 @@ export class RepositoryParser {
}
return project;
} else {
return this.createProjectWithDefaultConfig();
return new Project({
compilerOptions: {
target: 99,
module: 1,
allowJs: true,
checkJs: false,
skipLibCheck: true,
skipDefaultLibCheck: true,
strict: false,
noImplicitAny: false,
strictNullChecks: false,
strictFunctionTypes: false,
strictBindCallApply: false,
strictPropertyInitialization: false,
noImplicitReturns: false,
noFallthroughCasesInSwitch: false,
noUncheckedIndexedAccess: false,
noImplicitOverride: false,
noPropertyAccessFromIndexSignature: false,
allowUnusedLabels: false,
allowUnreachableCode: false,
exactOptionalPropertyTypes: false,
noImplicitThis: false,
alwaysStrict: false,
noImplicitUseStrict: false,
forceConsistentCasingInFileNames: true
}
});
}
}

private createProjectWithDefaultConfig(): Project {
Comment thread
yewanting marked this conversation as resolved.
return new Project({
compilerOptions: {
target: 99,
module: 1,
allowJs: true,
checkJs: false,
skipLibCheck: true,
skipDefaultLibCheck: true,
strict: false,
noImplicitAny: false,
strictNullChecks: false,
strictFunctionTypes: false,
strictBindCallApply: false,
strictPropertyInitialization: false,
noImplicitReturns: false,
noFallthroughCasesInSwitch: false,
noUncheckedIndexedAccess: false,
noImplicitOverride: false,
noPropertyAccessFromIndexSignature: false,
allowUnusedLabels: false,
allowUnreachableCode: false,
exactOptionalPropertyTypes: false,
noImplicitThis: false,
alwaysStrict: false,
noImplicitUseStrict: false,
forceConsistentCasingInFileNames: true
}
});
}

private buildGlobalGraph(repository: Repository): void {
// First pass: Create all nodes from functions, types, and variables
for (const [, module] of Object.entries(repository.Modules)) {
Expand Down
2 changes: 2 additions & 0 deletions ts-parser/src/utils/monorepo.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,10 +53,12 @@ export class MonorepoUtils {
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);
}

Expand Down
5 changes: 3 additions & 2 deletions ts-parser/src/utils/symbol-resolver.ts
Original file line number Diff line number Diff line change
Expand Up @@ -279,15 +279,16 @@ export class SymbolResolver {
return { path: relativePath === '' ? '.' : `${relativePath}` };
}

/**
/**
* Find the closest package.json file for a given file path
* @param fileDir - The directory to start searching from
* @param projectRoot - Project root
*/
private findPackageJsonPath(fileDir: string, projectRoot?: string): string | null {
let currentDir = fileDir;
const stopAtRoot = this.normalizePath(projectRoot || this.projectRoot);

const stopAtRoot = this.normalizePath(projectRoot || this.projectRoot);

while (this.normalizePath(currentDir) !== stopAtRoot && currentDir !== path.dirname(currentDir)) {
const packageJsonPath = path.join(currentDir, 'package.json');
if (fs.existsSync(packageJsonPath)) {
Expand Down