2014-02-15 20:43:38 +00:00
|
|
|
/* jshint evil:true */
|
2014-02-15 15:05:04 +00:00
|
|
|
define(function(require) {
|
|
|
|
|
2014-02-15 15:54:54 +00:00
|
|
|
var Path = require('src/path');
|
2014-03-10 18:28:22 +00:00
|
|
|
var Errors = require('src/errors');
|
2014-03-18 20:41:31 +00:00
|
|
|
var Environment = require('src/shell/environment');
|
2014-02-18 19:51:06 +00:00
|
|
|
var async = require('async');
|
2014-02-15 15:54:54 +00:00
|
|
|
|
2014-03-26 17:57:16 +00:00
|
|
|
require('zip');
|
|
|
|
require('unzip');
|
|
|
|
|
2014-02-15 15:05:04 +00:00
|
|
|
function Shell(fs, options) {
|
|
|
|
options = options || {};
|
|
|
|
|
2014-02-19 22:29:11 +00:00
|
|
|
var env = new Environment(options.env);
|
2014-02-17 15:47:47 +00:00
|
|
|
var cwd = '/';
|
2014-02-15 15:05:04 +00:00
|
|
|
|
2014-02-19 17:15:02 +00:00
|
|
|
/**
|
|
|
|
* The bound FileSystem (cannot be changed)
|
|
|
|
*/
|
2014-02-15 15:05:04 +00:00
|
|
|
Object.defineProperty(this, 'fs', {
|
|
|
|
get: function() { return fs; },
|
|
|
|
enumerable: true
|
|
|
|
});
|
|
|
|
|
2014-02-19 17:15:02 +00:00
|
|
|
/**
|
|
|
|
* The shell's environment (e.g., for things like
|
2014-02-19 22:29:11 +00:00
|
|
|
* path, tmp, and other env vars). Use env.get()
|
|
|
|
* and env.set() to work with variables.
|
2014-02-19 17:15:02 +00:00
|
|
|
*/
|
|
|
|
Object.defineProperty(this, 'env', {
|
|
|
|
get: function() { return env; },
|
|
|
|
enumerable: true
|
|
|
|
});
|
|
|
|
|
2014-02-19 22:29:11 +00:00
|
|
|
/**
|
|
|
|
* Change the current working directory. We
|
|
|
|
* include `cd` on the `this` vs. proto so that
|
|
|
|
* we can access cwd without exposing it externally.
|
|
|
|
*/
|
2014-02-17 15:47:47 +00:00
|
|
|
this.cd = function(path, callback) {
|
|
|
|
path = Path.resolve(this.cwd, path);
|
|
|
|
// Make sure the path actually exists, and is a dir
|
|
|
|
fs.stat(path, function(err, stats) {
|
|
|
|
if(err) {
|
2014-03-12 20:23:03 +00:00
|
|
|
callback(new Errors.ENOTDIR());
|
2014-02-17 15:47:47 +00:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
if(stats.type === 'DIRECTORY') {
|
|
|
|
cwd = path;
|
|
|
|
callback();
|
|
|
|
} else {
|
2014-03-12 20:23:03 +00:00
|
|
|
callback(new Errors.ENOTDIR());
|
2014-02-17 15:47:47 +00:00
|
|
|
}
|
|
|
|
});
|
2014-02-15 15:05:04 +00:00
|
|
|
};
|
2014-02-19 22:29:11 +00:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Get the current working directory (changed with `cd()`)
|
|
|
|
*/
|
|
|
|
this.pwd = function() {
|
|
|
|
return cwd;
|
|
|
|
};
|
2014-02-15 15:05:04 +00:00
|
|
|
}
|
|
|
|
|
2014-02-15 20:28:00 +00:00
|
|
|
/**
|
|
|
|
* Execute the .js command located at `path`. Such commands
|
|
|
|
* should assume the existence of 3 arguments, which will be
|
|
|
|
* defined at runtime:
|
|
|
|
*
|
2014-02-19 20:22:12 +00:00
|
|
|
* * fs - the current shell's bound filesystem object
|
2014-02-19 22:36:26 +00:00
|
|
|
* * args - a list of arguments for the command, or an empty list if none
|
2014-02-15 20:28:00 +00:00
|
|
|
* * callback - a callback function(error, result) to call when done.
|
|
|
|
*
|
|
|
|
* The .js command's contents should be the body of a function
|
|
|
|
* that looks like this:
|
|
|
|
*
|
2014-02-19 22:36:26 +00:00
|
|
|
* function(fs, args, callback) {
|
2014-02-15 20:28:00 +00:00
|
|
|
* // .js code here
|
|
|
|
* }
|
|
|
|
*/
|
2014-02-19 22:36:26 +00:00
|
|
|
Shell.prototype.exec = function(path, args, callback) {
|
2014-02-15 20:28:00 +00:00
|
|
|
var fs = this.fs;
|
2014-02-19 22:36:26 +00:00
|
|
|
if(typeof args === 'function') {
|
|
|
|
callback = args;
|
|
|
|
args = [];
|
2014-02-15 20:28:00 +00:00
|
|
|
}
|
2014-02-19 22:36:26 +00:00
|
|
|
args = args || [];
|
2014-02-15 20:28:00 +00:00
|
|
|
callback = callback || function(){};
|
|
|
|
path = Path.resolve(this.cwd, path);
|
2014-02-15 15:05:04 +00:00
|
|
|
|
2014-02-15 20:28:00 +00:00
|
|
|
fs.readFile(path, "utf8", function(error, data) {
|
|
|
|
if(error) {
|
|
|
|
callback(error);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
try {
|
2014-02-19 22:36:26 +00:00
|
|
|
var cmd = new Function('fs', 'args', 'callback', data);
|
|
|
|
cmd(fs, args, callback);
|
2014-02-15 20:28:00 +00:00
|
|
|
} catch(e) {
|
|
|
|
callback(e);
|
|
|
|
}
|
|
|
|
});
|
2014-02-15 15:05:04 +00:00
|
|
|
};
|
|
|
|
|
2014-02-15 15:54:54 +00:00
|
|
|
/**
|
|
|
|
* Create a file if it does not exist, or update access and
|
|
|
|
* modified times if it does. Valid options include:
|
|
|
|
*
|
2014-02-15 20:28:00 +00:00
|
|
|
* * updateOnly - whether to create the file if missing (defaults to false)
|
2014-02-15 15:54:54 +00:00
|
|
|
* * date - use the provided Date value instead of current date/time
|
|
|
|
*/
|
2014-02-15 15:05:04 +00:00
|
|
|
Shell.prototype.touch = function(path, options, callback) {
|
|
|
|
var fs = this.fs;
|
2014-02-15 15:54:54 +00:00
|
|
|
if(typeof options === 'function') {
|
|
|
|
callback = options;
|
|
|
|
options = {};
|
|
|
|
}
|
2014-02-15 20:28:00 +00:00
|
|
|
options = options || {};
|
|
|
|
callback = callback || function(){};
|
2014-02-15 15:05:04 +00:00
|
|
|
path = Path.resolve(this.cwd, path);
|
|
|
|
|
|
|
|
function createFile(path) {
|
2014-02-17 15:47:47 +00:00
|
|
|
fs.writeFile(path, '', callback);
|
2014-02-15 15:05:04 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
function updateTimes(path) {
|
|
|
|
var now = Date.now();
|
2014-02-15 15:54:54 +00:00
|
|
|
var atime = options.date || now;
|
|
|
|
var mtime = options.date || now;
|
|
|
|
|
2014-02-17 15:47:47 +00:00
|
|
|
fs.utimes(path, atime, mtime, callback);
|
2014-02-15 15:05:04 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
fs.stat(path, function(error, stats) {
|
|
|
|
if(error) {
|
2014-02-15 20:28:00 +00:00
|
|
|
if(options.updateOnly === true) {
|
2014-02-15 15:54:54 +00:00
|
|
|
callback();
|
|
|
|
} else {
|
|
|
|
createFile(path);
|
|
|
|
}
|
2014-02-15 15:05:04 +00:00
|
|
|
} else {
|
|
|
|
updateTimes(path);
|
|
|
|
}
|
|
|
|
});
|
|
|
|
};
|
|
|
|
|
2014-02-18 19:51:06 +00:00
|
|
|
/**
|
|
|
|
* Concatenate multiple files into a single String, with each
|
|
|
|
* file separated by a newline. The `files` argument should
|
|
|
|
* be a String (path to single file) or an Array of Strings
|
|
|
|
* (multiple file paths).
|
|
|
|
*/
|
|
|
|
Shell.prototype.cat = function(files, callback) {
|
2014-02-19 16:25:30 +00:00
|
|
|
var fs = this.fs;
|
|
|
|
var all = '';
|
|
|
|
callback = callback || function(){};
|
|
|
|
|
2014-02-18 19:51:06 +00:00
|
|
|
if(!files) {
|
2014-04-17 15:53:55 +00:00
|
|
|
callback(new Errors.EINVAL("Missing files argument"));
|
2014-02-18 19:51:06 +00:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
files = typeof files === 'string' ? [ files ] : files;
|
|
|
|
|
|
|
|
function append(item, callback) {
|
|
|
|
var filename = Path.resolve(this.cwd, item);
|
|
|
|
fs.readFile(filename, 'utf8', function(error, data) {
|
|
|
|
if(error) {
|
|
|
|
callback(error);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
all += data + '\n';
|
|
|
|
callback();
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
async.eachSeries(files, append, function(error) {
|
|
|
|
if(error) {
|
|
|
|
callback(error);
|
|
|
|
} else {
|
|
|
|
callback(null, all.replace(/\n$/, ''));
|
|
|
|
}
|
|
|
|
});
|
|
|
|
};
|
|
|
|
|
2014-02-18 21:48:53 +00:00
|
|
|
/**
|
|
|
|
* Get the listing of a directory, returning an array of
|
|
|
|
* file entries in the following form:
|
|
|
|
*
|
|
|
|
* {
|
|
|
|
* path: <String> the basename of the directory entry
|
|
|
|
* links: <Number> the number of links to the entry
|
|
|
|
* size: <Number> the size in bytes of the entry
|
|
|
|
* modified: <Number> the last modified date/time
|
|
|
|
* type: <String> the type of the entry
|
|
|
|
* contents: <Array> an optional array of child entries
|
|
|
|
* }
|
|
|
|
*
|
|
|
|
* By default ls() gives a shallow listing. If you want
|
|
|
|
* to follow directories as they are encountered, use
|
|
|
|
* the `recursive=true` option.
|
|
|
|
*/
|
|
|
|
Shell.prototype.ls = function(dir, options, callback) {
|
|
|
|
var fs = this.fs;
|
|
|
|
if(typeof options === 'function') {
|
|
|
|
callback = options;
|
|
|
|
options = {};
|
|
|
|
}
|
|
|
|
options = options || {};
|
|
|
|
callback = callback || function(){};
|
|
|
|
|
2014-02-19 16:25:30 +00:00
|
|
|
if(!dir) {
|
2014-04-17 15:53:55 +00:00
|
|
|
callback(new Errors.EINVAL("Missing dir argument"));
|
2014-02-19 16:25:30 +00:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2014-02-18 21:48:53 +00:00
|
|
|
function list(path, callback) {
|
|
|
|
var pathname = Path.resolve(this.cwd, path);
|
|
|
|
var result = [];
|
|
|
|
|
|
|
|
fs.readdir(pathname, function(error, entries) {
|
|
|
|
if(error) {
|
|
|
|
callback(error);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
function getDirEntry(name, callback) {
|
|
|
|
name = Path.join(pathname, name);
|
|
|
|
fs.stat(name, function(error, stats) {
|
|
|
|
if(error) {
|
|
|
|
callback(error);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
var entry = {
|
|
|
|
path: Path.basename(name),
|
|
|
|
links: stats.nlinks,
|
|
|
|
size: stats.size,
|
|
|
|
modified: stats.mtime,
|
|
|
|
type: stats.type
|
|
|
|
};
|
|
|
|
|
|
|
|
if(options.recursive && stats.type === 'DIRECTORY') {
|
|
|
|
list(Path.join(pathname, entry.path), function(error, items) {
|
|
|
|
if(error) {
|
|
|
|
callback(error);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
entry.contents = items;
|
|
|
|
result.push(entry);
|
|
|
|
callback();
|
|
|
|
});
|
|
|
|
} else {
|
|
|
|
result.push(entry);
|
|
|
|
callback();
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
async.each(entries, getDirEntry, function(error) {
|
|
|
|
callback(error, result);
|
|
|
|
});
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
list(dir, callback);
|
|
|
|
};
|
|
|
|
|
2014-02-19 17:15:02 +00:00
|
|
|
/**
|
|
|
|
* Removes the file or directory at `path`. If `path` is a file
|
|
|
|
* it will be removed. If `path` is a directory, it will be
|
|
|
|
* removed if it is empty, otherwise the callback will receive
|
|
|
|
* an error. In order to remove non-empty directories, use the
|
|
|
|
* `recursive=true` option.
|
|
|
|
*/
|
2014-02-19 16:25:30 +00:00
|
|
|
Shell.prototype.rm = function(path, options, callback) {
|
|
|
|
var fs = this.fs;
|
|
|
|
if(typeof options === 'function') {
|
|
|
|
callback = options;
|
|
|
|
options = {};
|
|
|
|
}
|
|
|
|
options = options || {};
|
|
|
|
callback = callback || function(){};
|
|
|
|
|
|
|
|
if(!path) {
|
2014-04-17 15:53:55 +00:00
|
|
|
callback(new Errors.EINVAL("Missing path argument"));
|
2014-02-19 16:25:30 +00:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
function remove(pathname, callback) {
|
|
|
|
pathname = Path.resolve(this.cwd, pathname);
|
|
|
|
fs.stat(pathname, function(error, stats) {
|
|
|
|
if(error) {
|
|
|
|
callback(error);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// If this is a file, delete it and we're done
|
|
|
|
if(stats.type === 'FILE') {
|
|
|
|
fs.unlink(pathname, callback);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// If it's a dir, check if it's empty
|
|
|
|
fs.readdir(pathname, function(error, entries) {
|
|
|
|
if(error) {
|
|
|
|
callback(error);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// If dir is empty, delete it and we're done
|
|
|
|
if(entries.length === 0) {
|
|
|
|
fs.rmdir(pathname, callback);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// If not, see if we're allowed to delete recursively
|
|
|
|
if(!options.recursive) {
|
2014-03-12 20:23:03 +00:00
|
|
|
callback(new Errors.ENOTEMPTY());
|
2014-02-19 16:25:30 +00:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Remove each dir entry recursively, then delete the dir.
|
|
|
|
entries = entries.map(function(filename) {
|
|
|
|
// Root dir entries absolutely
|
|
|
|
return Path.join(pathname, filename);
|
|
|
|
});
|
|
|
|
async.each(entries, remove, function(error) {
|
|
|
|
if(error) {
|
|
|
|
callback(error);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
fs.rmdir(pathname, callback);
|
|
|
|
});
|
|
|
|
});
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
remove(path, callback);
|
|
|
|
};
|
|
|
|
|
2014-02-19 17:15:02 +00:00
|
|
|
/**
|
|
|
|
* Gets the path to the temporary directory, creating it if not
|
|
|
|
* present. The directory used is the one specified in
|
|
|
|
* env.TMP. The callback receives (error, tempDirName).
|
|
|
|
*/
|
|
|
|
Shell.prototype.tempDir = function(callback) {
|
|
|
|
var fs = this.fs;
|
2014-02-19 22:29:11 +00:00
|
|
|
var tmp = this.env.get('TMP');
|
2014-02-19 17:15:02 +00:00
|
|
|
callback = callback || function(){};
|
|
|
|
|
|
|
|
// Try and create it, and it will either work or fail
|
|
|
|
// but either way it's now there.
|
|
|
|
fs.mkdir(tmp, function(err) {
|
|
|
|
callback(null, tmp);
|
|
|
|
});
|
|
|
|
};
|
|
|
|
|
2014-03-14 04:03:29 +00:00
|
|
|
/**
|
2014-03-16 15:47:55 +00:00
|
|
|
* Recursively creates the directory at `path`. If the parent
|
|
|
|
* of `path` does not exist, it will be created.
|
2014-03-14 04:03:29 +00:00
|
|
|
* Based off EnsureDir by Sam X. Xu
|
|
|
|
* https://www.npmjs.org/package/ensureDir
|
|
|
|
* MIT License
|
|
|
|
*/
|
|
|
|
Shell.prototype.mkdirp = function(path, callback) {
|
|
|
|
var fs = this.fs;
|
|
|
|
callback = callback || function(){};
|
|
|
|
|
|
|
|
if(!path) {
|
2014-03-30 04:13:25 +00:00
|
|
|
callback(new Errors.EINVAL("Missing path argument"));
|
2014-03-14 04:03:29 +00:00
|
|
|
return;
|
|
|
|
}
|
2014-03-30 04:13:25 +00:00
|
|
|
else if (path === '/') {
|
2014-03-14 04:03:29 +00:00
|
|
|
callback();
|
|
|
|
return;
|
|
|
|
}
|
2014-03-30 04:13:25 +00:00
|
|
|
function _mkdirp(path, callback) {
|
2014-03-14 04:03:29 +00:00
|
|
|
fs.stat(path, function (err, stat) {
|
2014-03-30 04:13:25 +00:00
|
|
|
if(stat) {
|
|
|
|
if(stat.isDirectory()) {
|
|
|
|
callback();
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
else if (stat.isFile()) {
|
|
|
|
callback(new Errors.ENOTDIR());
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else if (err && err.code !== 'ENOENT') {
|
|
|
|
callback(err);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
else {
|
2014-03-14 04:03:29 +00:00
|
|
|
var parent = Path.dirname(path);
|
2014-03-30 04:13:25 +00:00
|
|
|
if(parent === '/') {
|
2014-03-14 04:03:29 +00:00
|
|
|
fs.mkdir(path, function (err) {
|
|
|
|
if (err && err.code != 'EEXIST') {
|
|
|
|
callback(err);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
callback();
|
|
|
|
return;
|
|
|
|
});
|
|
|
|
}
|
2014-03-30 04:13:25 +00:00
|
|
|
else {
|
2014-03-14 04:03:29 +00:00
|
|
|
_mkdirp(parent, function (err) {
|
|
|
|
if (err) return callback(err);
|
2014-03-30 04:13:25 +00:00
|
|
|
fs.mkdir(path, function (err) {
|
2014-03-14 04:03:29 +00:00
|
|
|
if (err && err.code != 'EEXIST') {
|
|
|
|
callback(err);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
callback();
|
|
|
|
return;
|
|
|
|
});
|
|
|
|
});
|
|
|
|
}
|
|
|
|
}
|
2014-03-30 04:13:25 +00:00
|
|
|
|
2014-03-14 04:03:29 +00:00
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
_mkdirp(path, callback);
|
|
|
|
};
|
|
|
|
|
2014-03-25 15:47:08 +00:00
|
|
|
/**
|
|
|
|
* Downloads the file at `url` and saves it to the filesystem.
|
|
|
|
* The file is saved to a file named with the current date/time
|
|
|
|
* unless the `options.filename` is present, in which case that
|
|
|
|
* filename is used instead. The callback receives (error, path).
|
|
|
|
*/
|
|
|
|
Shell.prototype.wget = function(url, options, callback) {
|
|
|
|
var fs = this.fs;
|
|
|
|
if(typeof options === 'function') {
|
|
|
|
callback = options;
|
|
|
|
options = {};
|
|
|
|
}
|
|
|
|
options = options || {};
|
|
|
|
callback = callback || function(){};
|
|
|
|
|
|
|
|
if(!url) {
|
|
|
|
callback(new Errors.EINVAL('missing url argument'));
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2014-05-14 17:24:24 +00:00
|
|
|
// Grab whatever is after the last / (assuming there is one) and
|
|
|
|
// remove any non-filename type chars(i.e., : and /). Like the real
|
|
|
|
// wget, we leave query string or hash portions in tact.
|
|
|
|
var path = options.filename || url.replace(/[:/]/g, '').split('/').pop();
|
2014-03-25 15:47:08 +00:00
|
|
|
path = Path.resolve(fs.cwd, path);
|
|
|
|
|
2014-03-26 23:34:32 +00:00
|
|
|
function onerror() {
|
|
|
|
callback(new Error('unable to get resource'));
|
|
|
|
}
|
|
|
|
|
2014-03-25 15:47:08 +00:00
|
|
|
var request = new XMLHttpRequest();
|
2014-03-26 23:34:32 +00:00
|
|
|
request.onload = function() {
|
|
|
|
if(request.status !== 200) {
|
|
|
|
return onerror();
|
|
|
|
}
|
|
|
|
|
2014-03-25 15:47:08 +00:00
|
|
|
var data = new Uint8Array(request.response);
|
|
|
|
fs.writeFile(path, data, function(err) {
|
|
|
|
if(err) {
|
|
|
|
callback(err);
|
|
|
|
} else {
|
|
|
|
callback(null, path);
|
|
|
|
}
|
|
|
|
});
|
2014-03-26 23:34:32 +00:00
|
|
|
};
|
|
|
|
request.onerror = onerror;
|
|
|
|
request.open("GET", url, true);
|
|
|
|
if("withCredentials" in request) {
|
|
|
|
request.withCredentials = true;
|
|
|
|
}
|
2014-03-25 15:47:08 +00:00
|
|
|
|
2014-03-25 15:47:08 +00:00
|
|
|
request.responseType = "arraybuffer";
|
|
|
|
request.send();
|
|
|
|
};
|
|
|
|
|
2014-03-26 20:42:17 +00:00
|
|
|
Shell.prototype.unzip = function(zipfile, options, callback) {
|
2014-03-25 20:05:30 +00:00
|
|
|
var fs = this.fs;
|
2014-03-26 17:57:16 +00:00
|
|
|
var sh = this;
|
2014-03-25 20:05:30 +00:00
|
|
|
if(typeof options === 'function') {
|
|
|
|
callback = options;
|
|
|
|
options = {};
|
|
|
|
}
|
|
|
|
options = options || {};
|
|
|
|
callback = callback || function(){};
|
|
|
|
|
2014-03-26 20:42:17 +00:00
|
|
|
if(!zipfile) {
|
|
|
|
callback(new Errors.EINVAL('missing zipfile argument'));
|
2014-03-25 20:05:30 +00:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2014-03-26 20:42:17 +00:00
|
|
|
var path = Path.resolve(this.cwd, zipfile);
|
2014-03-25 20:05:30 +00:00
|
|
|
var destination = Path.resolve(options.destination || this.cwd);
|
|
|
|
|
2014-03-26 17:57:16 +00:00
|
|
|
fs.readFile(path, function(err, data) {
|
|
|
|
if(err) return callback(err);
|
2014-03-25 20:05:30 +00:00
|
|
|
|
2014-03-26 17:57:16 +00:00
|
|
|
var unzip = new Zlib.Unzip(data);
|
2014-03-26 18:28:50 +00:00
|
|
|
|
|
|
|
// Separate filenames within the zip archive with what will go in fs.
|
|
|
|
// Also mark any directories (i.e., paths with a trailing '/')
|
2014-03-26 17:57:16 +00:00
|
|
|
var filenames = unzip.getFilenames().map(function(filename) {
|
|
|
|
return {
|
|
|
|
zipFilename: filename,
|
2014-03-26 18:28:50 +00:00
|
|
|
fsFilename: Path.join(destination, filename),
|
|
|
|
isDirectory: /\/$/.test(filename)
|
2014-03-26 17:57:16 +00:00
|
|
|
};
|
2014-03-25 20:05:30 +00:00
|
|
|
});
|
2014-03-26 17:57:16 +00:00
|
|
|
|
|
|
|
function decompress(path, callback) {
|
|
|
|
var data = unzip.decompress(path.zipFilename);
|
2014-03-26 18:28:50 +00:00
|
|
|
if(path.isDirectory) {
|
|
|
|
sh.mkdirp(path.fsFilename, callback);
|
|
|
|
} else {
|
2014-03-26 17:57:16 +00:00
|
|
|
fs.writeFile(path.fsFilename, data, callback);
|
2014-03-26 18:28:50 +00:00
|
|
|
}
|
2014-03-26 17:57:16 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
async.eachSeries(filenames, decompress, callback);
|
|
|
|
});
|
2014-03-25 20:05:30 +00:00
|
|
|
};
|
|
|
|
|
2014-03-25 21:11:31 +00:00
|
|
|
Shell.prototype.zip = function(zipfile, paths, options, callback) {
|
|
|
|
var fs = this.fs;
|
2014-03-26 20:05:28 +00:00
|
|
|
var sh = this;
|
2014-03-25 21:11:31 +00:00
|
|
|
if(typeof options === 'function') {
|
|
|
|
callback = options;
|
|
|
|
options = {};
|
|
|
|
}
|
|
|
|
options = options || {};
|
|
|
|
callback = callback || function(){};
|
|
|
|
|
|
|
|
if(!zipfile) {
|
|
|
|
callback(new Errors.EINVAL('missing zipfile argument'));
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
if(!paths) {
|
|
|
|
callback(new Errors.EINVAL('missing paths argument'));
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
if(typeof paths === 'string') {
|
|
|
|
paths = [ paths ];
|
|
|
|
}
|
|
|
|
zipfile = Path.resolve(this.cwd, zipfile);
|
|
|
|
|
2014-03-26 17:57:16 +00:00
|
|
|
function encode(s) {
|
|
|
|
return new TextEncoder('utf8').encode(s);
|
|
|
|
}
|
2014-03-25 21:11:31 +00:00
|
|
|
|
2014-03-26 20:05:28 +00:00
|
|
|
function addFile(path, callback) {
|
2014-03-26 17:57:16 +00:00
|
|
|
fs.readFile(path, function(err, data) {
|
|
|
|
if(err) return callback(err);
|
2014-03-25 21:11:31 +00:00
|
|
|
|
2014-03-26 17:57:16 +00:00
|
|
|
// Make path relative within the zip
|
|
|
|
var relpath = path.replace(/^\//, '');
|
|
|
|
zip.addFile(data, { filename: encode(relpath) });
|
|
|
|
callback();
|
|
|
|
});
|
2014-03-26 16:45:10 +00:00
|
|
|
}
|
2014-03-25 21:11:31 +00:00
|
|
|
|
2014-03-26 20:05:28 +00:00
|
|
|
function addDir(path, callback) {
|
|
|
|
fs.readdir(path, function(err, list) {
|
|
|
|
// Add the directory itself (with no data) and a trailing /
|
|
|
|
zip.addFile([], {
|
|
|
|
filename: encode(path + '/'),
|
|
|
|
compressionMethod: Zlib.Zip.CompressionMethod.STORE
|
|
|
|
});
|
|
|
|
|
|
|
|
if(!options.recursive) {
|
|
|
|
callback();
|
|
|
|
}
|
|
|
|
|
|
|
|
// Add all children of this dir, too
|
|
|
|
async.eachSeries(list, function(entry, callback) {
|
|
|
|
add(Path.join(path, entry), callback);
|
|
|
|
}, callback);
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
function add(path, callback) {
|
|
|
|
path = Path.resolve(sh.cwd, path);
|
|
|
|
fs.stat(path, function(err, stats) {
|
|
|
|
if(err) return callback(err);
|
|
|
|
|
|
|
|
if(stats.isDirectory()) {
|
|
|
|
addDir(path, callback);
|
|
|
|
} else {
|
|
|
|
addFile(path, callback);
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2014-03-26 17:57:16 +00:00
|
|
|
var zip = new Zlib.Zip();
|
|
|
|
|
2014-03-26 20:05:28 +00:00
|
|
|
// Make sure the zipfile doesn't already exist.
|
|
|
|
fs.stat(zipfile, function(err, stats) {
|
|
|
|
if(stats) {
|
|
|
|
return callback(new Errors.EEXIST('zipfile already exists'));
|
|
|
|
}
|
|
|
|
|
|
|
|
async.eachSeries(paths, add, function(err) {
|
|
|
|
if(err) return callback(err);
|
|
|
|
|
|
|
|
var compressed = zip.compress();
|
|
|
|
fs.writeFile(zipfile, compressed, callback);
|
|
|
|
});
|
2014-03-26 17:57:16 +00:00
|
|
|
});
|
2014-03-25 21:11:31 +00:00
|
|
|
};
|
|
|
|
|
2014-02-15 15:05:04 +00:00
|
|
|
return Shell;
|
|
|
|
|
|
|
|
});
|