Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

fs-transform

Package Overview
Dependencies
Maintainers
1
Versions
21
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

fs-transform - npm Package Compare versions

Comparing version 1.0.1 to 2.0.0

test/fixtures/diff

2

index.js
'use strict';
var Transformer = require('transformer.js');
var Transformer = require('./lib/transformer.js');

@@ -5,0 +5,0 @@ /**

@@ -6,2 +6,8 @@ 'use strict';

var fs = require('fs');
var path = require('path');
var isString = require('101/is-string');
var last = require('101/last');
var async = require('async');
var debug = require('debug');
var execLog = debug('fs-transform:exec');

@@ -32,10 +38,20 @@ /**

if (last(root) === '/') {
this.root = root.substr(0, root.length - 1);
this.root = path.resolve(root.substr(0, root.length - 1));
}
else {
this.root = root;
this.root = path.resolve(root);
}
this.working = null;
}
/**
* Commands required to use the `FsDriver` class.
* @type {array}
*/
Object.defineProperty(FsDriver, "commands", {
value: ['cp', 'mv', 'grep', 'sed', 'diff', 'rm'],
writable: false
});
/**
* Makes a relative path absolute to the root directory of the driver. Returns

@@ -57,9 +73,96 @@ * the original path if it is already absolute.

FsDriver.prototype.absolutePath = function (path) {
if (!isString(path)) {
return null;
}
if (path.charAt(0) === '/') {
return path;
}
return this.root + '/' + path;
}
return !this.working ?
this.root + '/' + path :
this.working + '/' + path;
};
/**
* Escapes a string for use in a command.
* @param {string} str String to escape.
* @return {string} The command line escaped string.
*/
FsDriver.prototype.escape = function (str) {
// Note: I tried alternatives to this, but none worked
// in both osx and linux :(
return str.replace(/(['/\\])/g, '\\$1');
};
/**
* Logs and executes a command.
* @param {string} command Command to execute.
* @param {fs-driver~ExecCallback} cb Callback to execute after command
* finishes.
*/
FsDriver.prototype.exec = function (command, cb) {
var self = this;
execLog(command);
childProcess.exec(command, function (err, output) {
var scriptCommand;
if (!self.working) {
scriptCommand = command;
}
else {
scriptCommand = command.replace(
new RegExp(self.working + '([^\\s]*)', 'g'),
self.root + '$1'
);
}
cb(err, output, scriptCommand);
});
};
/**
* Callback to invoke after the command run by FsDriver.exec finishes.
* @param {Error} [err] An error, if one occurred when executing the command.
* @param {string} [output] The text result of the command.
* @param {string} scriptCommand The command that was executed, with all
* filenames pathed relative to the root (used for saving script commands).
*/
/**
* Determines if a given command is installed on the system.
* @param {string} commandName Name of the command to check.
* @param {fs-driver~ExecCallback} cb Callback to execute after command
* finishes.
*/
FsDriver.prototype.hasCommand = function (commandName, cb) {
this.exec('command -v ' + commandName, cb);
};
/**
* Ensures that all of the commands needed to run the FsDriver are installed
* on the client system.
* @param {fs-driver~ExecCallback} cb Callback to execute after command
* finishes.
*/
FsDriver.prototype.hasAllCommands = function(cb) {
async.each(FsDriver.commands, this.hasCommand.bind(this), cb);
};
/**
* @returns a shell script that checks for all needed commands.
*/
FsDriver.prototype.getCommandCheckScript = function () {
var checks = FsDriver.commands.map(function (name) {
return 'command -v ' + name + ' >/dev/null 2>&1 ' +
'|| { echo "' +
'Required command: ' + name +
' -- Please install it before running this script.' +
'"; exit 1; }';
}).join('\n');
return [
'# Check to ensure required commands are available',
checks,
'# END Required command check'
].join('\n') + '\n';
};
/**
* Moves a file.

@@ -76,13 +179,11 @@ *

* @param {string} dest Destination file path.
* @param {function} cb Callback to execute with the results of the file move.
* @return {string} The move command to be executed.
* @param {fs-driver~ExecCallback} cb Callback to execute after the move
* completes.
*/
FsDriver.prototype.move = function (source, dest, cb) {
var command = [
this.exec([
'mv',
this.absolutePath(source),
this.absolutePath(dest)
].join(' ');
childProcess.exec(command, cb);
return command;
].join(' '), cb);
};

@@ -101,13 +202,12 @@

* @param {string} dest Destination file path.
* @param {function} cb Callback to execute with the results of the file copy.
* @return {string} The copy command to be executed.
* @param {fs-driver~ExecCallback} cb Callback to execute after the copy
* completes.
*/
FsDriver.prototype.copy = function (source, dest, cb) {
var command = [
this.exec([
'cp',
this.absolutePath(source),
this.absolutePath(dest)
].join(' ');
childProcess.exec(command, cb);
return command;
].join(' '), cb);
};

@@ -126,13 +226,11 @@

* @param {string} text Text to search for.
* @param {function} cb Callback to execute after the search has been performed.
* @return {string} The grep command that is to be executed.
* @param {fs-driver~ExecCallback} cb Callback to execute after the grep
* completes.
*/
FsDriver.prototype.grep = function (text, cb) {
var command = [
this.exec([
'grep -rn',
text.replace(/\\/, '\\\\'),
this.root
].join(' ');
childProcess.exec(command, cb);
return command;
'\'' + this.escape(text) + '\'',
this.working ? this.working : this.root
].join(' '), cb);
};

@@ -153,4 +251,4 @@

* @param {Number} line Line number on which to perform the replacement.
* @param {function} cb Callback to execute when the replacement has been made.
* @return {string} The sed command that is to be executed.
* @param {fs-driver~ExecCallback} cb Callback to execute after the sed
* completes.
*/

@@ -160,4 +258,4 @@ FsDriver.prototype.sed = function (search, replace, name, line, cb) {

line + 's',
search.replace(/\//, '\\/'),
replace.replace(/\//, '\\/'),
this.escape(search),
this.escape(replace),
'g'

@@ -167,9 +265,8 @@ ].join('/');

var command = [
'sed -i "" "', pattern, '" ',
'sed -i.last',
'\'' + pattern + '\'',
this.absolutePath(name)
].join('');
].join(' ');
childProcess.exec(command, cb);
return command;
this.exec(command, cb);
};

@@ -185,1 +282,102 @@

};
/**
* Performs a file diff.
* @param {string} a Name of the first file for the diff.
* @param {string} b Name of the second file.
* @param {fs-driver~ExecCallback} cb Callback to execute after the diff
* completes.
*/
FsDriver.prototype.diff = function (a, b, cb) {
var command = [
'diff -u -r',
this.absolutePath(a),
this.absolutePath(b)
].join(' ');
this.exec(command, function (err, diff, scriptCommand) {
// `diff` returns 1 when there are differences and > 1 when there is trouble
if (err && err.code > 1) { return cb(err); }
cb(null, diff, scriptCommand);
});
};
/**
* Removes a file.
* @param {string} filename Name of the file to remove.
* @param {fs-driver~ExecCallback} cb Callback to execute after the remove
* completes.
*/
FsDriver.prototype.remove = function (filename, cb) {
this.exec([
'rm',
this.absolutePath(filename)
].join(' '), cb);
};
/**
* Recursively removes a file or directory.
* @param {string} filename Name of the file to remove.
* @param {fs-driver~ExecCallback} cb Callback to execute after the remove
* completes.
*/
FsDriver.prototype.removeRecursive = function (filename, cb) {
this.exec([
'rm -rf',
this.absolutePath(filename)
].join(' '), cb);
};
/**
* Attempts to find an available working directory relative to /tmp.
*/
FsDriver.prototype.findWorkingDirectory = function() {
var rootName = last(this.root.split('/'));
var workingPath = '/tmp/.' + rootName + '.fs-work';
var n = Math.random();
while (this.exists(workingPath + '.' + n)) {
n = Math.random();
}
return workingPath += '.' + n;
};
/**
* Creates a temporary working copy of the root directory. This copy is used to
* set the absolute path for given relative file names in various commands (e.g.
* move, copy, grep, sed, rm, etc.)
* @param {fs-driver~ExecCallback} cb Callback to execute after the working
* directory has been created.
*/
FsDriver.prototype.createWorkingDirectory = function (cb) {
var self = this;
var workingPath = this.findWorkingDirectory();
var copyCommand = ['cp -r', this.root, workingPath].join(' ');
this.exec(copyCommand, function (err) {
if (err) { return cb(err); }
self.working = workingPath;
cb();
});
};
/**
* Removes the temporary working directory.
* @param {fs-driver~ExecCallback} cb Callback to execute after removing the
* temporary working directory.
*/
FsDriver.prototype.removeWorkingDirectory = function (cb) {
var self = this;
if (!this.working) { return cb(); }
this.removeRecursive(this.working, function (err) {
if (err) { return cb(err); }
self.working = null;
cb();
});
};
/**
* Returns a full diff between the working directory and the root directory.
* @param {fs-driver~ExecCallback} cb Callback to execute after performing the
* diff.
*/
FsDriver.prototype.workingDiff = function (cb) {
this.diff(this.root, this.working, cb);
};

@@ -8,2 +8,4 @@ 'use strict';

var exists = require('101/exists');
var debug = require('debug');
var fullDiffDebug = debug('fs-transform:full-diff');

@@ -39,6 +41,7 @@ /**

this._ruleActions = {};
this.currentResult = null;
this.warnings = [];
// Stores a list of the transformative commands that were executed
this.commands = [];
this.results = [];
this.nameChanges = [];

@@ -51,42 +54,26 @@ this.setAction('copy', this.copy);

/**
* Callback executed when transformations have been completed, or if an error
* has occurred.
* @callback fs-transform~Callback
* @param {Error} err An error if something went wrong when processing the
* transformation rules.
* Default postfix to append to backup files after performing find and replace
* transformation operations.
* @type {string}
*/
Transformer.ORIGINAL_POSTFIX = '.fs-transform.original';
/**
* Performs a series of filesystem transformations.
* @param {String} root Root directory to run the transformations.
* @param {String|Array} rules An array of transformations, or a JSON string
* that parses into a stream of transformations.
* @param {fs-transform~Callback} cb Callback to execute once the
* transformations have been completed or if an error has occurred.
* Pushes a new result to the result list.
* @param {object} rule Rule that generates the result.
* @return {object} The new result object.
*/
Transformer.transform = function(root, rules, cb) {
try {
new Transformer(root, rules).execute(cb);
}
catch (err) {
cb(err);
}
Transformer.prototype.pushResult = function (rule) {
this.currentResult = {
rule: rule,
commands: [],
warnings: [],
nameChanges: [],
diffs: {}
};
this.results.push(this.currentResult);
return this.currentResult;
};
/**
* Executes the transformations.
* @param {fs-transform~Callback} cb Callback to execute once the
* transformations have been completed or if an error has occurred.
*/
Transformer.prototype.execute = function (cb) {
var self = this;
this.commands = [];
async.mapSeries(this.rules, function (rule, ruleCallback) {
self.applyRule(rule, ruleCallback);
}, function (err) {
cb(err, self);
});
};
/**
* Adds a rule generated warning to the transformer.

@@ -97,30 +84,54 @@ * @param object Object that generated the warning.

Transformer.prototype.addWarning = function (object, msg) {
this.warnings.push(new Warning(object, msg));
var warning = new Warning(object, msg);
this.warnings.push(warning);
if (exists(this.currentResult)) {
this.currentResult.warnings.push(warning);
}
};
/**
* Allows the user to override existing rule actions and define new ones.
* Note the action function will be applied within the context of this
* Transformation.
* @param {string} name Name of the action to define or override.
* @param {fs-transform~Rule} fn Function to apply when encountering the rule
* with the given action name.
* Adds a name change result.
* @param {object} nameChange Name change to add.
*/
Transformer.prototype.setAction = function(name, fn) {
var self = this;
this._ruleActions[name] = function() {
fn.apply(self, arguments);
Transformer.prototype.addNameChange = function (from, to) {
var nameChange = {
from: from,
to: to
};
this.nameChanges.push(nameChange);
if (exists(this.currentResult)) {
this.currentResult.nameChanges.push(nameChange);
}
};
/**
* Determine the rule action handler for the action name.
* @param {string} name Name of the action.
* @return {function} The rule handler for the action.
* Adds a diff to the current result.
* @param {string} filename Name of the file for the diff.
* @param {string} Contents of the diff (via diff -u, similar to git diff).
*/
Transformer.prototype.getAction = function(name) {
return this._ruleActions[name];
Transformer.prototype.addDiff = function (filename, diff) {
if (!this.currentResult) { return; }
if (!this.currentResult.diffs[filename]) {
this.currentResult.diffs[filename] = [];
}
this.currentResult.diffs[filename].push(diff);
};
/**
* Saves a command that was executed by the transformer. We do this so we can
* reconstruct all of the transformations in the form of a script.
* @param {string} command Command to save.
* @param {object} rule Rule that generated the command.
*/
Transformer.prototype.saveCommand = function (command, rule) {
this.commands.push({
rule: rule,
command: command
});
if (exists(this.currentResult)) {
this.currentResult.commands.push(command);
}
};
/**
* @return the commands executed by this transformer in the form of a command

@@ -137,2 +148,4 @@ * line script.

var commandCheck = this.driver.getCommandCheckScript();
var commands = this.commands.map(function (cmd) {

@@ -143,19 +156,37 @@ return ('# from rule: ' + JSON.stringify(cmd.rule)) + '\n' +

return [preamble, commands, ''].join('\n');
return [preamble, commandCheck, commands, ''].join('\n');
};
/**
* Saves a command that was executed by the transformer. We do this so we can
* reconstruct all of the transformations in the form of a script.
* @param {string} command Command to save.
* @param {object} rule Rule that generated the command.
* @return The full diff after performing all filesystem transforms.
*/
Transformer.prototype.saveCommand = function (command, rule) {
this.commands.push({
command: command,
rule: rule
});
Transformer.prototype.getDiff = function() {
return this._fullDiff;
};
/**
* Allows the user to override existing rule actions and define new ones.
* Note the action function will be applied within the context of this
* Transformation.
* @param {string} name Name of the action to define or override.
* @param {fs-transform~Rule} fn Function to apply when encountering the rule
* with the given action name.
*/
Transformer.prototype.setAction = function(name, fn) {
var self = this;
this._ruleActions[name] = function() {
fn.apply(self, arguments);
};
};
/**
* Determine the rule action handler for the action name.
* @param {string} name Name of the action.
* @return {fs-transform~Rule} The rule handler for the action.
*/
Transformer.prototype.getAction = function(name) {
return this._ruleActions[name];
};
/**
* Callback for the application of rule actions.

@@ -168,2 +199,133 @@ * @callback fs-transform~Rule

/**
* Performs a series of filesystem transformations.
* @param {String} root Root directory to run the transformations.
* @param {String|Array} rules An array of transformations, or a JSON string
* that parses into a stream of transformations.
* @param {fs-transform~Callback} cb Callback to execute once the
* transformations have been completed or if an error has occurred.
*/
Transformer.transform = function(root, rules, cb) {
try {
new Transformer(root, rules).transform(cb);
}
catch (err) {
cb(err);
}
};
/**
* Performs a dry run of the transformation.
* @param {String} root Root directory to run the transformations.
* @param {String|Array} rules An array of transformations, or a JSON string
* that parses into a stream of transformations.
* @param {fs-transform~Callback} cb Callback to execute once the
* transformations have been completed or if an error has occurred.
*/
Transformer.dry = function(root, rules, cb) {
try {
new Transformer(root, rules).dry(cb);
}
catch (err) {
cb(err);
}
};
/**
* Callback executed when transformations have been completed, or if an error
* has occurred.
* @callback fs-transform~Callback
* @param {Error} err An error if something went wrong when processing the
* transformation rules.
*/
/**
* Perform and commit the transformations.
* @param {fs-transform~Callback} cb Callback to execute once the
* transformations have been completed or if an error has occurred.
*/
Transformer.prototype.transform = function (cb) {
this._execute(true, cb);
};
/**
* Perform a dry run of of the tranformations.
* @param {fs-transform~Callback} cb Callback to execute once the
* transformations have been completed or if an error has occurred.
*/
Transformer.prototype.dry = function (cb) {
this._execute(false, cb);
};
/**
* Executes each of the the tranformation rules and collects their results.
* @param {boolean} commit Whether or not to commit the transformations to the
* original root directory. If true then the changes are applied to the root
* directory, if false then the root directory will remain unchanged.
* @param {fs-transform~Callback} executeCallback Callback to execute once the
* transformations have been completed or if an error has occurred.
*/
Transformer.prototype._execute = function (commit, executeCallback) {
var self = this;
this.commands = [];
this._fullDiff = "";
async.series([
// 0. Check to ensure we have all the required commands
function checkRequiredCommands(cb) {
self.driver.hasAllCommands(cb);
},
// 1. Create a working directory
function setup(cb) {
self.driver.createWorkingDirectory(cb);
},
// 2. Apply transformation rules
function applyRules(cb) {
async.mapSeries(self.rules, function (rule, ruleCallback) {
self.applyRule(rule, ruleCallback);
}, function (err) {
cb(err, self);
});
},
// 3. Fetch a diff between the working and the original root
function fetchFullDiff(cb) {
self.driver.workingDiff(function (err, diff) {
if (err) { return cb(err); }
fullDiffDebug(diff);
self._fullDiff = diff;
cb();
});
},
// 4. Commit the changes if applicable, otherwise remove the working
// directory.
function cleanup(cleanupCallback) {
if (!commit) {
return self.driver.removeWorkingDirectory(cleanupCallback);
}
var root = self.driver.root;
var backup = root + '.bak';
var working = self.driver.working;
async.series([
function backupRoot(cb) {
self.driver.move(root, backup, cb);
},
function moveWorkingToRoot(cb) {
self.driver.move(working, root, cb);
},
function removeBackup(cb) {
self.driver.removeRecursive(backup, cb);
}
], cleanupCallback);
}
], function (err) {
executeCallback(err, self);
});
};
/**
* Applys a given transformation rule.

@@ -187,2 +349,3 @@ * @param {object} rule Rule to apply.

this.pushResult(rule);
actionMethod(rule, cb);

@@ -213,3 +376,3 @@ };

return true;
}
};

@@ -226,5 +389,6 @@ /**

var self = this;
var command = this.driver.copy(rule.source, rule.dest, function (err) {
this.driver.copy(rule.source, rule.dest, function (err, output, command) {
if (err) { return cb(err); }
self.saveCommand(command, rule);
self.addNameChange(rule.source, rule.dest);
cb();

@@ -244,5 +408,6 @@ });

var self = this;
var command = this.driver.move(rule.source, rule.dest, function (err) {
this.driver.move(rule.source, rule.dest, function (err, output, command) {
if (err) { return cb(err); }
self.saveCommand(command, rule);
self.addNameChange(rule.source, rule.dest);
cb();

@@ -310,3 +475,3 @@ });

exclude.forEach(function(excludeRule, index) {
var name = excludeRule.name;
var name = self.driver.absolutePath(excludeRule.name);
var line = excludeRule.line;

@@ -341,21 +506,65 @@

if (files.length === 0) {
self.addWarning(rule, 'All results were excluded.')
self.addWarning(rule, 'All results were excluded.');
return cb();
}
// 3. Apply in-place `sed` to result set
async.each(files, function (file, cb) {
var command = self.driver.sed(
search,
replace,
file.name,
file.line,
function(err) {
if (err) { return cb(err); }
self.saveCommand(command, rule);
cb();
}
);
}, cb);
// 3. Perform Search and Replace
// 3.0 Collect the set of filenames for each of the files being changed.
var filenames = [];
files.forEach(function (file) {
if (!~filenames.indexOf(file.name)) {
filenames.push(file.name);
}
});
async.series([
// 3.1 Create an temporary copy for each file being changed
function createOriginalCopy(cb) {
async.each(filenames, function (name, copyCallback) {
var copyName = name + Transformer.ORIGINAL_POSTFIX;
self.driver.copy(name, copyName, copyCallback);
}, cb);
},
// 3.2 Make replacements by using sed
function sedFiles(cb) {
async.each(files, function (file, sedCallback) {
self.driver.sed(
search,
replace,
file.name,
parseInt(file.line),
function(err, output, command) {
if (err) { return sedCallback(err); }
self.saveCommand(command, rule);
sedCallback();
}
);
}, cb);
},
// 3.3 Collect diffs for each file
function collectDiffs(cb) {
async.each(filenames, function (name, diffCallback) {
var copyName = name + Transformer.ORIGINAL_POSTFIX;
self.driver.diff(copyName, name, function (err, diff) {
if (err) { return diffCallback(err); }
self.addDiff(name, diff);
diffCallback();
});
}, cb);
},
// 3.4 Cleanup temporary files
function cleanup(cb) {
async.each(filenames, function (name, removeCallback) {
var copyName = name + Transformer.ORIGINAL_POSTFIX;
var dotLastName = name + '.last';
var names = [copyName, dotLastName].join(' ');
self.driver.remove(names, removeCallback);
}, cb);
}
], cb);
});
};
{
"name": "fs-transform",
"version": "1.0.1",
"version": "2.0.0",
"description": "Fast, rule based, file system transformations.",
"main": "index.js",
"scripts": {
"test": "jshint index.js && lab -v -c test/*",
"test": "jshint index.js lib/ && lab -v -c -a code test/*",
"doc": "jsdoc -d doc index.js lib/"

@@ -34,3 +34,5 @@ },

"101": "^0.16.1",
"async": "^0.9.0"
"async": "^0.9.0",
"debug": "^2.1.3",
"diff": "^1.3.2"
},

@@ -37,0 +39,0 @@ "devDependencies": {

@@ -150,2 +150,21 @@ # fs-transform

## Dry Runs
`fs-transform` performs all of its work in a temporary directory so it can
gracefully fail if an error occurs (leaving the root directory as it was before
the transforms were applied).
For further safety you can perform transformations in "dry run mode". Using it
is rather simple:
```js
Transformer.dry('/root/directory', myRules, function (err, transformer) {
// Two things to not:
//
// 1) Check to see if errors are reported in `err`
//
// 2) The `transformer` now has all the same information as it would during
// an actual run!
});
```
## Generating Shell Scripts

@@ -192,2 +211,15 @@ `fs-transform` also has the ability to generate reusable shell scripts. Whenever

## Generating Diffs
`fs-transform` allows you to get a full recursive diff between the root
before transformations were applied, and the root after. Here's an example of
how to get the full text diff:
```js
Transformer.dry('/root/directory', myRules, function (err, transformer) {
// Get and log the diff:
var fullDiff = transformer.getDiff();
console.log(fullDiff);
});
```
## Contributing

@@ -194,0 +226,0 @@

@@ -42,5 +42,5 @@ var Lab = require('lab');

it('should use the empty string if no root was given', function (done) {
it('should use the current working directory if no root was given', function (done) {
var driver = new FsDriver();
expect(driver.root).to.equal('');
expect(driver.root).to.equal(process.cwd());
done();

@@ -68,9 +68,25 @@ });

});
it('should return null if given a non string path', function(done) {
expect(driver.absolutePath()).to.be.null();
expect(driver.absolutePath(undefined)).to.be.null();
expect(driver.absolutePath(null)).to.be.null();
expect(driver.absolutePath({})).to.be.null();
expect(driver.absolutePath(42)).to.be.null();
done();
});
it('should use the working directory if one is present', function (done) {
expect(driver.absolutePath('foo')).to.equal('/tmp/foo');
driver.working = '/etc';
expect(driver.absolutePath('foo')).to.equal('/etc/foo');
done();
})
}); // end 'absolutePath'
describe('file system', function () {
var driver = new FsDriver('/tmp');
describe('exec', function () {
var driver = new FsDriver('/root/dir');
beforeEach(function (done) {
sinon.stub(childProcess, 'exec').yields();
sinon.stub(childProcess, 'exec').yieldsAsync();
done();

@@ -84,71 +100,92 @@ });

describe('move', function() {
it('should return the move command', function(done) {
var command = "mv /tmp/foo /tmp/bar";
expect(driver.move('foo', 'bar', noop)).to.equal(command);
it('should execute the given command', function(done) {
var command = 'cp wow neat';
driver.exec(command, function (err) {
expect(childProcess.exec.calledWith(command)).to.be.true();
done();
});
it('should execute system `mv` when moving a file', function (done) {
var source = 'a.txt';
var dest = 'b.txt';
driver.move(source, dest, function (err) {
if (err) { return done(err); }
var command = 'mv /tmp/' + source + ' /tmp/' + dest;
expect(childProcess.exec.calledWith(command))
.to.be.true();
done();
});
});
it('should provide the command in the callback', function(done) {
var expected = 'cp gnarly brah';
driver.exec(expected, function (err, output, command) {
expect(command).to.equal(expected);
done();
});
});
it('should yield `exec` errors to the given callback', function (done) {
childProcess.exec.yields(new Error('Error'));
driver.move('foo', 'bar', function (err) {
expect(err).to.exist();
done();
});
it('should replace working paths with root in returned commands', function(done) {
var expected = 'command /root/dir/a /root/dir/b /root/dir/c';
var execute = 'command /work/dir/a /work/dir/b /work/dir/c'
driver.working = '/work/dir';
driver.exec(execute, function (err, output, command) {
expect(command).to.equal(expected);
delete driver.working;
done();
});
}); // end 'move'
});
describe('copy', function() {
it('should return the copy command', function(done) {
var command = "cp /tmp/foo /tmp/bar";
expect(driver.copy('foo', 'bar', noop)).to.equal(command);
it('should yield childProcess.exec errors to the callback', function(done) {
var error = new Error('Some error');
childProcess.exec.yieldsAsync(error);
driver.exec('whatever', function (err) {
expect(err).to.equal(error);
done();
});
});
}); // end 'exec'
it('should execute system `cp` when copying a file', function (done) {
var source = 'a.txt';
var dest = 'b.txt';
driver.copy(source, dest, function (err) {
if (err) { return done(err); }
var command = 'cp /tmp/' + source + ' /tmp/' + dest;
expect(childProcess.exec.calledWith(command))
.to.be.true();
done();
});
describe('file system', function () {
var driver;
beforeEach(function (done) {
driver = new FsDriver('/tmp');
sinon.stub(driver, 'exec').yieldsAsync();
done();
});
it('should check for commands using driver.exec', function(done) {
driver.hasCommand('foo', function () {
expect(driver.exec.calledOnce).to.be.true();
expect(driver.exec.calledWith('command -v foo')).to.be.true();
done();
});
});
it('should yield `exec` errors to the given callback', function (done) {
childProcess.exec.yields(new Error('Error'));
driver.copy('foo', 'bar', function (err) {
expect(err).to.exist();
done();
it('should check for all commands using driver.exec', function(done) {
sinon.stub(driver, 'hasCommand').yieldsAsync();
driver.hasAllCommands(function () {
expect(driver.hasCommand.callCount).to.equal(FsDriver.commands.length);
FsDriver.commands.forEach(function (name) {
expect(driver.hasCommand.calledWith(name)).to.be.true();
});
driver.hasCommand.restore();
done();
});
}); // end 'copy'
});
describe('grep', function() {
it('should return the grep command', function(done) {
var command = 'grep -rn search /tmp';
expect(driver.grep('search', noop)).to.equal(command);
it('should use driver.exec to perform file moves', function (done) {
var command = "mv /tmp/foo /tmp/bar";
driver.move('foo', 'bar', function () {
expect(driver.exec.calledOnce).to.be.true();
expect(driver.exec.calledWith(command)).to.be.true();
done();
});
});
it('should execute system `grep`', function (done) {
driver.grep('foo', function (err) {
if (err) { return done(err); }
var command = 'grep -rn foo /tmp';
expect(childProcess.exec.calledWith(command))
.to.be.true();
it('should use driver.exec to perform file copies', function(done) {
var command = "cp /tmp/foo /tmp/bar";
driver.copy('foo', 'bar', function () {
expect(driver.exec.calledOnce).to.be.true();
expect(driver.exec.calledWith(command)).to.be.true();
done();
});
});
describe('grep', function() {
it('should use driver.exec to perform the grep', function(done) {
var command = 'grep -rn \'search\' /tmp';
driver.grep('search', function () {
expect(driver.exec.calledOnce).to.be.true();
expect(driver.exec.calledWith(command)).to.be.true();
done();

@@ -159,6 +196,6 @@ });

it('should properly escape search patterns', function(done) {
driver.grep('\\lambda', function (err) {
driver.grep('\\lambda\'', function (err) {
if (err) { return done(err); }
var command = 'grep -rn \\\\lambda /tmp';
expect(childProcess.exec.calledWith(command))
var command = 'grep -rn \'\\\\lambda\\\'\' /tmp';
expect(driver.exec.calledWith(command))
.to.be.true();

@@ -169,8 +206,11 @@ done();

it('should yield `exec` errors to the given callback', function (done) {
childProcess.exec.yields(new Error('Error'));
driver.grep('foo', function (err) {
expect(err).to.exist();
done();
it('should use the working directory when one is supplied', function(done) {
var command = 'grep -rn \'search\' /working';
driver.working = '/working';
driver.grep('search', function (err) {
if (err) { return done(err); }
expect(driver.exec.calledWith(command)).to.be.true();
driver.exec.restore();
});
done();
});

@@ -180,15 +220,7 @@ }); // end 'grep'

describe('sed', function() {
it('should return the correct sed command', function(done) {
var command = 'sed -i "" "1337s/bar/baz/g" /tmp/file1.txt';
expect(driver.sed('bar', 'baz', 'file1.txt', 1337, noop))
.to.equal(command);
done();
});
it('should execute system `sed`', function(done) {
driver.sed('foo', 'bar', 'example.txt', 20, function (err) {
if (err) { return done(err); }
var command = 'sed -i "" "20s/foo/bar/g" /tmp/example.txt';
expect(childProcess.exec.calledWith(command))
.to.be.true();
it('should use driver.exec to perform the sed', function(done) {
var command = 'sed -i.last \'1337s/bar/baz/g\' /tmp/file1.txt';
driver.sed('bar', 'baz', 'file1.txt', 1337, function () {
expect(driver.exec.calledOnce).to.be.true();
expect(driver.exec.calledWith(command)).to.be.true();
done();

@@ -201,4 +233,4 @@ });

if (err) { return done(err); }
var command = 'sed -i "" "17s/\\/foo/\\/bar/g" /tmp/example.txt';
expect(childProcess.exec.calledWith(command))
var command = 'sed -i.last \'17s/\\/foo/\\/bar/g\' /tmp/example.txt';
expect(driver.exec.calledWith(command))
.to.be.true();

@@ -208,10 +240,2 @@ done();

});
it('should yield `exec` errors to the given callback', function(done) {
childProcess.exec.yields(new Error('Error'));
driver.sed('foo', 'bar', 'awesome.txt', 28, function (err) {
expect(err).to.exist();
done();
});
});
}); // end 'sed'

@@ -223,4 +247,4 @@

driver.exists('example');
expect(stub.calledOnce);
expect(stub.calledWith('/tmp/example'));
expect(stub.calledOnce).to.be.true();
expect(stub.calledWith('/tmp/example')).to.be.true();
fs.existsSync.restore();

@@ -239,4 +263,169 @@ done();

});
}); // end 'exists'
describe('diff', function() {
it('should use driver.exec to perform the diff', function(done) {
var command = 'diff -u -r /tmp/a /tmp/b';
driver.diff('/tmp/a', '/tmp/b', function (err) {
expect(driver.exec.calledOnce).to.be.true();
expect(driver.exec.calledWith(command)).to.be.true();
done();
});
});
it('should ignore errors with code 1 (indicated differences)', function (done) {
var error = new Error('diff error');
error.code = 1;
driver.exec.yields(error);
driver.diff('a', 'b', function (err) {
expect(err).to.be.null();
done();
});
});
it('should yield errors with code > 1 to the given callback', function (done) {
var error = new Error('diff error');
error.code = 2;
driver.exec.yields(error);
driver.diff('a', 'b', function (err) {
expect(err).to.equal(error);
done();
});
});
}); // end 'diff'
it('should use driver.exec to remove files', function(done) {
var command = 'rm /tmp/file1.txt';
driver.remove('file1.txt', function () {
expect(driver.exec.calledOnce).to.be.true();
expect(driver.exec.calledWith(command)).to.be.true();
done();
});
});
it('should use driver.exec to recursively remove files', function(done) {
var command = 'rm -rf /tmp/dir/';
driver.removeRecursive('dir/', function () {
expect(driver.exec.calledOnce).to.be.true();
expect(driver.exec.calledWith(command)).to.be.true();
done();
});
});
describe('findWorkingDirectory', function() {
beforeEach(function (done) {
driver.root = '/etc/init.d';
sinon.stub(driver, 'exists');
sinon.stub(Math, 'random')
.onFirstCall().returns(0)
.onSecondCall().returns(1)
.onThirdCall().returns(2);
done();
});
afterEach(function (done) {
Math.random.restore();
done();
});
it('should find a new working directory', function(done) {
driver.exists.returns(false);
var working = driver.findWorkingDirectory();
expect(Math.random.calledOnce).to.be.true();
expect(working).to.equal('/tmp/.init.d.fs-work.0');
done();
});
it('should not attempt to use a working directory that already exists', function(done) {
driver.exists
.onFirstCall().returns(true)
.onSecondCall().returns(true)
.onThirdCall().returns(false);
expect(driver.findWorkingDirectory())
.to.equal('/tmp/.init.d.fs-work.2');
done();
});
});
describe('createWorkingDirectory', function() {
beforeEach(function (done) {
driver.root = '/etc/init.d';
sinon.stub(driver, 'exists').returns(false);
sinon.stub(Math, 'random').returns(0);
done();
});
afterEach(function (done) {
Math.random.restore();
done();
});
it('should set the working directory', function(done) {
driver.createWorkingDirectory(function (err) {
if (err) { return done(err); }
expect(driver.working).to.equal('/tmp/.init.d.fs-work.0');
done();
});
});
it('should create the working directory with driver.exec', function (done) {
var command = 'cp -r /etc/init.d /tmp/.init.d.fs-work.0';
driver.createWorkingDirectory(function (err) {
if (err) { return done(err); }
expect(driver.exec.calledWith(command)).to.be.true();
done();
});
});
it('should yield system errors to the supplied callback', function(done) {
var error = new Error('Errorz');
driver.exec.yields(error);
driver.createWorkingDirectory(function (err) {
expect(err).to.equal(error);
done();
});
});
}); // end 'createWorkingDirectory'
describe('removeWorkingDirectory', function() {
beforeEach(function (done) {
sinon.stub(driver, 'removeRecursive').yieldsAsync();
done();
});
afterEach(function (done) {
driver.removeRecursive.restore();
done();
});
it('should remove the working directory', function(done) {
driver.working = '/tmp/x';
driver.removeWorkingDirectory(function (err) {
if (err) { return done(err); }
expect(driver.removeRecursive.calledWith('/tmp/x')).to.be.true();
expect(driver.working).to.be.null();
done();
});
});
it('should not remove a working directory if none exists', function(done) {
driver.working = null;
driver.removeWorkingDirectory(function (err) {
if (err) { return done(err); }
expect(driver.removeRecursive.callCount).to.equal(0);
done();
});
});
it('should yield system errors to the supplied callback', function(done) {
var error = new Error('Remove error');
driver.removeRecursive.yields(error);
driver.working = '/woot/sauce';
driver.removeWorkingDirectory(function (err) {
expect(err).to.equal(error);
done();
});
});
}); // end 'removeWorkingDirectory'
}); // end 'file system'
}); // end 'fs-driver'
SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc