UNPKG

node-java-maven

Version:

Utility for Node's java module to load mvn dependencies.

493 lines (440 loc) 15.1 kB
'use strict'; var fs = require('fs'); var async = require('async'); var xml2js = require('xml2js'); var request = require('request'); var path = require('path'); var mkdirp = require('mkdirp'); var Dependency = require('./lib/dependency'); module.exports = function(/*options, callback*/) { var options; var callback; if (arguments.length == 1) { options = {}; callback = arguments[0]; } else if (arguments.length == 2) { options = arguments[0]; callback = arguments[1]; } else { throw new Error('Expected 1 or 2 arguments not ' + arguments.length); } options = options || {}; options.packageJsonPath = options.packageJsonPath || 'package.json'; options.repositories = options.repositories || [ { id: 'maven-central', url: 'https://repo1.maven.org/maven2/' } ]; options.localRepository = options.localRepository || path.join(getUserHome(), '.m2/repository'); options.concurrency = options.concurrency || 1; var dependencies = {}; var exclusions = []; var errors = []; var dependencyQueue = async.queue(processDependency, options.concurrency); dependencyQueue.drain = complete; return go(callback); /***************************************************************************************/ function go(callback) { return readPackageJson(function(err, packageJson) { if (err) { return callback(err); } if (packageJson.java.repositories) { options.repositories = options.repositories.concat(packageJson.java.repositories); } if (!packageJson.java.dependencies) { return callback(new Error("Could not find java.dependencies property in package.json")); } if (!(packageJson.java.dependencies instanceof Array)) { return callback(new Error("java.dependencies property in package.json must be an array.")); } if (packageJson.java.exclusions) { if (!(packageJson.java.exclusions instanceof Array)) { return callback(new Error("java.exclusions property in package.json must be an array.")); } else { exclusions = packageJson.java.exclusions; } } return packageJson.java.dependencies.forEach(function(d) { dependencyQueuePush(Dependency.createFromObject(d, 'package.json')); }); }); } function complete() { debug("COMPLETE"); if (errors.length > 0) { return callback(errors); } var classpath = getClasspathFromDependencies(dependencies); return callback(null, { classpath: classpath, dependencies: dependencies }); } function getUserHome() { return process.env.HOME || process.env.HOMEPATH || process.env.USERPROFILE; } function dependencyQueuePush(dependency, callback) { var dependencyArray = dependency; if (!(dependencyArray instanceof Array)) { dependencyArray = [dependency]; } dependencyArray.forEach(function(d) { d.state = 'queued'; if (!d.groupId) { throw new Error('missing required field [groupId] for queue: ' + d.toString()); } if (!d.artifactId) { throw new Error('missing required field [artifactId] for queue: ' + d.toString()); } if (!d.version) { throw new Error('missing required field [version] for queue: ' + d.toString()); } }); return dependencyQueue.push(dependency, callback); } function processDependency(dependency, callback) { return resolveDependency(dependency, function(err) { dependency.markCompleted(); if (err) { errors.push(err); } var c = callback; callback = function() {}; return c(); }); } function resolveDependency(dependency, callback) { var existingDependency = dependencies[dependency.toString()]; if (existingDependency) { dependency.state = 'waitUntilComplete'; return existingDependency.waitUntilComplete(callback); } debug('resolving: ' + dependency.toString()); dependencies[dependency.toString()] = dependency; dependency.state = 'resolvePom'; return resolvePom(dependency, function(err) { if (err) { return callback(err); } return processJar(dependency, callback); }); function processJar(dependency, callback) { dependency.state = 'processJar'; if (dependency.getPackaging() == 'jar' || dependency.getPackaging() == 'bundle') { return resolveJar(dependency, function(err) { if (err) { return callback(err); } return processParents(dependency, callback); }); } else { return processParents(dependency, callback); } } function processParents(dependency, callback) { dependency.state = 'processParents'; var parent = dependency.getParent(); if (parent) { return processDependency(parent, function(err) { if (err) { return callback(err); } return processChildDependencies(dependency, callback); }); } else { return processChildDependencies(dependency, callback); } } function processChildDependencies(dependency, callback) { dependency.state = 'processChildDependencies'; var childDependencies = dependency .getDependencies() .filter(function(d) { var isExclusion = exclusions.reduce(function(isExclusion,exclusion){ if (isExclusion){ return isExclusion; } return exclusion.groupId === d.groupId && exclusion.artifactId === d.artifactId; },false ); return d.scope != 'test' && d.optional != true && !isExclusion; }); if (childDependencies.length > 0) { childDependencies.forEach(function(d) { resolveDependencyUnknowns(d, dependency); }); dependencyQueuePush(childDependencies); } return callback(); } } // END resolveDependency function resolvePom(dependency, callback) { var pomPath = path.resolve(options.localRepository, dependency.getPomPath()); return fs.exists(pomPath, function(exists) { if (exists) { return readFile(dependency, pomPath, callback); } else { return download(dependency, pomPath, callback); } }); function download(dependency, pomPath, callback) { return downloadFile(dependency.getPomPath(), pomPath, dependency.reason, function(err, url) { if (err) { return callback(err); } dependency.pomUrl = url; return readFile(dependency, pomPath, callback); }); } function readFile(dependency, pomPath, callback) { dependency.pomPath = pomPath; return fs.readFile(pomPath, 'utf8', function(err, data) { if (err) { return callback(err); } return loadFile(dependency, data, callback); }); } function loadFile(dependency, data, callback) { xml2js.parseString(data, function(err, xml) { if (err) { return callback(err); } dependency.pomXml = xml; if (dependency.getParent()) { var parentDep = dependency.getParent(); debug("resolving parent: " + parentDep); return resolvePom(parentDep, function(err, parentPomXml) { if (err) { return callback(err); } xml.project.parent[0].pomXml = parentPomXml; return callback(null, xml); }); } return callback(null, xml); }); } } // END resolvePom function resolveJar(dependency, callback) { var jarPath = path.resolve(options.localRepository, dependency.getJarPath()); return fs.exists(jarPath, function(exists) { if (exists) { dependency.jarPath = jarPath; return callback(); } else { return downloadFile(dependency.getJarPath(), jarPath, dependency.reason, function(err, url) { if (err) { return callback(err); } dependency.jarUrl = url; dependency.jarPath = jarPath; return callback(); }); } }); } function readPackageJson(callback) { return fs.readFile(options.packageJsonPath, 'utf8', function(err, packageJsonString) { if (err) { return callback(err); } try { var packageJson = JSON.parse(packageJsonString); } catch (ex) { return callback(ex); } if (!packageJson.java) { return callback(new Error("Could not find java property in package.json")); } return callback(null, packageJson); }); } function getClasspathFromDependencies(dependencies) { return Object.keys(dependencies) .map(function(depName) { return dependencies[depName].jarPath; }) .filter(function(p) { return p; }); } function resolveDependencyUnknowns(dependency, parent) { var p; if (dependency.groupId == '${project.groupId}') { dependency.groupId = parent.groupId; } if (!dependency.version) { var d = findInDependencyManagement(parent, dependency); if (d) { dependency.version = d.version; } } // this is a hack. should probably search the module list or something if (!dependency.version && dependency.groupId == parent.groupId) { dependency.version = parent.version; } if (!dependency.groupId || !dependency.artifactId || !dependency.version) { throw new Error('could not resolve unknowns: ' + dependency.toString()); } if (dependency.groupId) { dependency.groupId = resolveSubstitutions(dependency.groupId, parent, dependency); } if (dependency.artifactId) { dependency.artifactId = resolveSubstitutions(dependency.artifactId, parent, dependency); } if (dependency.version) { var changed; do { var newValue = resolveSubstitutions(dependency.version, parent, dependency); changed = dependency.version != newValue; dependency.version = newValue; } while(changed); } } function resolveSubstitutions(str, pom, dependency) { str = str.replace(/\$\{(.*?)\}/g, function(m, propertyName) { if(propertyName == 'project.version' || propertyName == 'version') { return pom.version; } if(propertyName == 'project.parent.version') { return pom.pomXml.project.parent[0].version[0]; } if(propertyName == 'project.parent.groupId') { return pom.pomXml.project.parent[0].groupId[0] } var property = resolveProperty(propertyName, pom); return property instanceof Array ? property.slice(-1) : property; }); return resolveVersionRange(str, dependency); } function resolveVersionRange(str, dependency) { var m = str.match(/[\[\(](.*),(.*)[\]\)]/); if(m) { var existingDependency = findExistingDependencyWithoutVersion(dependency); if (existingDependency) { return existingDependency.version; } if(m[2]) { return m[2]; } else if(m[1]) { return m[1]; } } return str; } function findExistingDependencyWithoutVersion(dependency) { var matching = Object.keys(dependencies).filter(function(dependencyId) { var d = dependencies[dependencyId]; return (d.groupId == dependency.groupId) && (d.artifactId == dependency.artifactId); }); if (matching.length == 1) { return dependencies[matching[0]]; } return null; } function resolveProperty(propertyName, pom) { if (pom.pomXml && pom.pomXml.project) { var project = pom.pomXml.project; if (project.properties && project.properties.length == 1) { var properties = project.properties[0]; if (properties[propertyName]) { return properties[propertyName]; } } if (project.parent && project.parent.length == 1) { return resolveProperty(propertyName, project.parent[0]); } } throw new Error("Could not resolve property: " + propertyName); } function findInDependencyManagement(parent, dependency) { var list = parent .getDependencyManagementDependencies() .filter(function(d) { return (d.groupId == dependency.groupId) && (d.artifactId == dependency.artifactId); }) list = list.filter(function(item, pos) { for(var i = 0; i < pos; i++) { if(list[i].toString() == item.toString()) { return false; } } return true; }); if (list.length == 1) { var d = list[0]; if (d.version == '${project.version}') { d.version = parent.version; } return d; } else if (list.length > 1) { throw new Error('multiple matches found in dependency management for ' + dependency.toString() + ' [' + list + ']'); } var p = parent.getParent(); if (p) { return findInDependencyManagement(dependencies[p.toString()], dependency); } return null; } function downloadFile(urlPath, destinationFile, reason, callback) { var repositoryIndex = 0; return mkdirp(path.dirname(destinationFile), function(err) { if (err) { return callback(err); } var error = null; var foundUrl = null; return async.whilst( function() { return (repositoryIndex < options.repositories.length) && !foundUrl; }, function(callback) { var repository = options.repositories[repositoryIndex]; var url = repository.url + urlPath; var req_options = { url: url }; if (repository.hasOwnProperty('credentials')) { var username = repository.credentials.username; var password = repository.credentials.password; req_options = { url: url, auth: { user: username, password: password } }; } debug('downloading ' + url); var r = request(req_options); r.on('response', function(response) { if (response.statusCode != 200) { error = new Error('download failed for ' + url + (reason ? ' (' + reason + ')' : '') + ' [status: ' + response.statusCode + ']'); return callback(); } else { var out = fs.createWriteStream(destinationFile); out.on('finish', function() { foundUrl = url; return callback(); }); out.on('error', function(err) { return callback(); }); return r.pipe(out); } }); repositoryIndex++; }, function() { if (foundUrl) { return callback(null, foundUrl); } return callback(error); } ); }); } function debug() { if (options.debug) { console.log.apply(console, arguments); } } };