Files
nexe/lib/index.js
T
2011-11-26 17:00:12 -06:00

227 lines
4.4 KiB
JavaScript

var sardines = require('sardines'),
child_process = require('child_process'),
spawn = child_process.spawn,
exec = child_process.exec,
fs = require('fs'),
mkdirp = require('mkdirp'),
http = require('http'),
celeri = require('celeri');
const ETC_DIR = '/usr/local/etc/nexe';
const RUNTIMES_DIR = ETC_DIR + '/runtimes';
const CURRENT_RUNTIME_DIR = ETC_DIR + '/current-runtime';
/**
*/
function fileExists(file) {
try {
return !!fs.lstatSync(file);
} catch(e) {
return false;
}
}
/**
* modify the input node.js file to evaluate nexe
*/
function wrapNode(path) {
var nodePath = path + '/src/node.js',
content = fs.readFileSync(nodePath,'utf8');
content = content.replace(/\(function\(process\) \{/,'function(process){ \n process._eval = require("nexe"); ');
fs.writeFileSync(nodePath, content);
}
/**
*/
function nexeMain(path) {
var pkgPath = path + '/package.json';
if(!fileExists(pkgPath)) return null;
var script = JSON.parse(fs.readFileSync(pkgPath,'utf8'))['nexe-main'];
return script ? fs.realpathSync(path+'/' + script) : null;
}
/**
*/
function prepare(version, callback) {
var runtimeDir = 'node-v' + version,
runtimeDirPath = RUNTIMES_DIR + '/' + runtimeDir,
runtimeTarPath = RUNTIMES_DIR + '/'+ runtimeDir + '.tar';
function onComplete() {
callback(false, {
path: runtimeDirPath,
libDir: runtimeDirPath + '/lib'
});
}
//file exists? already prepared
if(fileExists(runtimeDirPath)) return onComplete();
mkdirp(runtimeDirPath, 0755, function(err) {
if(err) return callback(err);
//download the node.js runtime if it doesn't exist
http.get({ host: 'nodejs.org', port: 80, path: '/dist/v' + version + '/node-v' + version + '.tar.gz' }, function(res) {
res.setEncoding('binary');
var output = fs.createWriteStream(runtimeTarPath, { 'flags': 'a' }),
size = Number(res.headers['content-length'] || 0),
progress = 0;
res.on('data', function(chunk) {
progress += chunk.length;
celeri.progress('Downloading ' + runtimeDir, Math.floor(progress/size * 100));
output.write(chunk, 'binary');
});
res.on('end', function() {
output.end();
function onUnzipped(err) {
fs.unlinkSync(runtimeTarPath);
if(err) return callback(err);
wrapNode(runtimeDirPath);
onComplete();
}
console.log('Configuring %s', runtimeDirPath);
exec('tar -xf ' + runtimeTarPath + '; cd ' + runtimeDirPath + '; ./configure', { cwd: RUNTIMES_DIR }, onUnzipped);
});
}).on('error', function(e) {
try {
fs.rmdirSync(runtimeDirPath);
} catch(e) { }
callback(new Error('Unable to download node '+version+': '+e.message));
});
});
}
/**
*/
function findIncludes(dir, inc) {
if(!inc) inc = [];
fs.readdirSync(dir).forEach(function(name) {
if(name.substr(0,1) == '.' || name == 'node_modules') return;
var subPath = dir + '/' + name;
if(fs.statSync(subPath).isDirectory()) {
findIncludes(subPath, inc);
} else if(name.match(/\.js$/)) {
inc.push(subPath);
}
});
return inc;
}
/**
*/
exports.compile = function(ops, callback) {
if(!ops.entries) throw new Error('You must provide entry javascript files.');
if(!ops.runtime) throw new Error('A node.js runtime version must be provided.');
if(!ops.output) throw new Error('Output must be provided.');
ops.output = ops.output.replace('~', process.env.HOME);
prepare(ops.runtime, function(err, node) {
if(err) return callback(err);
var include = [],
entries = [];
ops.entries.forEach(function(entry) {
var main = nexeMain(entry);
if(main) {
findIncludes(entry).forEach(function(path) {
include.push({
script: path,
alias: path.replace(entry,'')
});
});
entries.push(main);
} else {
entries.push(entry);
}
});
console.log('Making nexe.js');
//combine the JS
sardines.package({ input: entries, include: include, output: node.libDir + '/nexe.js' }, function(err) {
if(err) return callback(new Error('Unable to make nexe.js'));
console.log('Making executable');
var proc = spawn('make', [], { cwd: node.path });
proc.stdout.on('data', function(data) {
console.log(data.toString());
});
proc.stderr.on('data', function(data) {
console.error(data.toString());
});
proc.on('exit', function() {
exec('cp ./out/Release/node ' + ops.output, { cwd: node.path }, callback);
});
});
});
}