'use strict';
var fs = require('fs');
var path = require('path');
'use strict';
var fs = require('fs');
var path = require('path');
Helper to deep merge hash objects
var merge = require('./utils/merge');
To run shell like commands
var shell = require('shelljs');
To properly escape shell commands arguments
var quote = require('shell-quote').quote;
To create temporary directories
var Tempdir = require('temporary/lib/dir');
var Tempfile = require('temporary/lib/file');
Main Constructor
function BashPack(config) {
The overall defaults if not specified
var _defaults = {
logMute: true, // no output by default
logLevel: 'info',
logTimestamp: false,
logPrettyPrint: false,
includeNode: true,
exclude: [ '.git/*','doc/*','test/*' ],
name: 'the anonymous module',
outputFile: 'bashpack.run',
configName: 'bashpack.json',
libs: [],
force: false,
excludeFile: undefined // no exclude file speficied
};
Merge the config specified with our defaults
var _settings = merge(_defaults,config);
Configure a logger with our settings
var logger = require('./utils/logger.js')(_settings);
Safe a pointer to ourself var self = this;
Main Build function projectDir : the top directory to pack (String) startScript : the node script we will run (String) options : can override constructor options (Hash) callback : function run on error and completion (Function)
this.build = function bashPackBuild(projectDir, startScript ,options,callback) {
ProjectDir needs to be specified
logger.debug('projectDir specified %s',projectDir);
if(projectDir === undefined) {
return callback(new Error(projectDir+' is not specified'));
}
ProjectDir needs to be a directory
if(!shell.test('-d',projectDir)) {
return callback(new Error(projectDir+' is not a directory or does not exist'));
}
var configFile;
var configFileSettings = {};
Try to find a bashpack config file
if (!options.configFile) {
If no config is specified try to find it from the projectDir
configFile = path.join(projectDir,_settings.configName);
} else {
configFile = options.configFile;
}
Check if configFile is a file & exists
if(!shell.test('-f',configFile)) {
Only error if we have manually specified it
if (options.configFile) {
return callback(new Error('configFile ' + configFile + ' is not a file or does not exist.'));
}
} else {
configFile exists - let's see if it is valid JSON
try {
var configFileContents = fs.readFileSync(configFile,'utf8');
configFileSettings = JSON.parse(configFileContents);
} catch (err) {
return callback(new Error('error reading configFile "' + configFile + '" : ' + err.message));
}
}
Read settings from ConfigFile
var _settings2 = merge(_settings,configFileSettings);
Override settings with options specified
var settings = merge(_settings2,options);
StartScript needs to be specified
logger.debug('startScript specified %s',startScript);
if(startScript === undefined) {
return callback(new Error('startScript '+startScript+' is not specified'));
}
Calculate the full path of the startscript
var fullStartScript = path.resolve(projectDir ,startScript);
logger.debug('full path for startScript %s',fullStartScript);
Check if startScript is a file & exists
if(!shell.test('-f',fullStartScript)) {
return callback(new Error('startScript ' + fullStartScript+' is not a file or does not exist. Remember that it needs to be relative to the projectDir'));
}
The resulting archive will be written to outputFile
var outputFile = settings.outputFile;
logger.debug('outputFile speficied %s',outputFile);
if (shell.test('-f',outputFile)) {
if (!settings.force) {
return callback(new Error('outputFile "'+outputFile +'" already exists. Use --force to overwrite'));
} else {
logger.warn('outputFile "'+outputFile+'" already exists. But we can we remove it as --force has been specified');
shell.rm(outputFile);
}
} else {
logger.debug('outputFile does not yet exist. Good!');
}
outputFile needs to be specified
if(outputFile === undefined) {
return callback(new Error('outputFile '+outputFile+' is not specified'));
}
var excludeFile ;
First check if a manual excludeFile was specified excludeFile contains the patterns passed to tar to exclude files from the archive
if (settings.excludeFile !== undefined) {
if(!shell.test('-f',excludeFile)) {
return callback(new Error('excludeFile "' + settings.excludeFile+'" is not a file or does not exist. '));
} else {
excludeFile = settings.excludeFile;
}
}else {
logger.debug('no excludeFile specified');
}
let's check the exclude patterns
if (settings.excludeFile === undefined) {
if (settings.exclude.length > 0) {
logger.info('Excluding following patterns: ',JSON.stringify(settings.exclude));
We create a temporary excludeFile
var TempExcludeFileObject = new Tempfile();
excludeFile = TempExcludeFileObject.path;
fs.writeFileSync(excludeFile, settings.exclude.join('\n'));
} else {
logger.debug('No excludes specified');
}
}
logger.debug('Libs specified: ' + JSON.stringify(settings.libs));
check libs specified exist
if (settings.libs) {
var error;
settings.libs.forEach(function(lib) {
logger.debug('Checking if lib "'+lib+'" exists?');
if (!shell.test('-e',lib)) {
error = new Error('We could not find the lib specified. '+lib);
} else {
logger.debug('OK lib "'+lib+'" exists');
}
});
if (error) {
return callback(error);
}
}
Determine the node binary
var nodePath ;
A node binary was specified
if (settings.nodeBinary) {
if(!shell.test('-f',settings.nodeBinary)) {
return callback(new Error('We could not find the node-binary specified. '+settings.nodeBinary));
} else {
nodePath = settings.nodeBinary;
}
} else {
Autodetect node file from path
nodePath = shell.which('node');
if (nodePath === null) {
return callback(new Error('could not find a node binary in your path'));
}
}
logger.info('Node binary found at: "'+nodePath+'"');
var name = settings.name;
Exit on all errors
shell.config.fatal = true;
Silence the scripts output
shell.config.silent = true;
var makeselfPath = path.join(__dirname,'..','makeself','makeself.sh');
var execArgs = [];
var execArgsString = '';
var execResult ;
First pass at creating the run file This will include the files from the projectDir that have not been excluded
execArgs = [
'--bzip2',
'--nox11',
projectDir,
outputFile,
name,
'./.bashpack/bin/start.sh',
startScript
];
If we have an excludeFile, we pass it as another options
if (excludeFile) {
execArgs.unshift(path.resolve(excludeFile));
execArgs.unshift('--exclude-file');
execArgs.unshift('.git/*'); execArgs.unshift('--exclude');
}
execArgsString = quote(execArgs);
logger.debug('First Pass: arguments for makeself: '+execArgsString);
execResult = shell.exec( makeselfPath + ' '+execArgsString);
if (execResult.code !== 0) {
return callback(new Error('First pass failed',execResult.output));
}
logger.debug(execResult.output);
Get a temporary directory
var tempDirObject = new Tempdir();
var tempDir = tempDirObject.path;
logger.debug('Using temporary directory '+tempDir);
Create the temporary bin directory
var bashPackBinDir = tempDir+'/.bashpack/bin';
logger.debug('Creating the '+bashPackBinDir+' directory');
shell.mkdir('-p', bashPackBinDir);
Check if we need to include the node binary
if (!settings.skipNodeInclude) {
Prepare nodejs binary to be included in the run file
logger.debug('cp '+nodePath +' '+ bashPackBinDir);
shell.cp(nodePath,bashPackBinDir);
var dstNode= path.join(bashPackBinDir,'node');
logger.debug('chmod u+x '+dstNode);
shell.chmod('u+x',dstNode);
} else {
logger.debug('skipping inclusion of node-binary');
}
Prepare start script to be included in the run file
var startFile = path.join(__dirname, '..', 'scripts','start.sh');
logger.debug('Copying "'+startFile+'" to "'+bashPackBinDir+'"');
shell.cp(startFile,bashPackBinDir);
shell.chmod('u+x',bashPackBinDir+'/start.sh');
Create the temporary lib directory
var bashPackLibDir = tempDir+'/.bashpack/lib';
logger.debug('Creating the '+bashPackLibDir+' directory');
shell.mkdir('-p', bashPackLibDir);
settings.libs.forEach(function(lib) {
logger.debug('Copying lib "'+lib+'" to "'+bashPackLibDir+'"');
shell.cp(lib,bashPackLibDir);
});
Append the extra files to the run file
execArgs = [
'--append',
'--nox11',
tempDir,
outputFile
];
execArgsString = quote(execArgs);
logger.debug('Second Pass: arguments for makeself: '+execArgsString);
shell.exec( makeselfPath + ' '+execArgsString);
All went well
return callback(null);
};
Create main config file
this.init = function bashPackInit(projectDir, options, callback) {
Override settings
var settings = merge(_settings,options);
ProjectDir needs to be specified
logger.debug('projectDir specified %s',projectDir);
if(projectDir === undefined) {
return callback(new Error(projectDir+' is not specified'));
}
ProjectDir needs to be a directory
if(!shell.test('-d',projectDir)) {
return callback(new Error('projectDir "'+projectDir+'" is not a directory or does not exist'));
}
Check if configFile exists
var configFile = path.join(projectDir,settings.configName);
if(shell.test('-f',configFile)) {
if (!settings.force) {
return callback(new Error('configFile "'+configFile+'" already exists. Use --force to overwrite'));
} else {
logger.warn('configFile "'+configFile+'" already exists. But we can we remove it as --force has been specified');
shell.rm(configFile);
}
}
logger.info('Creating bashpack configFile "'+configFile+'"');
shell.cp(path.join(__dirname,'..','template','bashpack.json'),projectDir);
};
}
exports = module.exports = BashPack;