This commit is contained in:
263
app/src/analyzers/dependencyAnalyzer.ts
Normal file
263
app/src/analyzers/dependencyAnalyzer.ts
Normal file
@@ -0,0 +1,263 @@
|
|||||||
|
import * as path from 'path';
|
||||||
|
import * as fs from 'fs';
|
||||||
|
import { DependencyAnalysis, DependencyInfo, ProjectType } from '../types';
|
||||||
|
import { parseJSONFile, parseTOMLFile } from '../utils/fileUtils';
|
||||||
|
|
||||||
|
const NODE_PACKAGE_MANAGERS = ['yarn.lock', 'pnpm-lock.yaml', 'bun.lockb'];
|
||||||
|
const PYTHON_PACKAGE_MANAGERS = ['Pipfile.lock', 'poetry.lock'];
|
||||||
|
|
||||||
|
export async function analyzeDependencies(
|
||||||
|
directory: string,
|
||||||
|
projectType: ProjectType
|
||||||
|
): Promise<DependencyAnalysis> {
|
||||||
|
let dependencies: DependencyInfo[] = [];
|
||||||
|
let packageManager: DependencyAnalysis['packageManager'] = null;
|
||||||
|
let lockFile: string | undefined;
|
||||||
|
|
||||||
|
switch (projectType) {
|
||||||
|
case 'node':
|
||||||
|
const nodeDeps = await analyzeNodeDependencies(directory);
|
||||||
|
dependencies = nodeDeps.dependencies;
|
||||||
|
packageManager = nodeDeps.packageManager;
|
||||||
|
lockFile = nodeDeps.lockFile;
|
||||||
|
break;
|
||||||
|
case 'python':
|
||||||
|
const pyDeps = await analyzePythonDependencies(directory);
|
||||||
|
dependencies = pyDeps.dependencies;
|
||||||
|
packageManager = pyDeps.packageManager;
|
||||||
|
lockFile = pyDeps.lockFile;
|
||||||
|
break;
|
||||||
|
case 'go':
|
||||||
|
const goDeps = await analyzeGoDependencies(directory);
|
||||||
|
dependencies = goDeps.dependencies;
|
||||||
|
packageManager = goDeps.packageManager;
|
||||||
|
break;
|
||||||
|
case 'rust':
|
||||||
|
const rustDeps = await analyzeRustDependencies(directory);
|
||||||
|
dependencies = rustDeps.dependencies;
|
||||||
|
packageManager = rustDeps.packageManager;
|
||||||
|
break;
|
||||||
|
case 'java':
|
||||||
|
const javaDeps = await analyzeJavaDependencies(directory);
|
||||||
|
dependencies = javaDeps.dependencies;
|
||||||
|
packageManager = javaDeps.packageManager;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
dependencies = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
dependencies,
|
||||||
|
packageManager,
|
||||||
|
lockFile
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
async function analyzeNodeDependencies(
|
||||||
|
directory: string
|
||||||
|
): Promise<{ dependencies: DependencyInfo[]; packageManager: DependencyAnalysis['packageManager']; lockFile?: string }> {
|
||||||
|
const packageJsonPath = path.join(directory, 'package.json');
|
||||||
|
const packageJson = parseJSONFile<{
|
||||||
|
dependencies?: Record<string, string>;
|
||||||
|
devDependencies?: Record<string, string>;
|
||||||
|
}>(packageJsonPath);
|
||||||
|
|
||||||
|
if (!packageJson) {
|
||||||
|
return { dependencies: [], packageManager: null };
|
||||||
|
}
|
||||||
|
|
||||||
|
const dependencies: DependencyInfo[] = [];
|
||||||
|
|
||||||
|
if (packageJson.dependencies) {
|
||||||
|
for (const [name, version] of Object.entries(packageJson.dependencies)) {
|
||||||
|
dependencies.push({ name, version: String(version), type: 'prod' });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (packageJson.devDependencies) {
|
||||||
|
for (const [name, version] of Object.entries(packageJson.devDependencies)) {
|
||||||
|
dependencies.push({ name, version: String(version), type: 'dev' });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let packageManager: DependencyAnalysis['packageManager'] = 'npm';
|
||||||
|
let lockFile: string | undefined;
|
||||||
|
|
||||||
|
for (const lockFileName of NODE_PACKAGE_MANAGERS) {
|
||||||
|
if (fs.existsSync(path.join(directory, lockFileName))) {
|
||||||
|
lockFile = lockFileName;
|
||||||
|
if (lockFileName === 'yarn.lock') packageManager = 'yarn';
|
||||||
|
else if (lockFileName === 'pnpm-lock.yaml') packageManager = 'pnpm';
|
||||||
|
else if (lockFileName === 'bun.lockb') packageManager = 'npm';
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return { dependencies, packageManager, lockFile };
|
||||||
|
}
|
||||||
|
|
||||||
|
async function analyzePythonDependencies(
|
||||||
|
directory: string
|
||||||
|
): Promise<{ dependencies: DependencyInfo[]; packageManager: DependencyAnalysis['packageManager']; lockFile?: string }> {
|
||||||
|
const dependencies: DependencyInfo[] = [];
|
||||||
|
|
||||||
|
const requirementsPath = path.join(directory, 'requirements.txt');
|
||||||
|
if (fs.existsSync(requirementsPath)) {
|
||||||
|
const content = fs.readFileSync(requirementsPath, 'utf-8');
|
||||||
|
const lines = content.split('\n');
|
||||||
|
|
||||||
|
for (const line of lines) {
|
||||||
|
const trimmed = line.trim();
|
||||||
|
if (trimmed && !trimmed.startsWith('#')) {
|
||||||
|
const match = trimmed.match(/^([a-zA-Z0-9_-]+)([<>=!~]+[^;]+)?/);
|
||||||
|
if (match) {
|
||||||
|
dependencies.push({
|
||||||
|
name: match[1],
|
||||||
|
version: match[2] || 'latest',
|
||||||
|
type: 'prod'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const pyprojectPath = path.join(directory, 'pyproject.toml');
|
||||||
|
const pyproject = parseTOMLFile(pyprojectPath);
|
||||||
|
|
||||||
|
const projectDeps = pyproject?.['project'] as Record<string, unknown> | undefined;
|
||||||
|
if (projectDeps && Array.isArray(projectDeps['dependencies'])) {
|
||||||
|
const deps = projectDeps['dependencies'] as string[];
|
||||||
|
for (const dep of deps) {
|
||||||
|
const match = dep.match(/^([a-zA-Z0-9_-]+)([<>=!~]+[^;]+)?/);
|
||||||
|
if (match) {
|
||||||
|
dependencies.push({
|
||||||
|
name: match[1],
|
||||||
|
version: match[2] || 'latest',
|
||||||
|
type: 'prod'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let packageManager: DependencyAnalysis['packageManager'] = 'pip';
|
||||||
|
let lockFile: string | undefined;
|
||||||
|
|
||||||
|
for (const lockFileName of PYTHON_PACKAGE_MANAGERS) {
|
||||||
|
if (fs.existsSync(path.join(directory, lockFileName))) {
|
||||||
|
lockFile = lockFileName;
|
||||||
|
if (lockFileName === 'poetry.lock') packageManager = 'poetry';
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return { dependencies, packageManager, lockFile };
|
||||||
|
}
|
||||||
|
|
||||||
|
async function analyzeGoDependencies(
|
||||||
|
directory: string
|
||||||
|
): Promise<{ dependencies: DependencyInfo[]; packageManager: DependencyAnalysis['packageManager'] }> {
|
||||||
|
const goModPath = path.join(directory, 'go.mod');
|
||||||
|
|
||||||
|
if (!fs.existsSync(goModPath)) {
|
||||||
|
return { dependencies: [], packageManager: 'go' };
|
||||||
|
}
|
||||||
|
|
||||||
|
const content = fs.readFileSync(goModPath, 'utf-8');
|
||||||
|
const dependencies: DependencyInfo[] = [];
|
||||||
|
|
||||||
|
const requireBlockMatch = content.match(/require\s*\(\s*([\s\S]*?)\s*\)/);
|
||||||
|
if (requireBlockMatch) {
|
||||||
|
const requireBlock = requireBlockMatch[1];
|
||||||
|
const depRegex = /\b([^\s]+)\s+v?([^\s]+)/g;
|
||||||
|
let match;
|
||||||
|
while ((match = depRegex.exec(requireBlock)) !== null) {
|
||||||
|
const path = match[1];
|
||||||
|
if (path && !path.startsWith('//') && path !== 'require') {
|
||||||
|
dependencies.push({
|
||||||
|
name: path,
|
||||||
|
version: match[2] || 'latest',
|
||||||
|
type: 'prod'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return { dependencies, packageManager: 'go' };
|
||||||
|
}
|
||||||
|
|
||||||
|
async function analyzeRustDependencies(
|
||||||
|
directory: string
|
||||||
|
): Promise<{ dependencies: DependencyInfo[]; packageManager: DependencyAnalysis['packageManager'] }> {
|
||||||
|
const cargoTomlPath = path.join(directory, 'Cargo.toml');
|
||||||
|
const cargoToml = parseTOMLFile(cargoTomlPath);
|
||||||
|
|
||||||
|
if (!cargoToml) {
|
||||||
|
return { dependencies: [], packageManager: 'cargo' };
|
||||||
|
}
|
||||||
|
|
||||||
|
const dependencies: DependencyInfo[] = [];
|
||||||
|
|
||||||
|
const deps = cargoToml.dependencies as Record<string, string | { version?: string }> | undefined;
|
||||||
|
if (deps) {
|
||||||
|
for (const [name, config] of Object.entries(deps)) {
|
||||||
|
const version = typeof config === 'string' ? config : (config as { version?: string }).version || '*';
|
||||||
|
dependencies.push({ name, version, type: 'prod' });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const dev = cargoToml.dev as { dependencies?: Record<string, string | { version?: string }> } | undefined;
|
||||||
|
if (dev?.dependencies) {
|
||||||
|
for (const [name, config] of Object.entries(dev.dependencies)) {
|
||||||
|
const version = typeof config === 'string' ? config : (config as { version?: string }).version || '*';
|
||||||
|
dependencies.push({ name, version, type: 'dev' });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return { dependencies, packageManager: 'cargo' };
|
||||||
|
}
|
||||||
|
|
||||||
|
async function analyzeJavaDependencies(
|
||||||
|
directory: string
|
||||||
|
): Promise<{ dependencies: DependencyInfo[]; packageManager: DependencyAnalysis['packageManager'] }> {
|
||||||
|
const dependencies: DependencyInfo[] = [];
|
||||||
|
|
||||||
|
const pomPath = path.join(directory, 'pom.xml');
|
||||||
|
if (fs.existsSync(pomPath)) {
|
||||||
|
const content = fs.readFileSync(pomPath, 'utf-8');
|
||||||
|
|
||||||
|
const dependencyRegex = /<dependency>\s*<groupId>([^<]+)<\/groupId>\s*<artifactId>([^<]+)<\/artifactId>\s*<version>([^<]+)<\/version>\s*<\/dependency>/g;
|
||||||
|
let match;
|
||||||
|
|
||||||
|
while ((match = dependencyRegex.exec(content)) !== null) {
|
||||||
|
dependencies.push({
|
||||||
|
name: `${match[1]}:${match[2]}`,
|
||||||
|
version: match[3],
|
||||||
|
type: 'prod'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return { dependencies, packageManager: 'maven' };
|
||||||
|
}
|
||||||
|
|
||||||
|
const buildGradlePath = path.join(directory, 'build.gradle');
|
||||||
|
const buildGradleKtsPath = path.join(directory, 'build.gradle.kts');
|
||||||
|
|
||||||
|
if (fs.existsSync(buildGradlePath) || fs.existsSync(buildGradleKtsPath)) {
|
||||||
|
const content = fs.readFileSync(buildGradlePath || buildGradleKtsPath, 'utf-8');
|
||||||
|
|
||||||
|
const dependencyRegex = /implementation\s+['"]([^:'"]+):([^:'"']+):([^'"@]+)@?(jar)?['"]/g;
|
||||||
|
let match;
|
||||||
|
|
||||||
|
while ((match = dependencyRegex.exec(content)) !== null) {
|
||||||
|
dependencies.push({
|
||||||
|
name: `${match[1]}:${match[2]}`,
|
||||||
|
version: match[3],
|
||||||
|
type: 'prod'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return { dependencies, packageManager: 'gradle' };
|
||||||
|
}
|
||||||
|
|
||||||
|
return { dependencies, packageManager: null };
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user