Compare commits

...

2 Commits

3 changed files with 317 additions and 0 deletions

1
.gitignore vendored
View File

@ -10,4 +10,5 @@ node
node_modules
work
app.properties
.npmcache

View File

@ -1,6 +1,9 @@
{
"name": "blueocean-admin",
"version": "0.0.1",
"scripts": {
"preinstall": "(export I=../projectBuilder/dependencyLinker.js && test -e $I && node $I) || true"
},
"devDependencies": {
"babel-preset-es2015": "^6.5.0",
"babel-preset-react": "^6.5.0",
@ -14,5 +17,8 @@
"react": "^0.14.5",
"react-dom": "^0.14.5",
"window-handle": "^1.0.0"
},
"blueoceanDependencies": {
"jenkins-bootstrap": "jenkins-design-language/jenkins-bootstrap"
}
}

View File

@ -0,0 +1,310 @@
// this script should not require any non-core-modules,
// so it could be called to bootstrap other modules,
// without requiring npm modules in projectBuilder
// (i.e.: without npm install in projectBuilder)
var
fs = require('fs'),
path = require('path'),
execFile = require('child_process').execFile,
execFileSync = require('child_process').execFileSync,
semver = require('semver');
var skipGitDependencies = process.env.SKIP_GIT_DEPS === 'true';
var projectRoot = path.resolve(__dirname);
var globalNpmCacheFolder = path.join(projectRoot, '.npmcache');
var installedCacheFile = path.join(projectRoot, 'installedCache.tmp');
var installedCache = {};
var installedCacheFileExists = fs.existsSync(installedCacheFile);
if (installedCacheFileExists) {
try {
installedCache = JSON.parse(fs.readFileSync(installedCacheFile));
} catch (e) {}
}
function isNpmInstallNeeded(modulePath) {
var modulesThatShouldExist = [];
var packageJson = require(path.join(modulePath, 'package.json'));
var nodeModulesPath = path.join(modulePath, 'node_modules');
var allDeps = {};
if (packageJson.blueoceanDependencies) {
for (var tDep in packageJson.blueoceanDependencies) {
if (modulesThatShouldExist.indexOf(tDep) < 0) {
modulesThatShouldExist.push(tDep);
allDeps[tDep] = '*';
}
}
}
if (packageJson.devDependencies) {
for (var devDep in packageJson.devDependencies) {
if (modulesThatShouldExist.indexOf(devDep) < 0) {
modulesThatShouldExist.push(devDep);
allDeps[devDep] = packageJson.devDependencies[devDep];
}
}
}
if (packageJson.dependencies) {
for (var dep in packageJson.dependencies) {
if (modulesThatShouldExist.indexOf(dep) < 0) {
modulesThatShouldExist.push(dep);
allDeps[dep] = packageJson.dependencies[dep];
}
}
}
if (!fs.existsSync(nodeModulesPath)) {
return true;
}
var filtered = fs.readdirSync(nodeModulesPath).filter(function(file) {
return fs.statSync(path.join(nodeModulesPath, file)).isDirectory() && modulesThatShouldExist.indexOf(file) >= 0;
});
if (filtered.length < modulesThatShouldExist.length) {
return true;
}
for (var d in allDeps) {
var packageJsonIs = require(path.join(nodeModulesPath, d, 'package.json'));
var versionOfDepIs = packageJsonIs.version;
var versionOfDepShould = allDeps[d];
var isGit = versionOfDepShould.indexOf('git') >= 0 || versionOfDepShould.indexOf('/') >= 0;
if (isGit) {
if (skipGitDependencies) {
continue;
}
var ref = '';
if (versionOfDepShould.indexOf('#') > 0) {
ref = versionOfDepShould.substring(versionOfDepShould.indexOf('#') + 1);
}
var repo = packageJsonIs._resolved.substring(0, packageJsonIs._resolved.indexOf('#'));
if (!repo) {
return true;
}
var isGitHead = packageJsonIs.gitHead;
// git ls-remote repo HEAD ref
var shouldGitHead = getRemoteCommitHash(repo, ref);
if (isGitHead !== shouldGitHead) {
return true;
}
} else if (!semver.satisfies(versionOfDepIs, versionOfDepShould)) {
return true;
}
}
return false;
}
function getRemoteCommitHash(repo, ref) {
repo = repo.replace('git+https', 'git');
var res = execFileSync('git', ['ls-remote', repo, 'HEAD', ref]);
var output = res.toString();
return output.substring(0, output.indexOf('\t'));
}
/**
* run npm install in a given location
*
* @param {string} location current working directory where to run npm install
* @param callback
*/
function npmInstall(location, callback) {
execFile('npm', ['install', '--cache=' + globalNpmCacheFolder], {
cwd: location
}, callback);
}
/**
* Creates a two dimensional array of the key-value pairs for object, e.g. [[key1, value1], [key2, value2]].
*
* this is a helper function cloning _.pairs, as this script must get along without external dependencies
* @param object
*/
function pairs(object) {
var array = [];
for (var property in object) {
if (object.hasOwnProperty(property)) {
array.push([property, object[property]]);
}
}
return array;
}
function rmdir (dir) {
var list = fs.readdirSync(dir);
for(var i = 0; i < list.length; i++) {
var filename = path.join(dir, list[i]);
var stat = fs.statSync(filename);
if (filename === '.' || filename === '..') {
// pass these files
} else if (stat.isDirectory()) {
// rmdir recursively
rmdir(filename);
} else {
// rm fiilename
fs.unlinkSync(filename);
}
}
fs.rmdirSync(dir);
};
function link (location, symlinkDestination, dependency, dependencies, callback) {
var modulePath = path.join(projectRoot, dependency[1]);
console.log('link ' + dependency[1] + ' into ' + path.relative(projectRoot, location));
fs.symlink(path.relative(path.join(process.cwd(), 'node_modules') , modulePath), symlinkDestination, 'dir', function (err) {
if (err && err.code !== 'EEXIST') {
return callback(err);
}
// go to the just 'installed'/symlinked module and call npm install
if (installedCache[dependency[0]]) {
console.log('skipping ' + dependency[0] + ', already installed before');
return linkDependency(location, dependencies, callback);
}
installedCache[dependency[0]] = dependency[1];
// check if npm install is needed
if (!isNpmInstallNeeded(symlinkDestination)) {
console.log('skipping ' + dependency[0] + ', package.json fulfilled');
return linkDependency(location, dependencies, callback);
}
console.log('installing ' + symlinkDestination + '...');
npmInstall(symlinkDestination, function() {
// call installTaibikaDependencies for the just installed module
// to install dependencies recursively, as blueoceanDependencies might not have 'run link' configuration
// linkBlueoceanDependencies(symlinkDestination, function () {
linkDependency(location, dependencies, callback);
// });
});
});
}
/**
* install a list of (local) dependencies into a given location
*
* @param {string} location working directory where to call "npm install" (relative to projectRoot)
* @param {Array} dependencies list of tuples with (dependency-name, relativeLocation), used as a queue
* @param {function} callback callback to execute when all dependencies have been installed
*/
function linkDependency(location, dependencies, callback) {
if (dependencies.length > 0) {
var dependency = dependencies.shift();
var modulePath = path.join(projectRoot, dependency[1]);
// we fetch the name from the package.json, as it is more accurate than using the folder name
var moduleName = require(path.join(modulePath, 'package.json')).name;
var nodeModules = path.join(location, 'node_modules');
// 'node_modules' might not yet exist (e.g.: npm install did not yet run, or there are no dependencies beside
// blueoceanDependencies at all
fs.mkdir(nodeModules, function (err) {
if (err && err.code !== 'EEXIST') {
return callback(err);
}
var symlinkDestination = path.join(nodeModules, moduleName);
fs.exists(symlinkDestination, function (exists) {
if (exists) {
fs.lstat(symlinkDestination, function (err, stats) {
if (err) return callback(err);
if (!stats.isSymbolicLink()) {
// delete first
rmdir(symlinkDestination);
}
link(location, symlinkDestination, dependency, dependencies, callback);
});
} else {
link(location, symlinkDestination, dependency, dependencies, callback);
}
});
});
}
else {
callback();
}
}
/**
* install blueoceanDependencies from package.json
* this is done recursively, i.e. declared blueoceanDependencies are checked
* for blueoceanDependencies,
*
* @param {string} location
* @param callback
*/
function linkBlueoceanDependencies(location, callback) {
var expectedPackageJsonLocation = path.join(location, 'package.json');
fs.exists(expectedPackageJsonLocation, function (packageJsonExists) {
if (packageJsonExists) {
var packageJsonContent = require(expectedPackageJsonLocation);
if (packageJsonContent.hasOwnProperty('blueoceanDependencies')) {
// transform the dependency map into an array with [key, value] pairs, so we can use it as a queue
var dependencies = pairs(packageJsonContent.blueoceanDependencies);
// install each dependency one after the other (no asynchronous execution)
linkDependency(location, dependencies, callback);
}
else {
// no blueoceanDependencies in package.json, nothing to do here!
callback();
}
}
else {
// no package.json found: nothing to do here (why has this function been called in the first place?)
callback();
}
});
}
console.log( process.cwd(), process.argv.slice(2))
// call the main function iff this file is called directly (not required by any other module)
if (require.main === module) {
main.apply(process.argv.slice(2));
}
/**
* main function to execute if the script is executed directly
* @param {string} [directory] directory where to look for package.json
*/
function main(directory) {
var d = directory || process.cwd();
linkBlueoceanDependencies(d, function (err) {
if (err) return console.log(err);
console.log('Finished linking blueoceanDependencies in "' + d.substr(projectRoot.length) + '".');
if (installedCacheFileExists) {
try {
var newContent = JSON.parse(fs.readFileSync(installedCacheFile));
for (var m in installedCache) {
newContent[m] = installedCache[m];
}
fs.writeFileSync(installedCacheFile, JSON.stringify(newContent));
} catch (e) {}
}
});
}
module.exports = {
linkBlueoceanDependencies: linkBlueoceanDependencies
};