snyk-mvn-plugin
Version:
Snyk CLI Maven plugin
221 lines • 9.32 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.getMavenRepositoryPath = getMavenRepositoryPath;
exports.dependencyIdToArtifactPath = dependencyIdToArtifactPath;
exports.generateFingerprints = generateFingerprints;
exports.reportFingerprintTiming = reportFingerprintTiming;
exports.generateMavenFingerprints = generateMavenFingerprints;
exports.createMavenPurlWithChecksum = createMavenPurlWithChecksum;
const crypto = require("crypto");
const fs = require("fs");
const path = require("path");
const os = require("os");
const perf_hooks_1 = require("perf_hooks");
const util_1 = require("util");
const child_process_1 = require("child_process");
const packageurl_js_1 = require("packageurl-js");
const dependency_1 = require("./parse/dependency");
const index_1 = require("./index");
const execAsync = (0, util_1.promisify)(child_process_1.exec);
/**
* Get the Maven local repository path
* Uses the same Maven command that's being used for dependency resolution
*/
async function getMavenRepositoryPath(mavenCommand, providedPath) {
if (providedPath) {
return providedPath;
}
try {
// Use the same Maven command that's being used for dependency resolution
const { stdout } = await execAsync(`${mavenCommand} help:evaluate -Dexpression=settings.localRepository -DforceStdout -q`);
const repoPath = stdout.trim();
if (repoPath && fs.existsSync(repoPath)) {
return repoPath;
}
}
catch {
// Fall back to default if Maven command fails
}
// Default Maven repository locations
const homeDir = os.homedir();
const defaultPath = path.join(homeDir, '.m2', 'repository');
return defaultPath;
}
/**
* Convert dependency ID to the expected artifact file path in Maven repository
*/
function dependencyIdToArtifactPath(dependencyId, repositoryPath) {
const dep = (0, dependency_1.parseDependency)(dependencyId);
// Convert groupId dots to path separators
const groupPath = dep.groupId.replace(/\./g, path.sep);
// Build the artifact filename
// Format: artifactId-version[-classifier].type
const classifierPart = dep.classifier ? `-${dep.classifier}` : '';
const artifactFileName = `${dep.artifactId}-${dep.version}${classifierPart}.${dep.type}`;
// Construct full path
const artifactPath = path.join(repositoryPath, groupPath, dep.artifactId, dep.version, artifactFileName);
return artifactPath;
}
/**
* Calculate fingerprint for a single file
*/
async function calculateFileFingerprint(filePath, algorithm) {
const hash = crypto.createHash(algorithm);
const stream = fs.createReadStream(filePath);
return new Promise((resolve, reject) => {
stream.on('data', (data) => hash.update(data));
stream.on('end', () => resolve(hash.digest('hex')));
stream.on('error', reject);
});
}
/**
* Check if artifact file exists in the repository
*/
async function checkArtifactExists(artifactPath) {
try {
const stats = await fs.promises.stat(artifactPath);
return { exists: true, size: stats.size };
}
catch {
return { exists: false };
}
}
/**
* Sanitize artifact path to show only the relative path within the repository
*/
function sanitizeArtifactPath(artifactPath, repositoryPath) {
if (artifactPath.startsWith(repositoryPath)) {
// Remove the repository path prefix and any leading path separator
return artifactPath.slice(repositoryPath.length).replace(/^[/\\]+/, '');
}
// Fallback to the full path if sanitization isn't possible
return artifactPath;
}
/**
* Process a single dependency ID to generate fingerprint
*/
async function processSingleDependency(dependencyId, repositoryPath, algorithm) {
const startTime = perf_hooks_1.performance.now();
try {
const artifactPath = dependencyIdToArtifactPath(dependencyId, repositoryPath);
const { exists, size } = await checkArtifactExists(artifactPath);
// Sanitize the artifact path to show only the relative path within the repository
const sanitizedPath = sanitizeArtifactPath(artifactPath, repositoryPath);
if (!exists) {
const endTime = perf_hooks_1.performance.now();
return {
hash: '',
algorithm,
filePath: sanitizedPath,
fileSize: 0,
processingTime: endTime - startTime,
error: 'Artifact not found in repository',
};
}
const hash = await calculateFileFingerprint(artifactPath, algorithm);
const endTime = perf_hooks_1.performance.now();
return {
hash,
algorithm,
filePath: sanitizedPath,
fileSize: size || 0,
processingTime: endTime - startTime,
};
}
catch (error) {
const endTime = perf_hooks_1.performance.now();
const artifactPath = dependencyIdToArtifactPath(dependencyId, repositoryPath);
const sanitizedPath = sanitizeArtifactPath(artifactPath, repositoryPath);
return {
hash: '',
algorithm,
filePath: sanitizedPath,
fileSize: 0,
processingTime: endTime - startTime,
error: error instanceof Error ? error.message : 'Unknown error',
};
}
}
/**
* Process multiple dependency IDs with concurrency control
*/
async function processDependenciesConcurrently(dependencyIds, repositoryPath, algorithm, concurrency = 5) {
const results = [];
// Process dependencies in batches to control concurrency
for (let i = 0; i < dependencyIds.length; i += concurrency) {
const batch = dependencyIds.slice(i, i + concurrency);
const batchPromises = batch.map((id) => processSingleDependency(id, repositoryPath, algorithm));
const batchResults = await Promise.all(batchPromises);
results.push(...batchResults);
}
return results;
}
/**
* Generate fingerprints for all unique dependencies in Maven graphs
*/
async function generateFingerprints(mavenGraphs, options, mavenCommand) {
const fingerprintMap = new Map();
// Extract all unique node IDs from all graphs
const allNodeIds = new Set();
for (const graph of mavenGraphs) {
Object.keys(graph.nodes).forEach((nodeId) => allNodeIds.add(nodeId));
}
// Get Maven repository path using the same command
const repositoryPath = await getMavenRepositoryPath(mavenCommand, options.mavenRepository);
// Process all unique dependencies with concurrency control
const nodeIdArray = Array.from(allNodeIds);
const results = await processDependenciesConcurrently(nodeIdArray, repositoryPath, options.algorithm, 5);
// Build the map
for (let i = 0; i < nodeIdArray.length; i++) {
fingerprintMap.set(nodeIdArray[i], results[i]);
}
return fingerprintMap;
}
function reportFingerprintTiming(fingerprintMap) {
const results = Array.from(fingerprintMap.values());
// Don't report timing if there are no results
if (results.length === 0) {
return;
}
const processingTimes = results.map((r) => r.processingTime);
const totalTime = processingTimes.reduce((sum, time) => sum + time, 0);
const successful = results.filter((r) => !r.error).length;
const failed = results.filter((r) => r.error).length;
(0, index_1.debug)('=== Fingerprint Timing Summary ===');
(0, index_1.debug)(`Total artifacts: ${results.length}`);
(0, index_1.debug)(`Successful: ${successful}`);
(0, index_1.debug)(`Failed: ${failed}`);
(0, index_1.debug)(`Total time: ${totalTime.toFixed(2)}ms`);
(0, index_1.debug)(`Average time per artifact: ${(totalTime / results.length).toFixed(2)}ms`);
(0, index_1.debug)(`Fastest: ${Math.min(...processingTimes).toFixed(2)}ms`);
(0, index_1.debug)(`Slowest: ${Math.max(...processingTimes).toFixed(2)}ms`);
(0, index_1.debug)('=====================================');
}
/**
* Generate Maven fingerprints with reporting
*/
async function generateMavenFingerprints(mavenGraphs, fingerprintOptions, mavenCommand) {
const fingerprintMap = await generateFingerprints(mavenGraphs, fingerprintOptions, mavenCommand);
reportFingerprintTiming(fingerprintMap);
return fingerprintMap;
}
/**
* Create a Maven PURL with checksum qualifier from dependency information and fingerprint data
*/
function createMavenPurlWithChecksum(groupId, artifactId, version, fingerprintData, classifier, type) {
const standardQualifiers = {};
if (classifier) {
standardQualifiers.classifier = classifier;
}
if (type && type !== 'jar') {
standardQualifiers.type = type;
}
// Add checksum qualifier to standard qualifiers
if (fingerprintData && !fingerprintData.error && fingerprintData.hash) {
const checksumValue = `${fingerprintData.algorithm.toLowerCase()}:${fingerprintData.hash.toLowerCase()}`;
standardQualifiers.checksum = checksumValue;
}
const purl = new packageurl_js_1.PackageURL('maven', groupId, artifactId, version, Object.keys(standardQualifiers).length > 0 ? standardQualifiers : undefined, undefined);
return purl.toString();
}
//# sourceMappingURL=fingerprint.js.map