diff --git a/bin/nexe b/bin/nexe index 4af18e6..8b13eb4 100755 --- a/bin/nexe +++ b/bin/nexe @@ -43,7 +43,11 @@ function toRelative(pt) { -require('../lib').compile({ input: toRelative(argv.i), output: toRelative(argv.o), runtime: argv.r, temp: toRelative(argv.t) }, function(error) { +require('../lib').compile({ + input : toRelative(argv.i), + output : toRelative(argv.o), + nodeVersion : argv.r, + temp : toRelative(argv.t) }, function(error) { if(error) console.log(error.message); }); diff --git a/lib/exe.js b/lib/exe.js new file mode 100644 index 0000000..98c1d07 --- /dev/null +++ b/lib/exe.js @@ -0,0 +1,335 @@ +var async = require("async"), +outcome = require("outcome"), +mkdirp = require("mkdirp"), +request = require("request"), +path = require("path"), +fs = require("fs"), +ncp = require("ncp").ncp, +ProgressBar = require("progress"), +child_process = require("child_process"), +glob = require("glob"), +sardines = require("sardines"), +spawn = child_process.spawn; + +/** + */ + +exports.compile = function (options, complete) { + + var nodeCompiler, nexeEntryPath; + + async.waterfall([ + + /** + * first download node + */ + + function downloadNode (next) { + _downloadNode(options.nodeVersion, "/tmp/nexe", next); + }, + + /** + * bundle the application into one script + */ + + function combineProject (nc, next) { + nodeCompiler = nc; + _log("bundle %s", options.input); + sardines({ entries: [options.input], platform: "node" }, next); + }, + + /** + * write the bundle to the lib directory of the target node version + */ + + function writeBundle (source, next) { + bundlePath = path.join(nodeCompiler.dir, "lib", "nexe.js"); + _log("bundle -> %s", bundlePath); + fs.writeFile(bundlePath, source, next); + }, + + /** + * monkeypatch some files so that the nexe.js file is loaded when the app runs + */ + + function monkeyPatchNodeConfig (next) { + _monkeyPatchNodeConfig(nodeCompiler, next); + }, + + /** + * compile the node application + */ + + function makeExe (next) { + _log("make"); + nodeCompiler.make(next); + }, + + /** + */ + + function makeOutputDirectory (next) { + mkdirp(path.dirname(options.output), function(){ next(); }); + }, + + /** + */ + + function copyBinaryToOutput (next) { + _log("cp %s %s", nodeCompiler.releasePath, options.output); + ncp(nodeCompiler.releasePath, options.output, next); + } + ], complete); +} + +/** + */ + +function _downloadNode (version, directory, complete) { + + var nodeFileDir = path.join(directory, version), + nodeFilePath = path.join(nodeFileDir, "node-" + version + ".tar.gz"); + + + // might already be downloaded, and unzipped + if (_getNodeCompiler(nodeFileDir, complete)) { + return; + } + + + async.waterfall([ + + /** + * first make the directory where the zip file will live + */ + + function makeDirectory (next) { + mkdirp(path.dirname(nodeFilePath), function () { next(); }) + }, + + /** + * download node into the target directory + */ + + function downloadNode (next) { + + if (fs.existsSync(nodeFilePath)) return next(); + + var url, prefix = "http://nodejs.org/dist"; + + // pick which url depending on the version + if (version === "latest") { + url = prefix + "/node-" + version + ".tar.gz"; + } else { + url = prefix + "/v" + version + "/node-v" + version + ".tar.gz"; + } + + _log("downloading %s", url); + + var output = fs.createWriteStream(nodeFilePath, { "flags": "w+" }); + + _logProgress(request(url)).pipe(output); + + output.on("close", function () { next(); }); + }, + + /** + * unzip in the same directory + */ + + function unzipNodeTarball (next) { + + var cmd = ["tar", "-xf", nodeFilePath, "-C", nodeFileDir]; + _log(cmd.join(" ")); + + var tar = spawn(cmd.shift(), cmd); + tar.stdout.pipe(process.stdout); + tar.stderr.pipe(process.stderr); + + tar.on("close", function () { next(); }) + }, + + /** + * return the compiler object for the node version + */ + + function (next) { + _getNodeCompiler(nodeFileDir, next) + }, + + ], complete); +} + +/** + */ + +function _getNodeCompiler (nodeFileDir, complete) { + var dir = _getFirstDirectory(nodeFileDir); + + if (dir) { + complete(null, { + dir: dir, + version: path.basename(nodeFileDir), + releasePath: path.join(dir, "out", "Release", "node"), + make: function (next) { + var configure = spawn("./configure", [], { cwd: dir }); + configure.stdout.pipe(process.stdout); + configure.stderr.pipe(process.stderr); + configure.on("close", function () { + var make = spawn("make", [], { cwd: dir }); + make.stdout.pipe(process.stdout); + make.stderr.pipe(process.stderr); + make.on("close", function () { + next(); + }); + }) + } + }); + return true; + } + + return false; +} + +/** + */ + +function _monkeyPatchNodeConfig (compiler, complete) { + async.waterfall([ + + /** + * monkeypatch the gyp file to include the nexe.js file + */ + + function (next) { + _monkeyPatchGyp(compiler, next) + }, + + /** + * monkeypatch main entry point + */ + + function (next) { + _monkeyPatchMainJs(compiler, next) + } + ], complete); +} + +/** + */ + +function _monkeyPatchGyp (compiler, complete) { + + var gypPath = path.join(compiler.dir, "node.gyp"); + + _monkeypatch( + gypPath, + function (content) { + return ~content.indexOf("nexe.js"); + }, + function (content, next) { + next(null, content.replace("'lib/fs.js',", "'lib/fs.js', 'lib/nexe.js', ")) + }, + complete + ) +} + +/** + */ + +function _monkeyPatchMainJs (compiler, complete) { + var mainPath = path.join(compiler.dir, "src", "node.js"); + + _monkeypatch( + mainPath, + function (content) { + return ~content.indexOf("nexe"); + }, + function (content, next) { + next(null, content.replace(/\(function\(process\) \{/,'(function(process) {\n process._eval = \'require("nexe");\';\n process.argv.unshift("node");\n')) + }, + complete + ); +} + +/** + */ + +function _monkeypatch (filePath, monkeyPatched, processor, complete) { + + async.waterfall([ + + function read (next) { + fs.readFile(filePath, "utf8", next); + }, + + // TODO - need to parse gyp file - this is a bit hacker + function monkeypatch (content, next) { + + if (monkeyPatched(content)) return complete(); + + _log("monkey patch %s", filePath); + processor(content, next); + }, + + function write (content, next) { + fs.writeFile(filePath, content, "utf8", next); + } + ], complete); +} + + +/** + */ + +function _getFirstDirectory (dir) { + var files = glob.sync(dir + "/*"); + + for (var i = files.length; i--;) { + var file = files[i]; + if (fs.statSync(file).isDirectory()) return file; + } + + return false; +} + +/** + */ + +function _logProgress (req) { + + req.on("response", function (resp) { + + var len = parseInt(resp.headers["content-length"], 10), + bar = new ProgressBar("[:bar]", { + complete: "=", + incomplete: " ", + total: len, + width: process.stdout.columns - 2 + }); + + req.on("data", function (chunk) { + bar.tick(chunk.length); + }); + }); + + return req; +} + +/** + */ + +function _log () { + + var args = Array.prototype.slice.call(arguments, 0), + level = args.shift(); + + if (!~["log", "error", "warn"].indexOf(level)) { + args.unshift(level); + level = "log"; + } + + args[0] = "----> " + args[0]; + + console[level].apply(console, args); +} \ No newline at end of file diff --git a/lib/index-old.js b/lib/index-old.js new file mode 100644 index 0000000..5df53b6 --- /dev/null +++ b/lib/index-old.js @@ -0,0 +1,254 @@ +var step = require("step"), +outcome = require("outcome"), +request = require("request"), +fs = require("fs"), +mkdirp = require("mkdirp"), +path = require("path"), +child_process = require("child_process"), +exec = child_process.exec, +spawn = child_process.spawn, +sprintf = require("sprintf").sprintf, +ncp = require("ncp").ncp, +sardines = require("sardines"); + +/** + */ + +exports.compile = function(args, next) { + + var on = outcome.error(next); + + args.input = require.resolve(args.input); + + step( + + /** + * 1. download node + */ + + function() { + downloadNode(args.runtime, args.temp, this); + }, + + /** + * 2. build the script into ONE file + */ + + on.success(function(node) { + console.log("making application bundle with %s", args.input); + this.node = node; + + sardines({ entries: [args.input], platform: "node" }, this); + }), + + + on.success(function(content) { + console.log("writing application bundle: \n\n%s\n\n", content); + fs.writeFile(this.nexePath = path.join(this.node.dir, "lib", "nexe.js"), content, "utf8", this); + }), + + /** + * 4. monkeypatch the node entry + */ + + on.success(function() { + console.log("monkey patching node.js file"); + monkeyPatchNodejs(this.node, this); + }), + + /** + * 5. bundle it all together + */ + + on.success(function() { + console.log("building node.js"); + this.node.make(this); + }), + + /** + * 6. copy the executable + */ + + on.success(function() { + console.log("copying binary to output directory to %s", args.output); + + mkdirp(path.dirname(args.output), this); + }), + + /** + */ + + function() { + ncp(this.node.releasePath, args.output, this); + }, + + /** + */ + + next + + ); +}; + +/** + */ + +function downloadNode(version, temp, next) { + + var tmpPath = path.join(temp, "node-" + version + ".tar.gz"), + srcDr = tmpPath.replace(/\.tar.gz$/, ""); + + + step( + + /** + * download + */ + + function() { + + //zip exists? + if(fs.existsSync(tmpPath) || fs.existsSync(srcDr)) return this(); + + console.log("downloading node %s", version); + + var output = fs.createWriteStream(tmpPath, { "flags": "w+" }), + req = request("http://nodejs.org/dist/node-" + version + ".tar.gz").pipe(output); + + output.on("close", this); + }, + + /** + * untar + */ + + function() { + + //source dir exists? + if(fs.existsSync(srcDr)) return this(); + + try { + mkdirp.sync(srcDr); + } catch(e) { + + } + + //TODO - use native zlib library for this + exec(sprintf("tar -xf '%s' -C '%s'", tmpPath, srcDr), { cwd: path.dirname(tmpPath) }, this); + }, + + /** + * cleanup the node.js file + */ + + function() { + fs.unlink(tmpfile, this); + }, + + /** + * return make script + */ + + function() { + + console.log(srcDr) + + vDir = path.join(srcDr, fs.readdirSync(srcDr).filter(function(dir) { + return !/^\./.test(dir); + }).pop()); + + next(null, { + + //the directory to the node.js file + dir: vDir, + + releasePath: path.join(vDir, "out/Release/node"), + + //makes node.js + make: function(next) { + var configure = spawn('./configure', [], { cwd: vDir }); + configure.stdout.on('data', function(data) { + process.stdout.write(data.toString()); + }); + configure.stderr.on('data', function(data) { + process.stderr.write(data.toString()); + }); + configure.on('close', function() { + var make = spawn('make', [], { cwd: vDir }); + make.stdout.on('data', function(data) { + process.stdout.write(data.toString()); + }); + make.stderr.on('data', function(data) { + process.stderr.write(data.toString()); + }); + make.on('close', next); + }); + } + }); + } + ); +} + +/** + */ + +function monkeyPatchNodejs(node, next) { + + var nodejsPath = path.join(node.dir, "src", "node.js"), + nodegypPath = path.join(node.dir, "node.gyp"), + on = outcome.error(next); + + step( + + /** + * read the node.js file + */ + + function() { + fs.readFile(nodejsPath, "utf8", this); + }, + + /** + * inject nexe as _eval IF it hasn't already been injected + */ + + on.success(function(content) { + + //nexe already injected? + if(~content.indexOf('nexe')) return this(); + + fs.writeFile(nodejsPath, + content.replace(/\(function\(process\) \{/,'(function(process) {\n process._eval = \'require("nexe");\';\n process.argv.unshift("node");\n'), + "utf8", + this); + }), + + /** + * next, find node.gyp and add nexe as a native lib file + */ + + on.success(function() { + fs.readFile(nodegypPath, "utf8", this); + }), + + /** + * finally, monkeypatch node.gyp + */ + + on.success(function(content) { + + //nexe already included as dep? + if(~content.indexOf('lib/nexe.js')) return this(); + + fs.writeFile(nodegypPath, + content.replace("'lib/fs.js',", "'lib/fs.js', 'lib/nexe.js', "), + "utf8", + this); + }), + + /** + */ + + next + ); +} diff --git a/lib/index.js b/lib/index.js index 5df53b6..ba6760c 100644 --- a/lib/index.js +++ b/lib/index.js @@ -1,254 +1 @@ -var step = require("step"), -outcome = require("outcome"), -request = require("request"), -fs = require("fs"), -mkdirp = require("mkdirp"), -path = require("path"), -child_process = require("child_process"), -exec = child_process.exec, -spawn = child_process.spawn, -sprintf = require("sprintf").sprintf, -ncp = require("ncp").ncp, -sardines = require("sardines"); - -/** - */ - -exports.compile = function(args, next) { - - var on = outcome.error(next); - - args.input = require.resolve(args.input); - - step( - - /** - * 1. download node - */ - - function() { - downloadNode(args.runtime, args.temp, this); - }, - - /** - * 2. build the script into ONE file - */ - - on.success(function(node) { - console.log("making application bundle with %s", args.input); - this.node = node; - - sardines({ entries: [args.input], platform: "node" }, this); - }), - - - on.success(function(content) { - console.log("writing application bundle: \n\n%s\n\n", content); - fs.writeFile(this.nexePath = path.join(this.node.dir, "lib", "nexe.js"), content, "utf8", this); - }), - - /** - * 4. monkeypatch the node entry - */ - - on.success(function() { - console.log("monkey patching node.js file"); - monkeyPatchNodejs(this.node, this); - }), - - /** - * 5. bundle it all together - */ - - on.success(function() { - console.log("building node.js"); - this.node.make(this); - }), - - /** - * 6. copy the executable - */ - - on.success(function() { - console.log("copying binary to output directory to %s", args.output); - - mkdirp(path.dirname(args.output), this); - }), - - /** - */ - - function() { - ncp(this.node.releasePath, args.output, this); - }, - - /** - */ - - next - - ); -}; - -/** - */ - -function downloadNode(version, temp, next) { - - var tmpPath = path.join(temp, "node-" + version + ".tar.gz"), - srcDr = tmpPath.replace(/\.tar.gz$/, ""); - - - step( - - /** - * download - */ - - function() { - - //zip exists? - if(fs.existsSync(tmpPath) || fs.existsSync(srcDr)) return this(); - - console.log("downloading node %s", version); - - var output = fs.createWriteStream(tmpPath, { "flags": "w+" }), - req = request("http://nodejs.org/dist/node-" + version + ".tar.gz").pipe(output); - - output.on("close", this); - }, - - /** - * untar - */ - - function() { - - //source dir exists? - if(fs.existsSync(srcDr)) return this(); - - try { - mkdirp.sync(srcDr); - } catch(e) { - - } - - //TODO - use native zlib library for this - exec(sprintf("tar -xf '%s' -C '%s'", tmpPath, srcDr), { cwd: path.dirname(tmpPath) }, this); - }, - - /** - * cleanup the node.js file - */ - - function() { - fs.unlink(tmpfile, this); - }, - - /** - * return make script - */ - - function() { - - console.log(srcDr) - - vDir = path.join(srcDr, fs.readdirSync(srcDr).filter(function(dir) { - return !/^\./.test(dir); - }).pop()); - - next(null, { - - //the directory to the node.js file - dir: vDir, - - releasePath: path.join(vDir, "out/Release/node"), - - //makes node.js - make: function(next) { - var configure = spawn('./configure', [], { cwd: vDir }); - configure.stdout.on('data', function(data) { - process.stdout.write(data.toString()); - }); - configure.stderr.on('data', function(data) { - process.stderr.write(data.toString()); - }); - configure.on('close', function() { - var make = spawn('make', [], { cwd: vDir }); - make.stdout.on('data', function(data) { - process.stdout.write(data.toString()); - }); - make.stderr.on('data', function(data) { - process.stderr.write(data.toString()); - }); - make.on('close', next); - }); - } - }); - } - ); -} - -/** - */ - -function monkeyPatchNodejs(node, next) { - - var nodejsPath = path.join(node.dir, "src", "node.js"), - nodegypPath = path.join(node.dir, "node.gyp"), - on = outcome.error(next); - - step( - - /** - * read the node.js file - */ - - function() { - fs.readFile(nodejsPath, "utf8", this); - }, - - /** - * inject nexe as _eval IF it hasn't already been injected - */ - - on.success(function(content) { - - //nexe already injected? - if(~content.indexOf('nexe')) return this(); - - fs.writeFile(nodejsPath, - content.replace(/\(function\(process\) \{/,'(function(process) {\n process._eval = \'require("nexe");\';\n process.argv.unshift("node");\n'), - "utf8", - this); - }), - - /** - * next, find node.gyp and add nexe as a native lib file - */ - - on.success(function() { - fs.readFile(nodegypPath, "utf8", this); - }), - - /** - * finally, monkeypatch node.gyp - */ - - on.success(function(content) { - - //nexe already included as dep? - if(~content.indexOf('lib/nexe.js')) return this(); - - fs.writeFile(nodegypPath, - content.replace("'lib/fs.js',", "'lib/fs.js', 'lib/nexe.js', "), - "utf8", - this); - }), - - /** - */ - - next - ); -} +module.exports = require("./exe"); \ No newline at end of file diff --git a/package.json b/package.json index 22bcb03..3f6c268 100644 --- a/package.json +++ b/package.json @@ -2,21 +2,24 @@ "author": "Craig Condon ", "name": "nexe", "description": "Roll node.s applications into a single executable", - "version": "0.2.3", + "version": "0.2.4", "repository": { "type": "git", "url": "git://github.com/crcn/nexe.git" }, "main": "./lib/index.js", "dependencies": { - "outcome": "0.0.x", - "sprintf": "0.1.x", + "outcome": "0.0.18", + "sprintf": "~0.1.3", "step": "0.0.x", - "optimist": "0.3.x", - "request": "2.12.x", - "ncp": "0.2.x", - "mkdirp": "0.3.x", - "sardines": "0.4.x" + "optimist": "~0.3.7", + "request": "~2.12.0", + "ncp": "~0.2.7", + "mkdirp": "~0.3.5", + "sardines": "~0.4.5", + "async": "~0.2.10", + "colors": "~0.6.2", + "glob": "~3.2.9" }, "preferGlobal": "true", "bin": {