Socket
Socket
Sign inDemoInstall

fs-jetpack

Package Overview
Dependencies
Maintainers
1
Versions
61
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

Comparing version 0.5.3 to 0.6.0

.editorconfig

13

CHANGELOG.md

@@ -0,1 +1,14 @@

0.6.0 (2015-03-30)
-------------------
* Lots of code refactoring
* **(breaking change)** `dir()` no longer has `exists` option.
* **(breaking change)** `file()` no longer has `exists` and `empty` options.
* **(breaking change)** `safe` option for `write()` renamed to `atomic` (and uses new algorithm under the hood).
* **(breaking change)** `safe` option for `read()` dropped (`atomic` while writing is enough).
* **(breaking change)** In `copy()` options `only` and `allBut` have been replaced by option `matching`.
* **(breaking change)** In `remove()` options `only` and `allBut` have been dropped (to do the same use `find()`, and then remove).
* **(breaking change)** Default jsonIndent changed form 0 to 2.
* `find()` method added.
* More telling errors when `read()` failed while parsing JSON.
0.5.3 (2015-01-06)

@@ -2,0 +15,0 @@ -------------------

251

lib/copy.js

@@ -12,12 +12,51 @@ "use strict";

var inspector = require('./inspector');
var fileOps = require('./fileOps');
var fileOps = require('./file_ops');
var normalizeOptions = function (options) {
var parseOptions = function (options, from) {
var parsedOptions = {};
options = options || {};
if (typeof options.overwrite !== 'boolean') {
options.overwrite = false;
parsedOptions.overwrite = options.overwrite;
if (options.matching) {
parsedOptions.allowedToCopy = parseMatching(options.matching, from);
} else {
parsedOptions.allowedToCopy = function () {
// Default behaviour - copy everything.
return true;
};
}
return options;
return parsedOptions;
};
var parseMatching = function (matching, fromPath) {
if (typeof matching === 'string') {
matching = [matching];
}
var patterns = matching.map(function (pattern) {
// Turn relative matchers into absolute
// (change "./a/b" to "/path_to_copy/a/b")
if (/^\.\//.test(pattern)) {
return pattern.replace(/^\./, fromPath);
}
return pattern;
});
return matcher.create(patterns);
};
var generateNoSourceError = function (path) {
var err = new Error("Path to copy doesn't exist " + path);
err.code = 'ENOENT';
return err;
};
var generateDestinationExistsError = function (path) {
var err = new Error('Destination path already exists ' + path);
err.code = 'EEXIST';
return err;
};
//---------------------------------------------------------

@@ -27,14 +66,9 @@ // Sync

// Soubroutine which copies a file or tree of files.
var copySync = function (from, to) {
var stat = fs.statSync(from);
if (stat.isDirectory()) {
mkdirp.sync(to, { mode: mode.normalizeFileMode(stat.mode) });
var list = fs.readdirSync(from);
list.forEach(function (filename) {
copySync(pathUtil.resolve(from, filename), pathUtil.resolve(to, filename));
});
} else {
var data = fs.readFileSync(from);
fileOps.write(to, data, { mode: mode.normalizeFileMode(stat.mode) });
var copySync = function (inspectData, to) {
var mod = mode.normalizeFileMode(inspectData.mode);
if (inspectData.type === 'dir') {
mkdirp.sync(to, { mode: mod });
} else if (inspectData.type === 'file') {
var data = fs.readFileSync(inspectData.absolutePath);
fileOps.write(to, data, { mode: mod });
}

@@ -44,38 +78,21 @@ };

var sync = function (from, to, options) {
options = normalizeOptions(options);
options = parseOptions(options, from);
if (!exists.sync(from)) {
var err = new Error("Path to copy '" + from + "' doesn't exist.");
err.code = 'ENOENT';
throw err;
throw generateNoSourceError(from);
}
if (exists.sync(to) && options.overwrite === false) {
var err = new Error('Destination path already exists.');
err.code = 'EEXIST';
throw err;
if (exists.sync(to) && !options.overwrite) {
throw generateDestinationExistsError(to);
}
if (options.only === undefined && options.allBut === undefined) {
// No special options set, we can just copy the whole thing.
copySync(from, to);
return;
var walker = inspector.createTreeWalkerSync(from);
while (walker.hasNext()) {
var inspectData = walker.getNext();
var destPath = pathUtil.join(to, inspectData.relativePath);
if (options.allowedToCopy(inspectData.absolutePath)) {
copySync(inspectData, destPath);
}
}
// Figure out what to copy, and what not to copy.
var tree = inspector.tree(from);
var pathsToCopy;
if (options.only) {
pathsToCopy = matcher.treeToWhitelistPaths(from, tree, options.only);
} else if (options.allBut) {
pathsToCopy = matcher.treeToBlacklistPaths(from, tree, options.allBut);
}
// Remove every path which is in array separately.
pathsToCopy.forEach(function (path) {
var internalPart = path.substring(from.length);
copySync(path, pathUtil.join(to, internalPart));
});
};

@@ -87,38 +104,15 @@

var qStat = Q.denodeify(fs.stat);
var qReaddir = Q.denodeify(fs.readdir);
var qReadFile = Q.denodeify(fs.readFile);
var qMkdirp = Q.denodeify(mkdirp);
var promisedReadFile = Q.denodeify(fs.readFile);
var promisedMkdirp = Q.denodeify(mkdirp);
// Soubroutine which copies a file or tree of files.
var copyAsync = function (from, to) {
var deferred = Q.defer();
qStat(from).then(function (stat) {
if (stat.isDirectory()) {
// Prepare destination directory
qMkdirp(to, { mode: mode.normalizeFileMode(stat.mode) })
.then(function () {
// Read all things to copy...
return qReaddir(from);
})
.then(function (list) {
// ...and just copy them.
var promises = list.map(function (filename) {
return copyAsync(pathUtil.resolve(from, filename), pathUtil.resolve(to, filename));
});
return Q.all(promises);
})
.then(deferred.resolve, deferred.reject);
} else {
// Copy single file
qReadFile(from)
.then(function (data) {
return fileOps.writeAsync(to, data, { mode: mode.normalizeFileMode(stat.mode) });
})
.then(deferred.resolve, deferred.reject);
}
}, deferred.reject);
return deferred.promise;
var copyAsync = function (inspectData, to) {
var mod = mode.normalizeFileMode(inspectData.mode);
if (inspectData.type === 'dir') {
return promisedMkdirp(to, { mode: mod });
} else if (inspectData.type === 'file') {
return promisedReadFile(inspectData.absolutePath)
.then(function (data) {
return fileOps.writeAsync(to, data, { mode: mod });
});
}
};

@@ -128,62 +122,45 @@

var deferred = Q.defer();
options = normalizeOptions(options);
options = parseOptions(options, from);
// Checks before copying
exists.async(from)
.then(function (ex) {
if (!ex) {
var err = new Error("Path to copy '" + from + "' doesn't exist.");
err.code = 'ENOENT';
deferred.reject(err);
.then(function (srcPathExists) {
if (!srcPathExists) {
throw generateNoSourceError(from);
} else {
exists.async(to)
.then(function (ex) {
if (ex && options.overwrite === false) {
var err = new Error('Destination path already exists.');
err.code = 'EEXIST';
deferred.reject(err);
} else {
startCopying();
}
});
return exists.async(to)
}
});
var startCopying = function () {
if (options.only === undefined && options.allBut === undefined) {
// No special options set, we can just copy the whole thing.
copyAsync(from, to).then(done, deferred.reject);
})
.then(function (destPathExists) {
if (destPathExists && !options.overwrite) {
throw generateDestinationExistsError(to);
} else {
figureOutWhatToCopy();
startCopying();
}
};
var figureOutWhatToCopy = function () {
// Figure out what to copy, and what not to copy.
inspector.treeAsync(from)
.then(function (tree) {
var pathsToCopy;
if (options.only) {
pathsToCopy = matcher.treeToWhitelistPaths(from, tree, options.only);
} else if (options.allBut) {
pathsToCopy = matcher.treeToBlacklistPaths(from, tree, options.allBut);
})
.catch(deferred.reject);
var startCopying = function () {
var copyOne = function (walker) {
if (walker.hasNext()) {
var inspectData = walker.getNext();
var destPath = pathUtil.join(to, inspectData.relativePath);
if (options.allowedToCopy(inspectData.absolutePath)) {
copyAsync(inspectData, destPath)
.then(function () {
copyOne(walker);
});
} else {
copyOne(walker);
}
} else {
deferred.resolve();
}
// Remove every path which is in array separately.
var promises = pathsToCopy.map(function (path) {
var internalPart = path.substring(from.length);
return copyAsync(path, pathUtil.join(to, internalPart));
});
return Q.all(promises);
})
.then(done, deferred.reject);
};
inspector.createTreeWalkerAsync(from).then(copyOne);
};
var done = function () {
// Function to make sure we are returning undefined here.
deferred.resolve();
};
return deferred.promise;

@@ -190,0 +167,0 @@ };

@@ -15,5 +15,2 @@ "use strict";

}
if (typeof criteria.exists !== 'boolean') {
criteria.exists = true;
}
if (typeof criteria.empty !== 'boolean') {

@@ -33,9 +30,9 @@ criteria.empty = false;

var sync = function (path, criteria) {
criteria = getCriteriaDefaults(criteria);
try {
var stat = fs.statSync(path);
} catch (err) {
// detection if path exists
// Detection if path already exists
if (err.code !== 'ENOENT') {

@@ -45,27 +42,16 @@ throw err;

}
// check if for sure given path is directory, because this
// is the only thing which interests us here
if (stat && !stat.isDirectory()) {
// this is not a directory, so remove it
rimraf.sync(path);
// clear stat variable to indicate now nothing is there
if (stat && stat.isFile()) {
// This is file, but should be directory.
fs.unlinkSync(path);
// Clear stat variable to indicate now nothing is there
stat = undefined;
}
if (stat) {
// directory already exists
if (criteria.exists === false) {
// path exists, and we want for it not to exist
rimraf.sync(path);
// no need to check more criteria, exists=false is ultimate one
return;
}
// ensure existing directory matches criteria
if (criteria.empty === true) {
// delete everything inside this directory
// Ensure existing directory matches criteria
if (criteria.empty) {
// Delete everything inside this directory
var list = fs.readdirSync(path);

@@ -76,17 +62,12 @@ list.forEach(function (filename) {

}
var mode = modeUtil.normalizeFileMode(stat.mode);
if (criteria.mode !== undefined && criteria.mode !== mode) {
// mode is different than specified in criteria, fix that
// Mode is different than specified in criteria, fix that.
fs.chmodSync(path, criteria.mode);
}
} else {
// directory doesn't exist
if (criteria.exists === true) {
// create this directory
mkdirp.sync(path, { mode: criteria.mode });
}
// Directory doesn't exist now. Create it.
mkdirp.sync(path, { mode: criteria.mode });
}

@@ -99,22 +80,30 @@ };

var qStat = Q.denodeify(fs.stat);
var qChmod = Q.denodeify(fs.chmod);
var qReaddir = Q.denodeify(fs.readdir);
var qRimraf = Q.denodeify(rimraf);
var qMkdirp = Q.denodeify(mkdirp);
var promisedStat = Q.denodeify(fs.stat);
var promisedChmod = Q.denodeify(fs.chmod);
var promisedReaddir = Q.denodeify(fs.readdir);
var promisedRimraf = Q.denodeify(rimraf);
var promisedMkdirp = Q.denodeify(mkdirp);
// delete all files and directores inside a directory,
// but leave the main directory intact
// Delete all files and directores inside given directory
var empty = function (path) {
var deferred = Q.defer();
qReaddir(path)
promisedReaddir(path)
.then(function (list) {
var promises = list.map(function (filename) {
return qRimraf(pathUtil.resolve(path, filename));
});
return Q.all(promises);
var doOne = function (index) {
if (index === list.length) {
deferred.resolve();
} else {
var subPath = pathUtil.resolve(path, list[index]);
promisedRimraf(subPath).then(function () {
doOne(index + 1);
});
}
};
doOne(0);
})
.then(deferred.resolve, deferred.reject);
.catch(deferred.reject);
return deferred.promise;

@@ -125,79 +114,86 @@ };

var deferred = Q.defer();
criteria = getCriteriaDefaults(criteria);
qStat(path)
.then(function (stat) {
// check if for sure given path is directory, because this
// is the only thing which interests us here
if (!stat.isDirectory()) {
// this is not a directory, so remove it
qRimraf(path)
.then(function () {
// clear stat variable to indicate now nothing is there
ensureCriteria(undefined);
}, deferred.reject);
} else {
ensureCriteria(stat);
}
}, function (err) {
// detection if path exists
if (err.code !== 'ENOENT') {
// this is other error that nonexistent path, so end here
deferred.reject(err);
} else {
ensureCriteria(undefined);
}
});
var ensureCriteria = function (stat) {
if (stat) {
// if stat is defined it means directory exists
if (criteria.exists === false) {
// path exists, and we want for it not to exist
qRimraf(path).then(deferred.resolve, deferred.reject);
// no need to check more criteria, exists=false is ultimate one
return;
var checkWhatWeHaveNow = function () {
var deferred = Q.defer();
promisedStat(path)
.then(function (stat) {
if (stat.isFile()) {
// This is not a directory, and should be.
promisedRimraf(path)
.then(function () {
// We just deleted that path, so can't
// pass stat further down.
deferred.resolve(undefined);
})
.catch(deferred.reject);
} else {
deferred.resolve(stat);
}
// ensure existing directory matches criteria
})
.catch(function (err) {
if (err.code === 'ENOENT') {
// Path doesn't exist
deferred.resolve(undefined);
} else {
// This is other error that nonexistent path, so end here.
deferred.reject(err);
}
});
return deferred.promise;
};
var checkWhatShouldBe = function (stat) {
var deferred = Q.defer();
var needToCheckMoreCriteria = function () {
var checkEmptiness = function () {
if (criteria.empty) {
// Delete everything inside this directory
empty(path)
.then(deferred.resolve, deferred.reject);
} else {
// Everything done!
deferred.resolve();
}
};
var checkMode = function () {
var mode = modeUtil.normalizeFileMode(stat.mode);
if (criteria.mode !== undefined && criteria.mode !== mode) {
// mode is different than specified in criteria, fix that
qChmod(path, criteria.mode)
.then(deferred.resolve, deferred.reject);
// Mode is different than specified in criteria, fix that
promisedChmod(path, criteria.mode)
.then(checkEmptiness, deferred.reject);
} else {
// OMG, done!
deferred.resolve();
checkEmptiness();
}
};
if (criteria.empty === true) {
// delete everything inside this directory
empty(path)
.then(checkMode, deferred.reject);
} else {
checkMode();
}
} else {
// we know directory doesn't exist
if (criteria.exists === true) {
// create this directory
qMkdirp(path, { mode: criteria.mode })
checkMode();
};
var checkExistence = function () {
if (!stat) {
promisedMkdirp(path, { mode: criteria.mode })
.then(deferred.resolve, deferred.reject);
} else {
deferred.resolve();
// Directory exists, but we still don't
// know if it matches all criteria.
needToCheckMoreCriteria();
}
}
};
checkExistence();
return deferred.promise;
};
checkWhatWeHaveNow()
.then(checkWhatShouldBe)
.then(deferred.resolve, deferred.reject);
return deferred.promise;

@@ -211,2 +207,2 @@ };

module.exports.sync = sync;
module.exports.async = async;
module.exports.async = async;

@@ -62,2 +62,2 @@ "use strict";

module.exports.sync = sync;
module.exports.async = async;
module.exports.async = async;

@@ -9,3 +9,3 @@ "use strict";

var modeUtils = require('./utils/mode');
var fileOps = require('./fileOps');
var fileOps = require('./file_ops');

@@ -16,8 +16,2 @@ function getCriteriaDefaults(criteria) {

}
if (typeof criteria.exists !== 'boolean') {
criteria.exists = true;
}
if (typeof criteria.empty !== 'boolean') {
criteria.empty = false;
}
if (criteria.mode !== undefined) {

@@ -34,5 +28,5 @@ criteria.mode = modeUtils.normalizeFileMode(criteria.mode);

var sync = function (path, criteria) {
criteria = getCriteriaDefaults(criteria);
try {

@@ -46,3 +40,3 @@ var stat = fs.statSync(path);

}
if (stat && !stat.isFile()) {

@@ -54,18 +48,5 @@ // This have to be file, so if is directory must be removed.

}
if (stat) {
// Ensure existing file
if (criteria.exists === false) {
// Path exists and we want for it not to, so delete and end there.
fs.unlinkSync(path);
return;
}
if (criteria.empty === true) {
// Use truncate because it doesn't change mode of file.
fs.truncateSync(path, 0);
}
// Ensure file mode

@@ -76,11 +57,23 @@ var mode = modeUtils.normalizeFileMode(stat.mode);

}
// Ensure file content
if (criteria.empty === false && criteria.content !== undefined) {
fileOps.write(path, criteria.content, { mode: mode, jsonIndent: criteria.jsonIndent });
if (criteria.content !== undefined) {
fileOps.write(path, criteria.content, {
mode: mode,
jsonIndent: criteria.jsonIndent
});
}
} else if (criteria.exists === true) {
// Create new file.
fileOps.write(path, criteria.content || '', { mode: criteria.mode, jsonIndent: criteria.jsonIndent });
} else {
// File doesn't exist. Create it.
var content = '';
if (criteria.content !== undefined) {
content = criteria.content;
}
fileOps.write(path, content, {
mode: criteria.mode,
jsonIndent: criteria.jsonIndent
});
}

@@ -93,74 +86,79 @@ };

var qUnlink = Q.denodeify(fs.unlink);
var qStat = Q.denodeify(fs.stat);
var qTruncate = Q.denodeify(fs.truncate);
var qChmod = Q.denodeify(fs.chmod);
var qRimraf = Q.denodeify(rimraf);
var promisedStat = Q.denodeify(fs.stat);
var promisedChmod = Q.denodeify(fs.chmod);
var promisedRimraf = Q.denodeify(rimraf);
var async = function (path, criteria) {
var deferred = Q.defer();
criteria = getCriteriaDefaults(criteria);
qStat(path)
.then(function (stat) {
// Check if for sure given path is a file, because this
// is the only thing which interests us here.
if (!stat.isFile()) {
// This is not a file, so remove it.
qRimraf(path)
.then(function () {
// clear stat variable to indicate now nothing is there
ensureCriteria(undefined);
}, deferred.reject);
var checkWhatWeHaveNow = function () {
var deferred = Q.defer();
promisedStat(path)
.then(function (stat) {
if (stat.isDirectory()) {
// This is not a file, so remove it.
promisedRimraf(path)
.then(function () {
// Clear stat variable to indicate now nothing is there
deferred.resolve(undefined);
})
.catch(deferred.reject);
} else {
deferred.resolve(stat);
}
})
.catch(function (err) {
if (err.code === 'ENOENT') {
// Path doesn't exist.
deferred.resolve(undefined);
} else {
// This is other error. Must end here.
deferred.reject(err);
}
});
return deferred.promise;
};
var checkWhatShouldBe = function (stat) {
var deferred = Q.defer();
if (!stat) {
// Path doesn't exist now. Put file there.
var content = '';
if (criteria.content !== undefined) {
content = criteria.content;
}
fileOps.writeAsync(path, content, {
mode: criteria.mode,
jsonIndent: criteria.jsonIndent
})
.then(deferred.resolve, deferred.reject);
} else {
ensureCriteria(stat);
}
}, function (err) {
// Detection if path exists.
if (err.code !== 'ENOENT') {
// This is other error that nonexistent path, so end here.
deferred.reject(err);
} else {
ensureCriteria(undefined);
}
});
var ensureCriteria = function (stat) {
if (stat) {
// Ensure existing file.
if (criteria.exists === false) {
// Path exists and we want for it not to, so delete and end there.
qUnlink(path).then(deferred.resolve, deferred.reject);
return;
}
// File already exists. Must do more checking.
var mode = modeUtils.normalizeFileMode(stat.mode);
var ensureEmptiness = function () {
if (criteria.empty === true) {
// Use truncate because it doesn't change mode of the file.
qTruncate(path, 0)
.then(ensureMode, deferred.reject);
var checkContent = function () {
if (criteria.content !== undefined) {
fileOps.writeAsync(path, criteria.content, {
mode: mode,
jsonIndent: criteria.jsonIndent
})
.then(deferred.resolve, deferred.reject);
} else {
ensureMode();
checkMode();
}
};
var ensureMode = function () {
var checkMode = function () {
if (criteria.mode !== undefined && criteria.mode !== mode) {
qChmod(path, criteria.mode)
.then(ensureContent, deferred.reject);
} else {
ensureContent();
}
};
var ensureContent = function () {
// This is the last step in this branch! End after that.
if (criteria.empty === false && criteria.content !== undefined) {
fileOps.writeAsync(path, criteria.content, { mode: mode, jsonIndent: criteria.jsonIndent })
promisedChmod(path, criteria.mode)
.then(deferred.resolve, deferred.reject);

@@ -171,15 +169,13 @@ } else {

};
// this starts the sequence of asynchronous functions declared above
ensureEmptiness();
} else if (criteria.exists === true) {
fileOps.writeAsync(path, criteria.content || '', { mode: criteria.mode, jsonIndent: criteria.jsonIndent })
.then(deferred.resolve, deferred.reject);
} else {
// File doesn't exist and this is the desired condition, end here.
deferred.resolve();
checkContent();
}
return deferred.promise;
};
checkWhatWeHaveNow()
.then(checkWhatShouldBe)
.then(deferred.resolve, deferred.reject);
return deferred.promise;

@@ -193,2 +189,2 @@ };

module.exports.sync = sync;
module.exports.async = async;
module.exports.async = async;

@@ -10,5 +10,5 @@ "use strict";

var obj = {};
obj.name = pathUtil.basename(path);
if (stat.isFile()) {

@@ -22,11 +22,11 @@ obj.type = 'file';

}
if (computedChecksum) {
obj[computedChecksum.type] = computedChecksum.value;
}
if (options.mode) {
obj.mode = stat.mode;
}
if (options.times) {

@@ -37,14 +37,16 @@ obj.accessTime = stat.atime;

}
if (options.absolutePath) {
obj.absolutePath = path;
}
return obj;
};
// Creates checksum of a directory by using
// checksums and names of all its children inside.
var checksumOfDir = function (inspectList, algo) {
if (inspectList.length === 0) {
return null;
}
var hash = crypto.createHash(algo);
inspectList.forEach(function (inspectObj) {
hash.update(inspectObj.name + (inspectObj[algo] || ''));
hash.update(inspectObj.name + inspectObj[algo]);
});

@@ -54,2 +56,18 @@ return hash.digest('hex');

// Flattens tree structure to one list of inspectObjects.
var flattenTree = function (tree) {
var treeAsList = [];
var crawl = function (inspectObj) {
treeAsList.push(inspectObj);
if (inspectObj.children) {
inspectObj.children.forEach(crawl);
}
};
crawl(tree);
return treeAsList;
};
//---------------------------------------------------------

@@ -60,9 +78,2 @@ // Sync

var fileChecksum = function (path, stat, algo) {
if (stat.size === 0) {
return {
type: algo,
value: null
};
}
var hash = crypto.createHash(algo);

@@ -77,5 +88,5 @@ var data = fs.readFileSync(path);

var inspect = function (path, options) {
var inspectSync = function (path, options) {
options = options || {};
try {

@@ -92,11 +103,11 @@ var stat = fs.statSync(path);

}
if (stat.isFile() && options.checksum) {
var computedChecksum = fileChecksum(path, stat, options.checksum);
}
return createInspectObj(path, options, stat, computedChecksum);
};
var list = function (path, useInspect) {
var listSync = function (path, useInspect) {
try {

@@ -106,4 +117,5 @@ var simpleList = fs.readdirSync(path);

// Detection if path exists
if (err.code === 'ENOENT') {
// Doesn't exist. Return null instead of throwing.
if (err.code === 'ENOENT' || err.code === 'ENOTDIR') {
// Doesn't exist or is a file, not directory.
// Return null instead of throwing.
return null;

@@ -114,7 +126,7 @@ } else {

}
if (!useInspect) {
return simpleList;
}
var inspectConfig = {};

@@ -126,29 +138,65 @@ if (typeof useInspect === 'object') {

var inspectedList = simpleList.map(function (filename) {
return inspect(pathUtil.join(path, filename), inspectConfig);
return inspectSync(pathUtil.join(path, filename), inspectConfig);
});
return inspectedList;
};
var tree = function (path, options) {
var treeSync = function (path, options) {
options = options || {};
var treeBranch = inspect(path, options);
if (treeBranch && treeBranch.type === 'dir') {
treeBranch.size = 0;
treeBranch.children = list(path).map(function (filename) {
var treeSubBranch = tree(pathUtil.join(path, filename), options);
treeBranch.size += treeSubBranch.size;
return treeSubBranch;
});
if (options.checksum) {
treeBranch[options.checksum] = checksumOfDir(treeBranch.children, options.checksum);
return crawlTreeSync(path, options, null);
};
var crawlTreeSync = function (path, options, parent) {
var treeBranch = inspectSync(path, options);
if (treeBranch) {
if (options.relativePath) {
if (!parent) {
treeBranch.relativePath = '.';
} else {
treeBranch.relativePath = parent.relativePath + '/' + pathUtil.basename(path);
}
}
if (treeBranch.type === 'dir') {
treeBranch.size = 0;
// Process all children
treeBranch.children = listSync(path).map(function (filename) {
// Go one level deeper with crawling
var treeSubBranch = crawlTreeSync(pathUtil.join(path, filename), options, treeBranch);
// Add together all childrens' size
treeBranch.size += treeSubBranch.size;
return treeSubBranch;
});
if (options.checksum) {
treeBranch[options.checksum] = checksumOfDir(treeBranch.children, options.checksum);
}
}
}
return treeBranch;
};
var createTreeWalkerSync = function (startPath) {
var allFiles = flattenTree(treeSync(startPath, {
absolutePath: true,
relativePath: true,
mode: true
}));
return {
hasNext: function () {
return allFiles.length > 0;
},
getNext: function () {
return allFiles.shift();
}
}
};
//---------------------------------------------------------

@@ -158,28 +206,21 @@ // Async

var qStat = Q.denodeify(fs.stat);
var qReaddir = Q.denodeify(fs.readdir);
var promisedStat = Q.denodeify(fs.stat);
var promisedReaddir = Q.denodeify(fs.readdir);
var fileChecksumAsync = function (path, stat, algo) {
var deferred = Q.defer();
if (stat.size === 0) {
var hash = crypto.createHash(algo);
var s = fs.createReadStream(path);
s.on('data', function(data) {
hash.update(data);
});
s.on('end', function() {
deferred.resolve({
type: algo,
value: null
value: hash.digest('hex')
});
} else {
var hash = crypto.createHash(algo);
var s = fs.createReadStream(path);
s.on('data', function(data) {
hash.update(data);
});
s.on('end', function() {
deferred.resolve({
type: algo,
value: hash.digest('hex')
});
});
s.on('error', deferred.reject);
}
});
s.on('error', deferred.reject);
return deferred.promise;

@@ -190,20 +231,20 @@ };

var deferred = Q.defer();
options = options || {};
qStat(path)
var done = function (stat, computedChecksum) {
deferred.resolve(createInspectObj(path, options, stat, computedChecksum));
};
promisedStat(path)
.then(function (stat) {
if (stat.isFile() && options.checksum) {
// Have to count checksum
fileChecksumAsync(path, stat, options.checksum)
.then(function (computedChecksum) {
deferred.resolve(createInspectObj(path, options, stat, computedChecksum));
}, deferred.reject);
fileChecksumAsync(path, stat, options.checksum).then(function (checksum) {
done(stat, checksum);
});
} else {
// Finish now
deferred.resolve(createInspectObj(path, options, stat, null));
done(stat, null);
}
}, function (err) {
})
.catch(function (err) {
// Detection if path exists

@@ -217,3 +258,3 @@ if (err.code === 'ENOENT') {

});
return deferred.promise;

@@ -224,43 +265,17 @@ };

var deferred = Q.defer();
qReaddir(path)
promisedReaddir(path)
.then(function (simpleList) {
if (!useInspect) {
// we are done!
// Only list of paths is required. We are done.
deferred.resolve(simpleList);
} else {
var inspectConfig = {};
if (typeof useInspect === 'object') {
// useInspec contains config
inspectConfig = useInspect;
}
// have to call inspect for every item in array
var inspectedList = simpleList.concat();
var countDone = 0;
simpleList.forEach(function (filename) {
inspectAsync(pathUtil.join(path, filename), inspectConfig)
.then(function (inspectObj) {
var index = inspectedList.indexOf(filename);
inspectedList[index] = inspectObj;
countDone += 1;
if (countDone === simpleList.length) {
// we are done!
deferred.resolve(inspectedList);
}
}, deferred.reject);
});
turnSimpleListIntoInspectObjectsList(simpleList);
}
}, function (err) {
})
.catch(function (err) {
// Detection if path exists
if (err.code === 'ENOENT') {
// Doesn't exist. Return null instead of throwing.
if (err.code === 'ENOENT' || err.code === 'ENOTDIR') {
// Doesn't exist or is a file, not directory.
// Return null instead of throwing.
deferred.resolve(null);

@@ -271,3 +286,27 @@ } else {

});
var turnSimpleListIntoInspectObjectsList = function (pathsList) {
var inspectConfig = {};
if (typeof useInspect === 'object') {
// useInspec contains config
inspectConfig = useInspect;
}
var doOne = function (index) {
if (index === pathsList.length) {
deferred.resolve(pathsList);
} else {
var itemPath = pathUtil.join(path, pathsList[index]);
inspectAsync(itemPath, inspectConfig)
.then(function (inspectObj) {
pathsList[index] = inspectObj;
doOne(index + 1);
})
.catch(deferred.reject);
}
};
doOne(0);
};
return deferred.promise;

@@ -277,56 +316,96 @@ };

var treeAsync = function (path, options) {
options = options || {};
return crawlTreeAsync(path, options);
};
var crawlTreeAsync = function (path, options, parent) {
var deferred = Q.defer();
options = options || {};
inspectAsync(path, options)
.then(function (treeBranch) {
if (treeBranch && treeBranch.type === 'dir') {
// traverse recursively all levels down the directory structure
listAsync(path)
.then(function (children) {
// now children is just an array of filename strings
treeBranch.children = children;
treeBranch.size = 0;
var countDone = 0;
var areWeDone = function () {
if (countDone === children.length) {
if (options.checksum) {
// We are done, but still have to calculate checksum of whole directory.
treeBranch[options.checksum] = checksumOfDir(treeBranch.children, options.checksum);
}
deferred.resolve(treeBranch);
}
};
// maybe the directory is emtpy, so we are done here
areWeDone();
// otherwise replace every item with full tree branch object
children.forEach(function (filename, index) {
treeAsync(pathUtil.join(path, filename), options)
if (!treeBranch) {
// Given path doesn't exist, or it is a file. We are done.
deferred.resolve(treeBranch);
return;
}
if (options.relativePath) {
if (!parent) {
treeBranch.relativePath = '.';
} else {
treeBranch.relativePath = parent.relativePath + '/' + pathUtil.basename(path);
}
}
if (treeBranch.type !== 'dir') {
// This is a file, we are done here.
deferred.resolve(treeBranch);
return;
}
// Ok, this is a directory, must inspect all its children as well.
listAsync(path)
.then(function (children) {
treeBranch.children = children;
treeBranch.size = 0;
var done = function () {
if (options.checksum) {
// We are done, but still have to calculate checksum of whole directory.
treeBranch[options.checksum] = checksumOfDir(treeBranch.children, options.checksum);
}
deferred.resolve(treeBranch);
};
var doOne = function (index) {
if (index === children.length) {
done();
} else {
var subPath = pathUtil.join(path, children[index]);
crawlTreeAsync(subPath, options, treeBranch)
.then(function (treeSubBranch) {
children[index] = treeSubBranch;
treeBranch.size += treeSubBranch.size;
countDone += 1;
areWeDone();
}, deferred.reject);
});
});
} else {
// it's just a file, so we are done
deferred.resolve(treeBranch);
}
}, deferred.reject);
doOne(index + 1);
})
.catch(deferred.reject);
}
};
doOne(0);
});
})
.catch(deferred.reject);
return deferred.promise;
};
var createTreeWalkerAsync = function (startPath) {
var deferred = Q.defer();
treeAsync(startPath, {
absolutePath: true,
relativePath: true,
mode: true
})
.then(function (wholeTree) {
var allFiles = flattenTree(wholeTree);
deferred.resolve({
hasNext: function () {
return allFiles.length > 0;
},
getNext: function () {
return allFiles.shift();
}
});
});
return deferred.promise;
};
//---------------------------------------------------------

@@ -336,8 +415,14 @@ // API

module.exports.inspect = inspect;
module.exports.list = list;
module.exports.tree = tree;
module.exports.inspect = inspectSync;
module.exports.list = listSync;
module.exports.tree = treeSync;
module.exports.createTreeWalkerSync = createTreeWalkerSync;
module.exports.inspectAsync = inspectAsync;
module.exports.listAsync = listAsync;
module.exports.treeAsync = treeAsync;
module.exports.treeAsync = treeAsync;
module.exports.createTreeWalkerAsync = createTreeWalkerAsync;
module.exports.utils = {
flattenTree: flattenTree
};

@@ -8,6 +8,7 @@ // The main thing. Here everything starts.

var fileOps = require('./fileOps');
var fileOps = require('./file_ops');
var inspector = require('./inspector');
var dir = require('./dir');
var file = require('./file');
var find = require('./find');
var copy = require('./copy');

@@ -19,5 +20,5 @@ var exists = require('./exists');

// This is Jetpack Context object.
// It provides API which resolves all paths regarding to passed cwdPath,
// or default process.cwd() if cwdPath was not specified.
// The Jetpack Context object.
// It provides the public API, and resolves all paths regarding to
// passed cwdPath, or default process.cwd() if cwdPath was not specified.
var jetpackContext = function (cwdPath) {

@@ -28,3 +29,3 @@

}
var cwd = function () {

@@ -40,3 +41,3 @@ // return current CWD if no arguments specified...

}
// resolves path to inner CWD path of this jetpack instance

@@ -46,3 +47,3 @@ var resolvePath = function (path) {

}
var path = function () {

@@ -59,3 +60,3 @@ // add CWD base path as first element of arguments array

path: path,
append: function (path, data, options) {

@@ -67,3 +68,3 @@ fileOps.append(resolvePath(path), data, options);

},
copy: function (from, to, options) {

@@ -75,3 +76,3 @@ copy.sync(resolvePath(from), resolvePath(to), options);

},
createWriteStream: function (path, options) {

@@ -83,46 +84,46 @@ return streams.createWriteStream(resolvePath(path), options);

},
dir: function (path, criteria) {
var normalizedPath = pathUtil.resolve(getCwdPath(), path);
var normalizedPath = resolvePath(path);
dir.sync(normalizedPath, criteria);
if (criteria !== undefined && criteria.exists === false) {
return undefined;
}
return cwd(normalizedPath);
},
dirAsync: function (path, criteria) {
var qd = Q.defer();
var normalizedPath = pathUtil.resolve(getCwdPath(), path);
var deferred = Q.defer();
var normalizedPath = resolvePath(path);
dir.async(normalizedPath, criteria)
.then(function () {
if (criteria !== undefined && criteria.exists === false) {
qd.resolve(undefined);
} else {
qd.resolve(cwd(normalizedPath));
}
}, qd.reject);
return qd.promise;
deferred.resolve(cwd(normalizedPath));
}, deferred.reject);
return deferred.promise;
},
exists: function (path) {
return exists.sync(pathUtil.resolve(getCwdPath(), path));
return exists.sync(resolvePath(path));
},
existsAsync: function (path) {
return exists.async(pathUtil.resolve(getCwdPath(), path));
return exists.async(resolvePath(path));
},
file: function (path, criteria) {
file.sync(pathUtil.resolve(getCwdPath(), path), criteria);
file.sync(resolvePath(path), criteria);
return this;
},
fileAsync: function (path, criteria) {
var qd = Q.defer();
var deferred = Q.defer();
var that = this;
file.async(resolvePath(path), criteria)
.then(function () {
qd.resolve(that);
}, qd.reject);
return qd.promise;
deferred.resolve(that);
}, deferred.reject);
return deferred.promise;
},
find: function (startPath, options, returnAs) {
return find.sync(resolvePath(startPath), options, returnAs);
},
findAsync: function (startPath, options, returnAs) {
return find.async(resolvePath(startPath), options, returnAs);
},
inspect: function (path, fieldsToInclude) {

@@ -134,2 +135,3 @@ return inspector.inspect(resolvePath(path), fieldsToInclude);

},
inspectTree: function (path, options) {

@@ -141,3 +143,3 @@ return inspector.tree(resolvePath(path), options);

},
list: function (path, useInspect) {

@@ -149,3 +151,3 @@ return inspector.list(resolvePath(path), useInspect);

},
move: function (from, to) {

@@ -157,17 +159,17 @@ move.sync(resolvePath(from), resolvePath(to));

},
read: function (path, returnAs, options) {
return fileOps.read(resolvePath(path), returnAs, options);
read: function (path, returnAs) {
return fileOps.read(resolvePath(path), returnAs);
},
readAsync: function (path, returnAs, options) {
return fileOps.readAsync(resolvePath(path), returnAs, options);
readAsync: function (path, returnAs) {
return fileOps.readAsync(resolvePath(path), returnAs);
},
remove: function (path, options) {
remove.sync(pathUtil.resolve(getCwdPath(), path), options);
remove: function (path) {
remove.sync(resolvePath(path));
},
removeAsync: function (path, options) {
return remove.async(pathUtil.resolve(getCwdPath(), path), options)
removeAsync: function (path) {
return remove.async(resolvePath(path))
},
rename: function (path, newName) {

@@ -183,3 +185,3 @@ path = resolvePath(path)

},
write: function (path, data, options) {

@@ -194,2 +196,2 @@ fileOps.write(resolvePath(path), data, options);

module.exports = jetpackContext;
module.exports = jetpackContext;

@@ -6,3 +6,11 @@ "use strict";

var Q = require('q');
var mkdirp = require('mkdirp');
var exists = require('./exists');
var generateSourceDoesntExistError = function (path) {
var err = new Error("Path to move doesn't exist " + path);
err.code = 'ENOENT';
return err;
};
//---------------------------------------------------------

@@ -13,3 +21,22 @@ // Sync

var sync = function (from, to) {
fs.renameSync(from, to);
try {
fs.renameSync(from, to);
} catch(err) {
if (err.code !== 'ENOENT') {
// We can't make sense of this error. Rethrow it.
throw err;
} else {
// Ok, source or destination path doesn't exist.
// Must do more investigation.
if (!exists.sync(from)) {
throw generateSourceDoesntExistError(from);
}
if (!exists.sync(to)) {
var destDir = pathUtil.dirname(to);
mkdirp.sync(destDir);
// Retry the attempt
fs.renameSync(from, to);
}
}
}
};

@@ -21,6 +48,54 @@

var qRename = Q.denodeify(fs.rename);
var promisedRename = Q.denodeify(fs.rename);
var promisedMkdirp = Q.denodeify(mkdirp);
var ensureDestinationPathExistsAsync = function (to) {
var deferred = Q.defer();
var destDir = pathUtil.dirname(to);
exists.async(destDir)
.then(function (dstExists) {
if (!dstExists) {
promisedMkdirp(destDir)
.then(deferred.resolve, deferred.reject);
} else {
// Hah, no idea.
deferred.reject(err);
}
})
.catch(deferred.reject);
return deferred.promise;
};
var async = function (from, to) {
return qRename(from, to);
var deferred = Q.defer();
promisedRename(from, to)
.then(deferred.resolve)
.catch(function (err) {
if (err.code !== 'ENOENT') {
// Something unknown. Rethrow original error.
deferred.reject(err);
} else {
// Ok, source or destination path doesn't exist.
// Must do more investigation.
exists.async(from)
.then(function (srcExists) {
if (!srcExists) {
deferred.reject(generateSourceDoesntExistError(from));
} else {
ensureDestinationPathExistsAsync(to)
.then(function () {
// Retry the attempt
return promisedRename(from, to);
})
.then(deferred.resolve, deferred.reject);
}
})
.catch(deferred.reject);
}
});
return deferred.promise;
};

@@ -27,0 +102,0 @@

"use strict";
var pathUtil = require('path');
var Q = require('q');
var rimraf = require('rimraf');
var matcher = require('./utils/matcher');
var inspector = require('./inspector');
//---------------------------------------------------------

@@ -14,25 +10,4 @@ // Sync

var sync = function (path, options) {
options = options || {};
if (options.only === undefined && options.allBut === undefined) {
// No special options set, we can just remove the bastard.
rimraf.sync(path);
return;
}
// Figure out what to remove, and what not to remove.
var tree = inspector.tree(path);
var pathsToRemove;
if (options.only) {
pathsToRemove = matcher.treeToWhitelistPaths(path, tree, options.only);
} else if (options.allBut) {
pathsToRemove = matcher.treeToBlacklistPaths(path, tree, options.allBut);
}
// Remove every path which is in array separately.
pathsToRemove.forEach(function (path) {
rimraf.sync(path);
});
var sync = function (path) {
rimraf.sync(path);
};

@@ -47,34 +22,3 @@

var async = function(path, options) {
var deferred = Q.defer();
options = options || {};
if (options.only === undefined && options.allBut === undefined) {
// No special options set, we can just remove the bastard.
qRimraf(path)
.then(deferred.resolve, deferred.reject);
} else {
// Figure out what to remove, and what not to remove.
inspector.treeAsync(path)
.then(function (tree) {
var pathsToRemove;
if (options.only) {
pathsToRemove = matcher.treeToWhitelistPaths(path, tree, options.only);
} else if (options.allBut) {
pathsToRemove = matcher.treeToBlacklistPaths(path, tree, options.allBut);
}
// Remove every path which is in array separately.
var promises = pathsToRemove.map(function (path) {
return qRimraf(path);
});
return Q.all(promises);
})
.then(function () {
// Wrapped into function to prevent returning array from Q.all
deferred.resolve();
}, deferred.reject);
}
return deferred.promise;
return qRimraf(path);
}

@@ -87,2 +31,2 @@

module.exports.sync = sync;
module.exports.async = async;
module.exports.async = async;

@@ -6,2 +6,2 @@ "use strict";

module.exports.createWriteStream = fs.createWriteStream;
module.exports.createReadStream = fs.createReadStream;
module.exports.createReadStream = fs.createReadStream;

@@ -1,2 +0,2 @@

// Logic for the .gitignore style pattern matching.
// Matcher for glob patterns (e.g. *.txt, /a/b/**/z)

@@ -8,31 +8,17 @@ "use strict";

// Creates minimatch matcher.
// Params:
// masks - array of patterns
// excludePart - (optional) absolute path which is some parent directory
// of `path` and should be excluded from matching
var create = function (patterns, excludePart) {
var create = function (patterns) {
if (typeof patterns === 'string') {
patterns = [patterns];
}
var matchers = patterns.map(function (pattern) {
// if the pattern contains any slashes should be treated as "absolute" pattern,
// so make sure it starts with slash
if (pattern.indexOf('/') !== -1 && pattern.charAt(0) !== '/') {
pattern = '/' + pattern;
}
return new Minimatch(pattern, { matchBase: true, nonegate: false, nocomment: true });
return new Minimatch(pattern, {
matchBase: true,
nonegate: false,
nocomment: true
});
});
// Params:
// path - absolute path to match against
var match = function (path) {
// if...
// path = '/a/b/c'
// excludePart = '/a/b'
// ...then the real path we are testing against is '/c'
if (typeof excludePart === 'string') {
// throw out the excludePart from left side of path
path = pathUtil.sep + pathUtil.relative(excludePart, path);
}
return function (path) {
for (var i = 0; i < matchers.length; i += 1) {

@@ -45,103 +31,4 @@ if (matchers[i].match(path)) {

};
return match;
};
// Gets tree as returned by jetpack.tree(), and adds to every element properties
// if this element (or any parent of this element) matches any of given patterns.
var markTree = function (tree, patterns) {
var matcher = create(patterns);
var traverse = function (branch, parentPath, parentMatches) {
// This is the path we was
branch.matchPath = parentPath + '/' + branch.name;
// If parent matches mark all of its children as well.
branch.matches = parentMatches || matcher(branch.matchPath);
if (branch.type === 'dir') {
branch.children.forEach(function (child) {
traverse(child, branch.matchPath, branch.matches);
});
}
};
traverse(tree, '', false);
};
// Creates list of all paths to branches inside a tree, which are
// matched by patterns (whitelisting).
// Params:
// basePath - path to drectory which is the tree root
// tree - object as returned by inspector.tree()
// patterns - array of .gitignore-like patterns
var treeToWhitelistPaths = function (basePath, tree, patterns) {
var rootPath = pathUtil.dirname(basePath);
var paths = [];
markTree(tree, patterns);
var traverse = function (branch) {
if (branch.matches) {
paths.push(rootPath + branch.matchPath);
} else if (branch.type === 'dir') {
branch.children.forEach(function (child) {
traverse(child);
});
}
};
traverse(tree);
return paths;
};
// Creates list of all paths to branches inside a tree, excluding parts
// of tree matched by patterns (blacklisting).
// Params:
// basePath - path to drectory which is the tree root
// tree - object as returned by inspector.tree()
// patterns - array of .gitignore-like patterns
var treeToBlacklistPaths = function (basePath, tree, patterns) {
var rootPath = pathUtil.dirname(basePath);
var paths = [];
markTree(tree, patterns);
var anyChildMatches = function (branch) {
if (branch.matches) {
return true;
}
if (branch.type === 'dir') {
for (var i = 0; i < branch.children.length; i += 1) {
if (anyChildMatches(branch.children[i])) {
return true;
}
}
}
return false;
};
var traverse = function (branch) {
if (!anyChildMatches(branch)) {
paths.push(rootPath + branch.matchPath);
} else if (branch.type === 'dir') {
branch.children.forEach(function (child) {
traverse(child);
});
}
};
traverse(tree);
return paths;
};
//---------------------------------------------------------
// API
//---------------------------------------------------------
module.exports.create = create;
module.exports.markTree = markTree;
module.exports.treeToWhitelistPaths = treeToWhitelistPaths;
module.exports.treeToBlacklistPaths = treeToBlacklistPaths;

@@ -5,2 +5,2 @@ "use strict";

module.exports = jetpack();
module.exports = jetpack();
{
"name": "fs-jetpack",
"description": "Higher level API for 'fs' library",
"version": "0.5.3",
"description": "Better file system API",
"version": "0.6.0",
"author": "Jakub Szwacz <jakub@szwacz.com>",
"dependencies": {
"minimatch": "0.3.0",
"minimatch": "2.0.1",
"mkdirp": "0.5.0",

@@ -13,7 +13,10 @@ "q": "1.0.1",

"devDependencies": {
"jasmine-node": "1.14.x",
"fs-extra": "0.9.1"
"coveralls": "^2.11.2",
"fs-extra": "^0.16.3",
"istanbul": "^0.3.6",
"jasmine": "^2.2.1"
},
"scripts": {
"test": "node_modules/.bin/jasmine-node spec"
"test": "node_modules/.bin/jasmine",
"coveralls": "node_modules/.bin/istanbul cover node_modules/.bin/jasmine && node_modules/.bin/coveralls < coverage/lcov.info"
},

@@ -26,3 +29,7 @@ "main": "main.js",

},
"license": "MIT"
"license": "MIT",
"keywords": [
"fs",
"file system"
]
}

@@ -1,7 +0,9 @@

fs-jetpack
fs-jetpack [![Build Status](https://travis-ci.org/szwacz/fs-jetpack.svg?branch=master)](https://travis-ci.org/szwacz/fs-jetpack) [![Coverage Status](https://coveralls.io/repos/szwacz/fs-jetpack/badge.svg)](https://coveralls.io/r/szwacz/fs-jetpack)
==========
Motivation: node's [fs library](http://nodejs.org/api/fs.html) is very low level API, what makes it too often painful/inconvenient to use. This project is an attempt to build comprehensive, higher level API on top of that library. See ["Neat tricks fs-jetpack knows"](#how-fun) as a starter.
Node's [fs library](http://nodejs.org/api/fs.html) API is very low level, and because of that painful to use. We need higher layer of abstraction over it. That's what **fs-jetpack** aspires to be.
### Installation
#### [Jump to API Docs](#api)
## Installation
```

@@ -11,3 +13,3 @@ npm install fs-jetpack

### Usage
## Usage
```javascript

@@ -17,46 +19,139 @@ var jetpack = requite('fs-jetpack');

# API
API has the same set of synchronous and asynchronous methods. All async methods are promise based (so no callbacks folks, [promises](https://github.com/kriskowal/q) instead).
Commonly used naming convention in node world is reversed in this library. Asynchronous methods are those with "Async" suffix, all methods without "Async" in the name are synchronous. Reason behind this is that it gives very nice look to blocking API, and promise based non-blocking code is verbose anyway, so one more word is not much of a difference. Also it just feels right to me. When you see "Async" word you are 100% sure this method is returning promise, and when you don't see it, you are 100% sure this method retuns immediately (and possibly blocks).
#What's cool about jetpack?
```javascript
// Usage of blocking API
try {
jetpack.dir('foo');
} catch (err) {
// Something went wrong
}
## Promises instead of callbacks
API has the same set of synchronous and asynchronous methods. All async methods are promise based.
// Usage of non-blocking API
jetpack.dirAsync('foo')
.then(function () {
// Done!
}, function (err) {
// Something went wrong
Commonly used naming convention in Node world is reversed in this library (no 'method' and 'methodSync' naming). Asynchronous methods are those with 'Async' suffix, all methods without 'Async' in the name are synchronous. Reason behind this is that it gives very nice look to blocking API, and promise based non-blocking code is verbose anyway, so one more word is not much of a difference.
Thanks to that the API is also coherent...
```js
// If method has no 'Async' suffix it gives you answer right away.
var data = jetpack.read('file.txt');
console.log(data);
// Want to make that call asnychronous? Just add the word "Async"
// and it will give you promise instead of ready value.
jetpack.readAsync('file.txt')
.then(function (data) {
console.log(data);
});
```
## Every jetpack instance has its internal CWD
You can create many jetpack objects with different internal working directories (which are independent of `process.cwd()`) and work on directories in a little more object-oriented manner.
```js
var src = jetpack.cwd('path/to/source');
var dest = jetpack.cwd('path/to/destination');
src.copy('foo.txt', dest.path('bar.txt'));
```
## JSON is a first class citizen
You can write JavaScript object directly to disk and it will be transformed to JSON automatically.
```js
var obj = { greet: "Hello World!" };
jetpack.write('file.json', obj);
```
Then you can get your object back just by telling read method that it's a JSON.
```js
var obj = jetpack.read('file.json', 'json');
```
## Throws errors at you as the last resort
Everyone who did something with files for sure seen *"ENOENT, no such file or directory"* error. Jetpack tries to recover from that error if possible.
1. For wrte/creation operations, if any of parent directories doesn't exist jetpack will just create them as well.
2. For read/inspect operations, if file or directory doesn't exist `null` is returned instead of throwing.
## This is just a powerful API
All methods play nicely with each other. Here are few examples what it can give you.
**Note:** All examples are synchronous. Unfortunately asynchronous versions of them will be uglier :)
#### Files creation in declarative style
```js
// To create structure...
// (CWD path)
// |- greets
// |- greet.txt
// |- greet.json
// |- greets-i18n
// |- polish.txt
jetpack
.dir('greets')
.file('greet.txt', { content: 'Hello World!' })
.file('greet.json', { content: { greet: 'Hello World!' } })
.cwd('..')
.dir('greets-i18n')
.file('polish.txt', { content: 'Cześć!' });
```
#### Delete all tmp files inside directory tree
```js
jetpack.find('my-dir', {
matching: '*.tmp'
})
.forEach(jetpack.remove);
```
#### Check if two files have the same content
```js
var file1 = jetpack.inspect('file1', { checksum: 'md5' });
var file2 = jetpack.inspect('file2', { checksum: 'md5' });
var areTheSame = (file1.md5 === file2.md5);
```
#### Great for build scripts
```js
var src = jetpack.cwd('path/to/source');
var dest = jetpack.dir('path/to/destination', { empty: true });
src.copy('.', dest.path(), {
matching: ['./vendor/**', '*.html', '*.png', '*.jpg']
});
var config = src.read('config.json', 'json');
config.env = 'production';
dest.write('config.json', config);
```
# <a name="api"></a> API Docs
API methods have blocking and non-blocking equivalents:
```js
// Synchronous call
var data = jetpack.read('file.txt');
console.log(data);
// Asynchronous call
jetpack.readAsync('file.txt')
.then(function (data) {
console.log(data);
});
```
**Methods:**
* [append(path, data, [options])](#append)
* [copy(from, to, [options])](#copy)
* [createReadStream(path, [options])](#create-read-stream)
* [createWriteStream(path, [options])](#create-write-stream)
* [cwd([path...])](#cwd)
* [dir(path, [criteria])](#dir)
* [exists(path)](#exists)
* [file(path, [criteria])](#file)
* [inspect(path, [options])](#inspect)
* [inspectTree(path, [options])](#inspect-tree)
* [list(path, [useInspect])](#list)
* [move(from, to)](#move)
* [path(parts...)](#path)
* [read(path, [returnAs], [options])](#read)
* [remove(path, [options])](#remove)
* [rename(path, newName)](#rename)
* [write(path, data, [options])](#write)
* [append](#append)
* [copy](#copy)
* [createReadStream](#create-read-stream)
* [createWriteStream](#create-write-stream)
* [cwd](#cwd)
* [dir](#dir)
* [exists](#exists)
* [file](#file)
* [find](#find)
* [inspect](#inspect)
* [inspectTree](#inspect-tree)
* [list](#list)
* [move](#move)
* [path](#path)
* [read](#read)
* [remove](#remove)
* [rename](#rename)
* [write](#write)
## <a name="append"></a> append(path, data, [options])
also **appendAsync(path, data, [options])**
asynchronous: **appendAsync(path, data, [options])**

@@ -76,3 +171,3 @@ Appends given data to the end of file. If file (or any parent directory) doesn't exist, creates it (or them).

## <a name="copy"></a> copy(from, to, [options])
also **copyAsync(from, to, [options])**
asynchronous: **copyAsync(from, to, [options])**

@@ -86,4 +181,3 @@ Copies given file or directory (with everything inside).

* `overwrite` (default: `false`) Whether to overwrite destination path if it exists. For directories, source directory is merged with destination directory, so files in destination which are not present in the source, will remain intact.
* `only` (`Array` of globs) will copy **only** items matching any of specified glob patterns [(read more)](#matching-paths).
* `allBut` (`Array` of globs) will copy **everything except** items matching any of specified glob patterns [(read more)](#matching-paths). If `only` was also specified this field is ignored.
* `matching` if defined will actually copy **only** items matching any of specified glob patterns and omit everything else (see examples below).

@@ -98,7 +192,11 @@ **returns:**

// Copies only ".jpg" files from my_dir
jetpack.copy('my_dir', 'somewhere/my_dir', { only: ['*.jpg'] });
// Copies only .md files from my-dir to somewhere/my-dir
jetpack.copy('my-dir', 'somewhere/my-dir', { matching: '*.md' });
// Copies everything except "logs" directory inside my_dir
jetpack.copy('my_dir', 'somewhere/my_dir', { allBut: ['my_dir/logs'] });
// When glob pattern starts with './' it means it is anchored to specified
// directory to copy. Here will be copied only .jpg files from my-dir/images
// and .md files from my-dir/articles
jetpack.copy('my_dir', 'somewhere/my_dir', {
matching: ['./images/**/*.jpg', './articles/**/*.md' ]
});
```

@@ -153,5 +251,5 @@

## <a name="dir"></a> dir(path, [criteria])
also **dirAsync(path, [criteria])**
asynchronous: **dirAsync(path, [criteria])**
Ensures that directory on given path meets given criteria. If any criterium is not met it will be after this call.
Ensures that directory on given path exists and meets given criteria. If any criterium is not met it will be after this call.

@@ -161,4 +259,3 @@ **parameters:**

`criteria` (optional) criteria to be met by the directory. Is an `Object` with possible fields:
* `exists` (default: `true`) whether directory should exist or not. If set to `true` and `path` contains many nested, nonexistent directories all of them will be created.
* `empty` (default: `false`) whether directory should be empty (no other files or directories inside). If set to `true` and directory contains any files or subdirectories all of them will be deleted. If `exists = false` this field is ignored.
* `empty` (default: `false`) whether directory should be empty (no other files or directories inside). If set to `true` and directory contains any files or subdirectories all of them will be deleted.
* `mode` ensures directory has specified mode. If not set and directory already exists, current mode will be preserved. Value could be number (eg. `0700`) or string (eg. `'700'`).

@@ -168,3 +265,2 @@

New CWD context with directory specified in `path` as CWD.
Or `undefined` if `exists` was set to `false`.

@@ -174,9 +270,6 @@ **examples:**

// Creates directory if doesn't exist
jetpack.dir('new_dir');
jetpack.dir('new-dir');
// Makes sure that directory does NOT exist
jetpack.dir('/my_stuff/some_dir', { exists: false });
// Makes sure directory mode is 0700 and that it's empty
jetpack.dir('empty_dir', { empty: true, mode: '700' });
jetpack.dir('empty-dir', { empty: true, mode: '700' });

@@ -186,4 +279,4 @@ // Because dir returns new CWD context pointing to just

jetpack
.dir('main_dir') // creates 'main_dir'
.dir('sub_dir'); // creates 'main_dir/sub_dir'
.dir('main-dir') // creates 'main-dir'
.dir('sub-dir'); // creates 'main-dir/sub-dir'
```

@@ -193,3 +286,3 @@

## <a name="exists"></a> exists(path)
also **existsAsync(path)**
asynchronous: **existsAsync(path)**

@@ -206,5 +299,5 @@ Checks whether something exists on given `path`. This method returns values more specyfic than `true/false` to protect from errors like "I was expecting directory, but it was a file".

## <a name="file"></a> file(path, [criteria])
also **fileAsync(path, [criteria])**
asynchronous: **fileAsync(path, [criteria])**
Ensures that file meets given criteria. If any criterium is not met it will be after this call.
Ensures that file exists and meets given criteria. If any criterium is not met it will be after this call.

@@ -214,6 +307,4 @@ **parameters:**

`criteria` (optional) criteria to be met by the file. Is an `Object` with possible fields:
* `exists` (default: `true`) whether file should exist or not.
* `empty` (default: `false`) whether file should be empty. If `exists = false` this field is ignored.
* `content` (`String`, `Buffer`, `Object` or `Array`) sets file content. If `Object` or `Array` given to this parameter the output will be JSON. If `exists = false`, or `empty = true` this field is ignored.
* `jsonIndent` (defaults to 0) if writing JSON data this tells how many spaces should one indentation have.
* `content` sets file content. Could be `String`, `Buffer`, `Object` or `Array`. If `Object` or `Array` given to this parameter data will be written as JSON.
* `jsonIndent` (defaults to 2) if writing JSON data this tells how many spaces should one indentation have.
* `mode` ensures file has specified mode. If not set and file already exists, current mode will be preserved. Value could be number (eg. `0700`) or string (eg. `'700'`).

@@ -229,5 +320,2 @@

// Ensures file does NOT exist (if exists will be deleted)
jetpack.file('not_something.txt', { exists: false });
// Creates file with mode '777' and content 'Hello World!'

@@ -238,4 +326,31 @@ jetpack.file('hello.txt', { mode: '777', content: 'Hello World!' });

## <a name="find"></a> find(path, searchOptions, [returnAs])
asynchronous: **findAsync(path, searchOptions, [returnAs])**
Finds in directory specified by `path` all files fulfilling `searchOptions`.
**parameters:**
`path` path to start search in (all subdirectories will be searched).
`searchOptions` is an `Object` with possible fields:
* `matching` glob patterns of files you would like to find.
`returnAs` (optional) how the results should be returned. Could be one of:
* `'absolutePath'` (default) returns array of absolute paths.
* `'relativePath'` returns array of relative paths. The paths are relative to `path` you started search in.
* `'inspect'` returns array of objects like you called [inspect](#inspect) on every of those files.
**returns:**
`Array` of found files.
**examples:**
```javascript
// Finds all files or directories which has 2015 in the name
jetpack.find('my-work', { matching: '*2015*' });
// Finds all jpg and png files and gives you back the list of inspect objects
// (like you called jetpack.inspect on every of those paths)
jetpack.find('my-work', { matching: ['*.jpg', '*.png'] }, 'inspect');
```
## <a name="inspect"></a> inspect(path, [options])
also **inspectAsync(path, [options])**
asynchronous: **inspectAsync(path, [options])**

@@ -247,5 +362,6 @@ Inspects given path (replacement for `fs.stat`). Returned object by default contains only very basic, not platform-dependent properties (so you have something e.g. your unit tests can rely on), you can enable more properties through options object.

`options` (optional). Possible values:
* `checksum` if specified will return checksum of inspected file. Possible values are strings `'md5'` or `'sha1'`. If given path is directory this field is ignored.
* `checksum` if specified will return checksum of inspected file. Possible values are strings `'md5'`, `'sha1'` or `'sha256'`. If given path is directory this field is ignored.
* `mode` (default `false`) if set to `true` will add file mode (unix file permissions) value.
* `times` (default `false`) if set to `true` will add atime, mtime and ctime fields (here called `accessTime`, `modifyTime` and `changeTime`).
* `absolutePath` (dafault `false`) if set to `true` will add absolute path to this resource.

@@ -273,3 +389,3 @@ **returns:**

## <a name="inspect-tree"></a> inspectTree(path, [options])
also **inspectTreeAsync(path, [options])**
asynchronous: **inspectTreeAsync(path, [options])**

@@ -281,3 +397,4 @@ Calls [inspect](#inspect) recursively on given path so it creates tree of all directories and sub-directories inside it.

`options` (optional). Possible values:
* `checksum` if specified will also calculate checksum of every item in the tree. Possible values are strings `'md5'` or `'sha1'`. Checksums for directories are calculated as checksum of all children' checksums plus their filenames (see example below).
* `checksum` if specified will also calculate checksum of every item in the tree. Possible values are strings `'md5'`, `'sha1'` or `'sha256'`. Checksums for directories are calculated as checksum of all children' checksums plus their filenames (see example below).
* `relativePath` if set to `true` every tree node will have relative path anchored to root inspected folder.

@@ -292,2 +409,3 @@ **returns:**

size: 123, // this is combined size of all items in this directory
relativePath: '.',
md5: '11c68d9ad988ff4d98768193ab66a646',

@@ -301,2 +419,3 @@ // checksum of this directory was calculated as:

size: 0, // the directory is empty
relativePath: './dir',
md5: null, // can't calculate checksum of empty directory

@@ -308,2 +427,3 @@ children: []

size: 123,
relativePath: './file.txt',
md5: '900150983cd24fb0d6963f7d28e17f72'

@@ -317,3 +437,3 @@ }

## <a name="list"></a> list(path, [useInspect])
also **listAsync(path, [useInspect])**
asynchronous: **listAsync(path, [useInspect])**

@@ -325,3 +445,3 @@ Lists the contents of directory.

`useInspect` (optional) the type of data this call should return. Possible values:
* `false` (default) returns just a list of filenames (the same as `fs.readdir()`)
* `false` (default) returns just a list of filenames (the same as `fs.readdir`)
* `true` performs [inspect](#inspect) on every item in directory, and returns array of those objects

@@ -335,3 +455,3 @@ * `object` if object has been passed to this parameter, it is treated as `options` parameter for [inspect](#inspect) method, and will alter returned inspect objects

## <a name="move"></a> move(from, to)
also **moveAsync(from, to)**
asynchronous: **moveAsync(from, to)**

@@ -366,6 +486,6 @@ Moves given path to new location.

## <a name="read"></a> read(path, [returnAs], [options])
also **readAsync(path, [returnAs], [options])**
## <a name="read"></a> read(path, [returnAs])
asynchronous: **readAsync(path, [returnAs])**
Reads content of file. If file on given path doesn't exist returns `null` instead of throwing `ENOENT` error.
Reads content of file. If file on given path doesn't exist returns `null` instead of throwing error.

@@ -380,5 +500,2 @@ **parameters:**

`options` (optional) is an object with possible fields:
* `safe` if set to `true` the file will be read in ["safe mode"](#safe-mode).
**returns:**

@@ -388,12 +505,9 @@ File content in specified format, or `null` if file doesn't exist.

## <a name="remove"></a> remove(path, [options])
also **removeAsync(path, [options])**
## <a name="remove"></a> remove(path)
asynchronous: **removeAsync(path)**
Deletes given path, no matter what it is (file or directory).
Deletes given path, no matter what it is (file or directory). If path already doesn't exist ends without throwing, so you can use it as 'ensure path doesn't exist'.
**parameters:**
`path` path to file or directory you want to remove.
`options` (optional) additional conditions to removal process. Is an object with possible fields:
* `only` (`Array` of globs) will delete **only** items matching any of specified glob patterns [(read more on that)](#matching-paths).
* `allBut` (`Array` of globs) will delete **everything except** items matching any of specified glob patterns [(read more on that)](#matching-paths). If `only` was also specified this field is ignored.

@@ -410,10 +524,2 @@ **returns:**

jetpack.remove('my_work/important_stuff');
// Will delete any ".log" file, and any folder or file named "temp" inside "my_app",
// but will leave all other files intact.
jetpack.remove('my_app', { only: [ '*.log', 'temp' ] });
// Will delete everything inside "my_app" directory,
// but will leave directory or file "my_app/user_data" intact.
jetpack.remove('my_app', { allBut: [ 'my_app/user_data' ] });
```

@@ -423,3 +529,3 @@

## <a name="rename"></a> rename(path, newName)
also **renameAsync(path, newName)**
asynchronous: **renameAsync(path, newName)**

@@ -437,3 +543,3 @@ Renames given file or directory.

## <a name="write"></a> write(path, data, [options])
also **writeAsync(path, data, [options])**
asynchronous: **writeAsync(path, data, [options])**

@@ -446,101 +552,6 @@ Writes data to file.

`options` (optional) `Object` with possible fields:
* `jsonIndent` (defaults to 0) if writing JSON data this tells how many spaces should one indentation have.
* `safe` if set to `true` the file will be written in ["safe mode"](#safe-mode).
* `atomic` (default `false`) if set to `true` the file will be written using strategy which is much more resistant to data loss. The trick is very simple, [read this to get the concept](http://stackoverflow.com/questions/17047994/transactionally-writing-files-in-node-js).
* `jsonIndent` (defaults to 2) if writing JSON data this tells how many spaces should one indentation have.
**returns:**
Nothing.
# <a name="how-fun"></a> Neat tricks fs-jetpack knows
### Every jetpack instance has its independent, internal CWD
So you can create many jetpack objects and work on directories in a little more object-oriented fashion.
```javascript
var src = jetpack.cwd('path/to/source');
var dest = jetpack.cwd('path/to/destination');
src.copy('foo.txt', dest.path('bar.txt'));
```
### Files creation in declarative style
You can create whole tree of directories and files in declarative style.
```javascript
// Synchronous style
jetpack
.dir('foo')
.file('foo.txt', { content: 'Hello...' })
.file('bar.txt', { content: '...world!' })
.cwd('..')
.dir('bar')
.file('foo.txt', { content: 'Wazup?' });
// Asynchronous style (unfortunately not that pretty)
jetpack
.dirAsync('foo')
.then(function (dir) {
return dir.fileAsync('foo.txt', { content: 'Hello...' });
})
.then(function (dir) {
return dir.fileAsync('bar.txt', { content: '...world!' });
.then(function (dir) {
return dir.cwd('..').dirAsync('bar');
})
.then(function (dir) {
dir.fileAsync('foo.txt', { content: 'Wazup?' });
});
```
### Hides ENOENT from you as much as possible
*"ENOENT, no such file or directory"* is the most annoying error when working with file system, and fs-jetpack does 2 things to save you the hassle:
1. For wrte/creation operations, if any of parent directories doesn't exist, jetpack will just create them as well.
2. For read/inspect operations, if file or directory doesn't exist, `null` is returned instead of throwing.
### <a name="matching-paths"></a> Filtering things to copy/remove with "globs"
[Copy](#copy) and [remove](#remove) have option for blacklisting and whitelisting things inside directory which will be affected by the operation. For instance:
```javascript
// Let's assume we have folder structure:
// foo
// |- a.jpg
// |- b.pdf
// |- c.txt
// |- bar
// |- a.pdf
jetpack.copy('foo', 'foo1', { allBut: ['*.pdf'] });
// Will give us folder:
// foo1
// |- a.jpg
// |- c.txt
// |- bar
jetpack.copy('foo', 'foo2', { only: ['*.pdf'] });
// Will give us folder:
// foo2
// |- b.pdf
// |- bar
// |- a.pdf
```
### <a name="safe-mode"></a> "Safe" file overwriting
It is not fully safe to just overwrite existing file with new content. If your process crashes during this operation you are srewed. The old file content is lost, because you were writing to the same place new stuff, and the new stuff is lost totally or written only partially. Fs-jetpack has built-in "safe mode", which helps you deal with this issue. Under the hood it works as follows...
Let's assume there's already `file.txt` with content `Hello world!` on disk, and we want to update it to `Hello universe!`.
```javascript
jetpack.write('file.txt', 'Hello universe!', { safe: true });
```
Above line will perform tasks as follows:
1. Write `Hello universe!` to `file.txt.__new__` (so we didn't owerwrite the original file).
2. Move `file.txt` (with `Hello world!`) to `file.txt.__bak__`, so it can serve as a backup.
3. Move `file.txt.__new__` to `file.txt`, where we wanted it to be on the first place.
4. Delete `file.txt.__bak__`, because it is no longer needed.
Thanks to that the backup of old data is reachable all the time, until we are 100% sure the new data has been successfuly written to disk.
For this to work, read operation have to be aware of the backup file.
```javascript
jetpack.read('file.txt', { safe: true });
```
Above read will do:
1. Read `file.txt`
2. If step 1 failed, try to read `file.txt.__bak__`.
3. If step 2 failed as well, we are sure there is no such file.
The whole process is performed automatically for you by simply adding `safe: true` to call options of [write](#write) and [read](#read) methods.
"use strict";
describe('append |', function () {
var fse = require('fs-extra');
var pathUtil = require('path');
var helper = require('./helper');
var helper = require('./support/spec_helper');
var jetpack = require('..');
var path = 'file.txt';
beforeEach(helper.beforeEach);
afterEach(helper.afterEach);
it('appends String to file', function (done) {
var preparations = function () {
fse.writeFileSync(path, 'abc');
helper.clearWorkingDir();
fse.writeFileSync('file.txt', 'abc');
};
var expectations = function () {
expect(fse.readFileSync(path, 'utf8')).toBe('abcxyz');
expect('file.txt').toBeFileWithContent('abcxyz');
};
// SYNC
preparations();
jetpack.append(path, 'xyz');
jetpack.append('file.txt', 'xyz');
expectations();
helper.clearWorkingDir();
// ASYNC
preparations();
jetpack.appendAsync(path, 'xyz')
jetpack.appendAsync('file.txt', 'xyz')
.then(function () {

@@ -40,26 +37,25 @@ expectations();

});
it('appends Buffer to file', function (done) {
var preparations = function () {
fse.writeFileSync(path, new Buffer([11]));
helper.clearWorkingDir();
fse.writeFileSync('file.txt', new Buffer([11]));
};
var expectations = function () {
var buf = fse.readFileSync(path);
expect(buf.length).toBe(2);
var buf = fse.readFileSync('file.txt');
expect(buf[0]).toBe(11);
expect(buf[1]).toBe(22);
expect(buf.length).toBe(2);
};
// SYNC
preparations();
jetpack.append(path, new Buffer([22]));
jetpack.append('file.txt', new Buffer([22]));
expectations();
helper.clearWorkingDir();
// ASYNC
preparations();
jetpack.appendAsync(path, new Buffer([22]))
jetpack.appendAsync('file.txt', new Buffer([22]))
.then(function () {

@@ -70,17 +66,21 @@ expectations();

});
it("if file doesn't exist creates it", function (done) {
var preparations = function () {
helper.clearWorkingDir();
};
var expectations = function () {
expect(fse.readFileSync(path, 'utf8')).toBe('xyz');
expect('file.txt').toBeFileWithContent('xyz');
};
// SYNC
jetpack.append(path, 'xyz');
preparations();
jetpack.append('file.txt', 'xyz');
expectations();
helper.clearWorkingDir();
// ASYNC
jetpack.appendAsync(path, 'xyz')
preparations();
jetpack.appendAsync('file.txt', 'xyz')
.then(function () {

@@ -91,19 +91,21 @@ expectations();

});
it("if parent directory doesn't exist creates it as well", function (done) {
var path = 'dir/dir/file.txt';
var preparations = function () {
helper.clearWorkingDir();
};
var expectations = function () {
expect(fse.readFileSync(path, 'utf8')).toBe('xyz');
expect('dir/dir/file.txt').toBeFileWithContent('xyz');
};
// SYNC
jetpack.append(path, 'xyz');
preparations();
jetpack.append('dir/dir/file.txt', 'xyz');
expectations();
helper.clearWorkingDir();
// ASYNC
jetpack.appendAsync(path, 'xyz')
preparations();
jetpack.appendAsync('dir/dir/file.txt', 'xyz')
.then(function () {

@@ -114,46 +116,58 @@ expectations();

});
it("returns undefined", function (done) {
it("respects internal CWD of jetpack instance", function (done) {
var preparations = function () {
helper.clearWorkingDir();
fse.outputFileSync('a/b.txt', 'abc');
};
var expectations = function () {
expect('a/b.txt').toBeFileWithContent('abcxyz');
};
var jetContext = jetpack.cwd('a');
// SYNC
var ret = jetpack.append('file.txt', 'xyz');
expect(ret).toBe(undefined);
helper.clearWorkingDir();
preparations();
jetContext.append('b.txt', 'xyz');
expectations();
// ASYNC
jetpack.appendAsync('file.txt', 'xyz')
.then(function (ret) {
expect(ret).toBe(undefined);
preparations();
jetContext.appendAsync('b.txt', 'xyz')
.then(function () {
expectations();
done();
});
});
if (process.platform !== 'win32') {
describe('*nix specyfic |', function () {
it('sets file mode on created file', function (done) {
var expectations = function () {
expect(fse.statSync('file.txt').mode.toString(8)).toBe('100711');
};
// SYNC
jetpack.append('file.txt', 'abc', { mode: '711' });
describe('*nix specyfic |', function () {
if (process.platform === 'win32') {
return;
}
it('sets file mode on created file', function (done) {
var expectations = function () {
expect('file.txt').toHaveMode('711');
};
// SYNC
jetpack.append('file.txt', 'abc', { mode: '711' });
expectations();
helper.clearWorkingDir();
// AYNC
jetpack.appendAsync('file.txt', 'abc', { mode: '711' })
.then(function () {
expectations();
helper.clearWorkingDir();
// AYNC
jetpack.appendAsync('file.txt', 'abc', { mode: '711' })
.then(function () {
expectations();
done();
});
done();
});
});
}
});
});
"use strict";
describe('copy |', function () {
var fse = require('fs-extra');
var pathUtil = require('path');
var helper = require('./helper');
var helper = require('./support/spec_helper');
var jetpack = require('..');
beforeEach(helper.beforeEach);
afterEach(helper.afterEach);
it("copies a file", function (done) {
var preparations = function () {
helper.clearWorkingDir();
fse.outputFileSync('file.txt', 'abc');
};
var expectations = function () {
expect(fse.readFileSync('file.txt', 'utf8')).toBe('abc');
expect(fse.readFileSync('file_1.txt', 'utf8')).toBe('abc');
expect('file.txt').toBeFileWithContent('abc');
expect('file_1.txt').toBeFileWithContent('abc');
};
// SYNC

@@ -28,5 +29,3 @@ preparations();

expectations();
helper.clearWorkingDir();
// ASYNC

@@ -40,14 +39,15 @@ preparations();

});
it("can copy file to nonexistent directory (will create directory)", function (done) {
var preparations = function () {
helper.clearWorkingDir();
fse.outputFileSync('file.txt', 'abc');
};
var expectations = function () {
expect(fse.readFileSync('file.txt', 'utf8')).toBe('abc');
expect(fse.readFileSync('dir/dir/file.txt', 'utf8')).toBe('abc');
expect('file.txt').toBeFileWithContent('abc');
expect('dir/dir/file.txt').toBeFileWithContent('abc');
};
// SYNC

@@ -57,5 +57,3 @@ preparations();

expectations();
helper.clearWorkingDir();
// ASYNC

@@ -69,13 +67,14 @@ preparations();

});
it("copies empty directory", function (done) {
var preparations = function () {
helper.clearWorkingDir();
fse.mkdirsSync('dir');
};
var expectations = function () {
expect(fse.statSync('a/dir').isDirectory()).toBe(true);
expect('a/dir').toBeDirectory();
};
// SYNC

@@ -85,5 +84,3 @@ preparations();

expectations();
helper.clearWorkingDir();
// ASYNC

@@ -97,6 +94,7 @@ preparations();

});
it("copies a tree of files", function (done) {
var preparations = function () {
helper.clearWorkingDir();
fse.outputFileSync('a/f1.txt', 'abc');

@@ -106,9 +104,9 @@ fse.outputFileSync('a/b/f2.txt', '123');

};
var expectations = function () {
expect(fse.readFileSync('dir/a/f1.txt', 'utf8')).toBe('abc');
expect(fse.existsSync('dir/a/b/c')).toBe(true);
expect(fse.readFileSync('dir/a/b/f2.txt', 'utf8')).toBe('123');
expect('dir/a/f1.txt').toBeFileWithContent('abc');
expect('dir/a/b/c').toBeDirectory();
expect('dir/a/b/f2.txt').toBeFileWithContent('123');
};
// SYNC

@@ -118,5 +116,3 @@ preparations();

expectations();
helper.clearWorkingDir();
// ASYNC

@@ -130,76 +126,100 @@ preparations();

});
it("returns undefined", function (done) {
var preparations = function () {
fse.outputFileSync('a/f1.txt', 'abc');
it("generates nice error if source path doesn't exist", function (done) {
var expectations = function (err) {
expect(err.code).toBe('ENOENT');
expect(err.message).toMatch(/^Path to copy doesn't exist/);
};
// SYNC
preparations();
var ret = jetpack.copy('a', 'dir/a');
expect(ret).toBe(undefined);
helper.clearWorkingDir();
// ASYNC
preparations();
jetpack.copyAsync('a', 'dir/a')
.then(function (ret) {
expect(ret).toBe(undefined);
done();
});
});
it("throws if source path doesn't exist", function (done) {
// SYNC
try {
jetpack.copy('a', 'b', { allBut: ['c'] }); // allBut used because then jetpack code follows more comlicated path
jetpack.copy('a', 'b');
throw "to make sure this code throws"
} catch (err) {
expect(err.code).toBe('ENOENT');
expectations(err);
}
// ASYNC
jetpack.copyAsync('a', 'b', { allBut: ['c'] })
jetpack.copyAsync('a', 'b')
.catch(function (err) {
expect(err.code).toBe('ENOENT');
expectations(err);
done();
});
});
it("respects internal CWD of jetpack instance", function (done) {
var preparations = function () {
helper.clearWorkingDir();
fse.outputFileSync('a/b.txt', 'abc');
};
var expectations = function () {
expect('a/b.txt').toBeFileWithContent('abc');
expect('a/x.txt').toBeFileWithContent('abc');
};
var jetContext = jetpack.cwd('a');
// SYNC
preparations();
jetContext.copy('b.txt', 'x.txt');
expectations();
// ASYNC
preparations();
jetContext.copyAsync('b.txt', 'x.txt')
.then(function () {
expectations();
done();
});
});
describe('overwriting behaviour', function () {
it("does not overwrite by default", function (done) {
fse.outputFileSync('a/file.txt', 'abc');
fse.mkdirsSync('b');
var preparations = function () {
helper.clearWorkingDir();
fse.outputFileSync('a/file.txt', 'abc');
fse.mkdirsSync('b');
};
var expectations = function (err) {
expect(err.code).toBe('EEXIST');
expect(err.message).toMatch(/^Destination path already exists/);
};
// SYNC
preparations();
try {
jetpack.copy('a', 'b');
throw "to make sure this code throws"
throw "to make sure this code throws";
} catch (err) {
expect(err.code).toBe('EEXIST');
expectations(err);
}
// ASYNC
preparations();
jetpack.copyAsync('a', 'b')
.catch(function (err) {
expect(err.code).toBe('EEXIST');
expectations(err);
done();
});
});
it("overwrites if it was specified", function (done) {
var preparations = function () {
helper.clearWorkingDir();
fse.outputFileSync('a/file.txt', 'abc');
fse.mkdirsSync('b');
fse.outputFileSync('b/file.txt', 'xyz');
};
var expectations = function () {
expect(fse.readFileSync('a/file.txt', 'utf8')).toBe('abc');
expect(fse.readFileSync('b/file.txt', 'utf8')).toBe('abc');
expect('a/file.txt').toBeFileWithContent('abc');
expect('b/file.txt').toBeFileWithContent('abc');
};
// SYNC

@@ -209,5 +229,3 @@ preparations();

expectations();
helper.clearWorkingDir();
// ASYNC

@@ -221,35 +239,36 @@ preparations();

});
});
describe('mask matching', function () {
it("copies ONLY", function (done) {
describe('filter what to copy |', function () {
it("copies only paths matching", function (done) {
var preparations = function () {
fse.outputFileSync('dir/f.txt', 'abc');
fse.outputFileSync('dir/f.doc', 'xyz');
fse.outputFileSync('dir/b/f.txt', '123');
fse.mkdirsSync('dir/a/b/c');
fse.mkdirsSync('dir/z');
helper.clearWorkingDir();
fse.outputFileSync('dir/file.txt', '1');
fse.outputFileSync('dir/file.md', 'm1');
fse.outputFileSync('dir/a/file.txt', '2');
fse.outputFileSync('dir/a/file.md', 'm2');
fse.outputFileSync('dir/a/b/file.txt', '3');
fse.outputFileSync('dir/a/b/file.md', 'm3');
};
var expectations = function () {
expect(fse.existsSync('copy/f.txt')).toBe(true);
expect(fse.existsSync('copy/f.doc')).toBe(false);
expect(fse.existsSync('copy/b/f.txt')).toBe(true);
expect(fse.existsSync('copy/a/b/c')).toBe(true);
expect(fse.existsSync('copy/z')).toBe(false);
expect('copy/file.txt').toBeFileWithContent('1');
expect('copy/file.md').not.toExist();
expect('copy/a/file.txt').toBeFileWithContent('2');
expect('copy/a/file.md').not.toExist();
expect('copy/a/b/file.txt').toBeFileWithContent('3');
expect('copy/a/b/file.md').not.toExist();
};
// SYNC
preparations();
jetpack.copy('dir', 'copy', { only: ['*.txt', 'dir/a/b'] });
jetpack.copy('dir', 'copy', { matching: '*.txt' });
expectations();
helper.clearWorkingDir();
// ASYNC
preparations();
jetpack.copyAsync('dir', 'copy', { only: ['*.txt', 'dir/a/b'] })
jetpack.copyAsync('dir', 'copy', { matching: '*.txt' })
.then(function () {

@@ -260,26 +279,26 @@ expectations();

});
it("ONLY works also for files", function (done) {
// this is not especially useful, but logical
// continuation of how this feature works on directories
it("copies only paths matching and anchored to ./", function (done) {
var preparations = function () {
fse.outputFileSync('a', '123');
helper.clearWorkingDir();
fse.outputFileSync('dir/file.txt', '1');
fse.outputFileSync('dir/a/file.txt', '2');
fse.outputFileSync('dir/a/b/file.txt', '3');
};
var expectations = function () {
expect(fse.existsSync('b')).toBe(true);
expect('copy/file.txt').not.toExist();
expect('copy/a/file.txt').toBeFileWithContent('2');
expect('copy/a/b/file.txt').not.toExist();
};
// SYNC
preparations();
jetpack.copy('a', 'b', { only: ['a'] });
jetpack.copy('dir', 'copy', { matching: './a/*.txt' });
expectations();
helper.clearWorkingDir();
// ASYNC
preparations();
jetpack.copyAsync('a', 'b', { only: ['a'] })
jetpack.copyAsync('dir', 'copy', { matching: './a/*.txt' })
.then(function () {

@@ -290,31 +309,22 @@ expectations();

});
it("copies ALLBUT", function (done) {
it("works also if copying single file", function (done) {
var preparations = function () {
fse.outputFileSync('a/f.txt', 'abc');
fse.outputFileSync('a/f.doc', 'xyz');
fse.outputFileSync('a/b/f.txt', '123');
fse.mkdirsSync('a/b/c/d');
fse.mkdirsSync('a/c');
helper.clearWorkingDir();
fse.outputFileSync('a', '1');
};
var expectations = function () {
expect(fse.existsSync('a1/f.txt')).toBe(false);
expect(fse.existsSync('a1/b/f.txt')).toBe(false);
expect(fse.existsSync('a1/b/c')).toBe(false);
expect(fse.existsSync('a1/c')).toBe(true);
expect(fse.existsSync('a1/f.doc')).toBe(true);
expect('b').not.toExist();
};
// SYNC
preparations();
jetpack.copy('a', 'a1', { allBut: ['*.txt', 'a/b/c'] });
jetpack.copy('a', 'b', { matching: 'x' });
expectations();
helper.clearWorkingDir();
// ASYNC
preparations();
jetpack.copyAsync('a', 'a1', { allBut: ['*.txt', 'a/b/c'] })
jetpack.copyAsync('a', 'b', { matching: 'x' })
.then(function () {

@@ -325,26 +335,22 @@ expectations();

});
it("ALLBUT works also for files", function () {
// this is not especially useful, but logical
// continuation of how this feature works on directories
it('can copy empty directory', function (done) {
var preparations = function () {
fse.outputFileSync('a', '123');
helper.clearWorkingDir();
fse.mkdirsSync('dir/a/b');
};
var expectations = function () {
expect(fse.existsSync('b')).toBe(false);
expect('copy/a/b').toBeDirectory();
};
// SYNC
preparations();
jetpack.copy('a', 'b', { allBut: ['a'] });
jetpack.copy('dir', 'copy', { matching: 'b' });
expectations();
helper.clearWorkingDir();
// ASYNC
preparations();
jetpack.copyAsync('a', 'b', { allBut: ['a'] })
jetpack.copyAsync('dir', 'copy', { matching: 'b' })
.then(function () {

@@ -355,25 +361,33 @@ expectations();

});
it("ONLY takes precedence over ALLBUT", function () {
});
describe('*nix specyfic |', function () {
if (process.platform === 'win32') {
return;
}
it('copies also file permissions', function (done) {
var preparations = function () {
fse.outputFileSync('dir/a.txt', 'abc');
fse.outputFileSync('dir/b.txt', 'abc');
helper.clearWorkingDir();
fse.outputFileSync('a/b/c.txt', 'abc');
fse.chmodSync('a/b', '700');
fse.chmodSync('a/b/c.txt', '711');
};
var expectations = function () {
expect(fse.existsSync('copy/a.txt')).toBe(true);
expect(fse.existsSync('copy/b.txt')).toBe(false);
expect('x/b').toHaveMode('700');
expect('x/b/c.txt').toHaveMode('711');
};
// SYNC
preparations();
jetpack.copy('dir', 'copy', { only: ['a.*'], allBut: ['b.*'] });
jetpack.copy('a', 'x');
expectations();
helper.clearWorkingDir();
// ASYNC
// AYNC
preparations();
jetpack.copyAsync('dir', 'copy', { only: ['a.*'], allBut: ['b.*'] })
jetpack.copyAsync('a', 'x')
.then(function () {

@@ -384,42 +398,5 @@ expectations();

});
});
if (process.platform !== 'win32') {
describe('*nix specyfic |', function () {
it('copies also file permissions', function (done) {
var preparations = function () {
fse.outputFileSync('a/b/c.txt', 'abc');
fse.chmodSync('a/b', '700');
fse.chmodSync('a/b/c.txt', '711');
};
var expectations = function () {
expect(fse.statSync('a1/b').mode.toString(8)).toBe('40700');
expect(fse.statSync('a1/b/c.txt').mode.toString(8)).toBe('100711');
};
// SYNC
preparations();
jetpack.copy('a', 'a1');
expectations();
helper.clearWorkingDir();
// AYNC
preparations();
jetpack.copyAsync('a', 'a1')
.then(function () {
expectations();
done();
});
});
});
}
});
"use strict";
describe('cwd', function () {
var pathUtil = require('path');
var jetpack = require('..');
it('returns the same path as process.cwd for main instance of jetpack', function () {
expect(jetpack.cwd()).toBe(process.cwd());
});
it('can create new context with different cwd', function () {
var jetCwd = jetpack.cwd('/'); // absolute path
expect(jetCwd.cwd()).toBe(pathUtil.resolve(process.cwd(), '/'));
jetCwd = jetpack.cwd('../..'); // relative path
expect(jetCwd.cwd()).toBe(pathUtil.resolve(process.cwd(), '../..'));
expect(jetpack.cwd()).toBe(process.cwd()); // cwd of main lib should be intact
});
it('cwd contexts can be created recursively', function () {
var jetCwd1, jetCwd2;
jetCwd1 = jetpack.cwd('..');
expect(jetCwd1.cwd()).toBe(pathUtil.resolve(process.cwd(), '..'));
jetCwd2 = jetCwd1.cwd('..');
expect(jetCwd2.cwd()).toBe(pathUtil.resolve(process.cwd(), '../..'));
});
it('cwd can join path parts', function () {

@@ -36,3 +36,3 @@ var jetCwd = jetpack.cwd('a', 'b', 'c');

});
});
"use strict";
describe('dir |', function () {
var fse = require('fs-extra');
var pathUtil = require('path');
var helper = require('./helper');
var helper = require('./support/spec_helper');
var jetpack = require('..');
beforeEach(helper.beforeEach);
afterEach(helper.afterEach);
describe('ensure dir exists |', function () {
it("creates dir if it doesn't exist", function (done) {
var expectations = function (jetpackContext) {
expect(fse.existsSync('x')).toBe(true);
var preparations = function () {
helper.clearWorkingDir();
};
var expectations = function () {
expect('x').toBeDirectory();
};
// SYNC
var jetpackContext = jetpack.dir('x');
expectations(jetpackContext);
helper.clearWorkingDir();
preparations();
jetpack.dir('x');
expectations();
//ASYNC
preparations();
jetpack.dirAsync('x')
.then(function (jetpackContext) {
expectations(jetpackContext);
.then(function () {
expectations();
done();
});
});
it('does nothing if dir already exists', function (done) {
var preparations = function () {
helper.clearWorkingDir();
fse.mkdirsSync('x');
};
var expectations = function () {
expect(fse.existsSync('x')).toBe(true);
expect('x').toBeDirectory();
};
// SYNC

@@ -49,5 +54,3 @@ preparations();

expectations();
helper.clearWorkingDir();
//ASYNC

@@ -61,67 +64,21 @@ preparations();

});
it('creates nested directories if necessary', function (done) {
var path = 'a/b/c';
var expectations = function () {
expect(fse.existsSync(path)).toBe(true);
};
// SYNC
jetpack.dir(path);
expectations();
helper.clearWorkingDir();
// ASYNC
jetpack.dirAsync(path)
.then(function () {
expectations();
done();
});
});
});
describe('ensure dir does not exist |', function () {
it("dir exists and it shouldn't", function (done) {
var preparations = function () {
fse.mkdirsSync('x');
helper.clearWorkingDir();
};
var expectations = function (jetpackContext) {
expect(fse.existsSync('x')).toBe(false);
var expectations = function () {
expect('a/b/c').toBeDirectory();
};
// SYNC
preparations();
var jetpackContext = jetpack.dir('x', { exists: false });
expectations(jetpackContext);
helper.clearWorkingDir();
jetpack.dir('a/b/c');
expectations();
// ASYNC
preparations();
jetpack.dirAsync('x', { exists: false })
.then(function (jetpackContext) {
expectations(jetpackContext);
done();
});
});
it("dir already doesn't exist", function (done) {
var expectations = function () {
expect(fse.existsSync('x')).toBe(false);
};
// SYNC
jetpack.dir('x', { exists: false });
expectations();
// ASYNC
jetpack.dirAsync('x', { exists: false })
jetpack.dirAsync('a/b/c')
.then(function () {

@@ -132,17 +89,18 @@ expectations();

});
});
describe('ensures dir empty |', function () {
it('not bothers about emptiness if not specified', function (done) {
var preparations = function () {
helper.clearWorkingDir();
fse.mkdirsSync('a/b');
};
var expectations = function () {
expect(fse.existsSync('a/b')).toBe(true);
expect('a/b').toExist();
};
// SYNC

@@ -152,5 +110,3 @@ preparations();

expectations();
helper.clearWorkingDir();
// ASYNC

@@ -164,13 +120,15 @@ preparations();

});
it('makes dir empty', function (done) {
var preparations = function () {
fse.mkdirsSync('a/b');
helper.clearWorkingDir();
fse.outputFileSync('a/b/file.txt', 'abc');
};
var expectations = function () {
expect(fse.existsSync('a/b')).toBe(false);
expect('a/b/file.txt').not.toExist();
expect('a').toExist();
};
// SYNC

@@ -180,5 +138,3 @@ preparations();

expectations();
helper.clearWorkingDir();
// ASYNC

@@ -192,51 +148,50 @@ preparations();

});
});
it('EXISTS=false takes precendence over EMPTY', function (done) {
it('if given path is file, deletes it and places dir instead', function (done) {
var preparations = function () {
fse.mkdirsSync('a/b');
helper.clearWorkingDir();
fse.outputFileSync('a', 'abc');
};
var expectations = function () {
expect(fse.existsSync('a')).toBe(false);
expect('a').toBeDirectory();
};
// SYNC
preparations();
jetpack.dir('a', { exists: false, empty: true });
jetpack.dir('a');
expectations();
helper.clearWorkingDir();
// ASYNC
preparations();
jetpack.dirAsync('a', { exists: false, empty: true })
jetpack.dirAsync('a')
.then(function () {
expectations();
done();
})
});
});
it('if given path is file, deletes it and places dir instead', function (done) {
it("respects internal CWD of jetpack instance", function (done) {
var preparations = function () {
fse.outputFileSync('a', 'abc');
helper.clearWorkingDir();
};
var expectations = function () {
expect(fse.statSync('a').isDirectory()).toBe(true);
expect('a/b').toBeDirectory();
};
var jetContext = jetpack.cwd('a');
// SYNC
preparations();
jetpack.dir('a');
jetContext.dir('b');
expectations();
helper.clearWorkingDir();
// ASYNC
preparations();
jetpack.dirAsync('a')
jetContext.dirAsync('b')
.then(function () {

@@ -247,175 +202,183 @@ expectations();

});
describe("returns", function () {
it("returns jetack instance pointing on this directory if EXISTS=true", function (done) {
it("returns jetack instance pointing on this directory", function (done) {
var preparations = function () {
helper.clearWorkingDir();
};
var expectations = function (ret) {
expect(ret.cwd()).toBe(pathUtil.resolve('a'));
};
// SYNC
preparations();
var ret = jetpack.dir('a');
expectations(ret);
// ASYNC
preparations();
jetpack.dirAsync('a')
.then(function (ret) {
expectations(ret);
done();
});
});
describe('windows specyfic |', function () {
if (process.platform !== 'win32') {
return;
}
it('specyfying mode have no effect, and throws no error', function (done) {
var preparations = function () {
helper.clearWorkingDir();
};
var expectations = function (ret) {
expect(ret.cwd()).toBe(pathUtil.resolve('a'));
expect('x').toBeDirectory();
};
// SYNC
var ret = jetpack.dir('a');
expectations(ret);
helper.clearWorkingDir();
preparations();
jetpack.dir('x', { mode: '511' });
expectations();
// ASYNC
jetpack.dirAsync('a')
.then(function (ret) {
expectations(ret);
preparations();
jetpack.dirAsync('x', { mode: '511' })
.then(function () {
expectations();
done();
});
});
it("returns undefined if EXISTS=false", function (done) {
var expectations = function (ret) {
expect(ret).toBe(undefined);
});
describe('*nix specyfic |', function () {
if (process.platform === 'win32') {
return;
}
// Tests assume umask is not greater than 022
it('sets mode to newly created directory', function (done) {
var preparations = function () {
helper.clearWorkingDir();
};
var expectations = function () {
expect('a').toHaveMode('511');
};
// SYNC
var ret = jetpack.dir('a', { exists: false });
expectations(ret);
helper.clearWorkingDir();
// mode as string
preparations();
jetpack.dir('a', { mode: '511' });
expectations();
// mode as number
preparations();
jetpack.dir('a', { mode: parseInt('511', 8) });
expectations();
// ASYNC
jetpack.dirAsync('a', { exists: false })
.then(function (ret) {
expectations(ret);
// mode as string
preparations();
jetpack.dirAsync('a', { mode: '511' })
.then(function () {
expectations();
// mode as number
preparations();
return jetpack.dirAsync('a', { mode: parseInt('511', 8) });
})
.then(function () {
expectations();
done();
});
});
});
if (process.platform === 'win32') {
describe('windows specyfic |', function () {
it('specyfying mode have no effect, and throws no error', function (done) {
// SYNC
jetpack.dir('x', { mode: '511' });
it('sets that mode to every created directory', function (done) {
var preparations = function () {
helper.clearWorkingDir();
// ASYNC
jetpack.dirAsync('x', { mode: '511' })
.then(function () {
done();
});
};
var expectations = function () {
expect('a').toHaveMode('711');
expect('a/b').toHaveMode('711');
};
// SYNC
preparations();
jetpack.dir('a/b', { mode: '711' });
expectations();
// ASYNC
preparations();
jetpack.dirAsync('a/b', { mode: '711' })
.then(function () {
expectations();
done();
});
});
} else {
describe('*nix specyfic |', function () {
// tests assume umask is not greater than 022
it('sets mode to newly created directory', function (done) {
var expectations = function () {
expect(fse.statSync('a').mode.toString(8)).toBe('40511');
};
// SYNC
// mode as string
jetpack.dir('a', { mode: '511' });
expectations();
it('changes mode of existing directory to desired', function (done) {
var preparations = function () {
helper.clearWorkingDir();
// mode as number
jetpack.dir('a', { mode: parseInt('511', 8) });
fse.mkdirSync('a', '777');
};
var expectations = function () {
expect('a').toHaveMode('511');
};
// SYNC
preparations();
jetpack.dir('a', { mode: '511' });
expectations();
// ASYNC
preparations();
jetpack.dirAsync('a', { mode: '511' })
.then(function () {
expectations();
helper.clearWorkingDir();
// ASYNC
// mode as string
jetpack.dirAsync('a', { mode: '511' })
.then(function () {
expectations();
helper.clearWorkingDir();
// mode as number
return jetpack.dirAsync('a', { mode: parseInt('511', 8) });
})
.then(function () {
expectations();
done();
});
done();
});
it('sets that mode to every created directory', function (done) {
var expectations = function () {
expect(fse.statSync('a').mode.toString(8)).toBe('40711');
expect(fse.statSync('a/b').mode.toString(8)).toBe('40711');
};
// SYNC
jetpack.dir('a/b', { mode: '711' });
expectations();
});
it('leaves mode of directory intact if this option was not specified', function (done) {
var preparations = function () {
helper.clearWorkingDir();
// ASYNC
jetpack.dirAsync('a/b', { mode: '711' })
.then(function () {
expectations();
done();
});
});
it('changes mode of existing directory to desired', function (done) {
var preparations = function () {
fse.mkdirSync('a', '777');
};
var expectations = function () {
expect(fse.statSync('a').mode.toString(8)).toBe('40511');
};
// SYNC
preparations();
jetpack.dir('a', { mode: '511' });
expectations();
helper.clearWorkingDir();
// ASYNC
preparations();
jetpack.dirAsync('a', { mode: '511' })
.then(function () {
expectations();
done();
});
});
it('leaves mode of directory intact if not specified', function (done) {
var expectations = function () {
expect(fse.statSync('a').mode.toString(8)).toBe('40700');
};
fse.mkdirSync('a', '700');
// SYNC
jetpack.dir('a');
};
var expectations = function () {
expect('a').toHaveMode('700');
};
// SYNC
preparations();
jetpack.dir('a');
expectations();
// ASYNC
preparations();
jetpack.dirAsync('a')
.then(function () {
expectations();
// ASYNC
jetpack.dirAsync('x')
.then(function () {
expectations();
done();
});
done();
});
});
}
});
});

@@ -7,3 +7,3 @@ "use strict";

var pathUtil = require('path');
var helper = require('./helper');
var helper = require('./support/spec_helper');
var jetpack = require('..');

@@ -18,3 +18,3 @@

expect(exists).toBe(false);
// ASYNC

@@ -30,7 +30,7 @@ jetpack.existsAsync('file.txt')

fse.mkdirsSync('a');
// SYNC
var exists = jetpack.exists('a');
expect(exists).toBe('dir');
// ASYNC

@@ -46,7 +46,7 @@ jetpack.existsAsync('a')

fse.outputFileSync('text.txt', 'abc');
// SYNC
var exists = jetpack.exists('text.txt');
expect(exists).toBe('file');
// ASYNC

@@ -60,2 +60,19 @@ jetpack.existsAsync('text.txt')

it("respects internal CWD of jetpack instance", function (done) {
fse.outputFileSync('a/text.txt', 'abc');
var jetContext = jetpack.cwd('a');
// SYNC
var exists = jetContext.exists('text.txt');
expect(exists).toBe('file');
// ASYNC
jetContext.existsAsync('text.txt')
.then(function (exists) {
expect(exists).toBe('file');
done();
});
});
});
"use strict";
describe('file |', function () {
var fse = require('fs-extra');
var pathUtil = require('path');
var helper = require('./helper');
var helper = require('./support/spec_helper');
var jetpack = require('..');
beforeEach(helper.beforeEach);
afterEach(helper.afterEach);
describe('ensure file exists |', function () {
it("file doesn't exists", function (done) {
it("file doesn't exist before call", function (done) {
var prepartions = function () {
helper.clearWorkingDir();
};
var expectations = function () {
expect(fse.existsSync('file.txt')).toBe(true);
expect('file.txt').toBeFileWithContent('');
};
// SYNC
prepartions();
jetpack.file('file.txt');
expectations();
helper.clearWorkingDir();
// ASYNC
prepartions();
jetpack.fileAsync('file.txt')

@@ -34,13 +38,14 @@ .then(function () {

});
it("file already exists", function (done) {
var preparations = function () {
helper.clearWorkingDir();
fse.outputFileSync('file.txt', 'abc');
};
var expectations = function () {
expect(fse.existsSync('file.txt')).toBe(true);
expect('file.txt').toBeFileWithContent('abc');
};
// SYNC

@@ -50,5 +55,3 @@ preparations();

expectations();
helper.clearWorkingDir();
// ASYNC

@@ -62,43 +65,25 @@ preparations();

});
});
describe('ensure file does not exist |', function () {
it('file already does not exist', function (done) {
var expectations = function () {
expect(fse.existsSync('file.txt')).toBe(false);
};
// SYNC
jetpack.file('file.txt', { exists: false });
expectations();
// ASYNC
jetpack.fileAsync('file.txt', { exists: false })
.then(function () {
expectations();
done();
});
});
it('file already does not exist', function (done) {
describe('ensures file content |', function () {
it("from string", function (done) {
var preparations = function () {
fse.outputFileSync('file.txt', 'abc');
helper.clearWorkingDir();
};
var expectations = function () {
expect(fse.existsSync('file.txt')).toBe(false);
expect('file.txt').toBeFileWithContent('ąbć');
};
// SYNC
preparations();
jetpack.file('file.txt', { exists: false });
jetpack.file('file.txt', { content: 'ąbć' });
expectations();
// ASYNC
preparations();
jetpack.fileAsync('file.txt', { exists: false })
jetpack.fileAsync('file.txt', { content: 'ąbć' })
.then(function () {

@@ -109,90 +94,23 @@ expectations();

});
});
describe('ensures file empty |', function () {
it('not bothers about file emptiness if not explicitly specified', function (done) {
var expectations = function () {
expect(fse.readFileSync('file.txt', 'utf8')).toBe('abc');
};
fse.outputFileSync('file.txt', 'abc');
// SYNC
jetpack.file('file.txt');
expectations();
// ASYNC
jetpack.fileAsync('file.txt')
.then(function () {
expectations();
done();
});
});
it('makes sure file is empty if specified', function (done) {
it("from buffer", function (done) {
var preparations = function () {
fse.outputFileSync('file.txt', 'abc');
helper.clearWorkingDir();
};
var expectations = function () {
expect(fse.readFileSync('file.txt', 'utf8')).toBe('');
};
// SYNC
preparations();
jetpack.file('file.txt', { empty: true });
expectations();
// ASYNC
preparations();
jetpack.fileAsync('file.txt', { empty: true })
.then(function () {
expectations();
done();
});
});
});
describe('ensures file content |', function () {
it("from string", function (done) {
var expectations = function () {
expect(fse.readFileSync('file.txt', 'utf8')).toBe('ąbć');
};
// SYNC
jetpack.file('file.txt', { content: 'ąbć' });
expectations();
helper.clearWorkingDir();
// ASYNC
jetpack.fileAsync('file.txt', { content: 'ąbć' })
.then(function () {
expectations();
done();
});
});
it("from buffer", function (done) {
var expectations = function () {
var buf = fse.readFileSync('file');
expect(buf[0]).toBe(11);
expect(buf[1]).toBe(22);
expect(buf.length).toBe(2);
};
// SYNC
preparations();
jetpack.file('file', { content: new Buffer([11, 22]) });
expectations();
helper.clearWorkingDir();
// ASYNC
preparations();
jetpack.fileAsync('file', { content: new Buffer([11, 22]) })

@@ -204,7 +122,11 @@ .then(function () {

});
it("from object (json)", function (done) {
var obj = { a: "abc", b: 123 };
var preparations = function () {
helper.clearWorkingDir();
};
var expectations = function () {

@@ -214,10 +136,10 @@ var data = JSON.parse(fse.readFileSync('file.txt', 'utf8'));

};
// SYNC
preparations();
jetpack.file('file.txt', { content: obj });
expectations();
helper.clearWorkingDir();
// ASYNC
preparations();
jetpack.fileAsync('file.txt', { content: obj })

@@ -229,26 +151,36 @@ .then(function () {

});
it('written JSON data can be indented', function (done) {
var obj = { a: "abc", b: 123 };
var preparations = function () {
helper.clearWorkingDir();
};
var expectations = function (content) {
var sizeA = fse.statSync('a.json').size;
var sizeB = fse.statSync('b.json').size;
var sizeC = fse.statSync('c.json').size;
expect(sizeB).toBeGreaterThan(sizeA);
expect(sizeC).toBeGreaterThan(sizeB);
};
// SYNC
jetpack.file('a.json', { content: obj });
jetpack.file('b.json', { content: obj, jsonIndent: 4 });
preparations();
jetpack.file('a.json', { content: obj, jsonIndent: 0 });
jetpack.file('b.json', { content: obj }); // Default indent = 2
jetpack.file('c.json', { content: obj, jsonIndent: 4 });
expectations();
helper.clearWorkingDir();
// ASYNC
jetpack.fileAsync('a.json', { content: obj })
preparations();
jetpack.fileAsync('a.json', { content: obj, jsonIndent: 0 })
.then(function () {
return jetpack.fileAsync('b.json', { content: obj, jsonIndent: 4 });
return jetpack.fileAsync('b.json', { content: obj }); // Default indent = 2
})
.then(function () {
return jetpack.fileAsync('c.json', { content: obj, jsonIndent: 4 });
})
.then(function () {
expectations();

@@ -258,13 +190,14 @@ done();

});
it("replaces content of already existing file", function (done) {
var preparations = function () {
helper.clearWorkingDir();
fse.writeFileSync('file.txt', 'abc');
};
var expectations = function () {
expect(fse.readFileSync('file.txt', 'utf8')).toBe('123');
expect('file.txt').toBeFileWithContent('123');
};
// SYNC

@@ -274,5 +207,3 @@ preparations();

expectations();
helper.clearWorkingDir();
// ASYNC

@@ -286,16 +217,17 @@ preparations();

});
});
it('if given path is directory, should delete it and place file instead', function (done) {
var preparations = function () {
// create nested directories to be sure we can delete non-empty dir
helper.clearWorkingDir();
// Create nested directories to be sure we can delete non-empty dir.
fse.outputFileSync('a/b/c.txt', 'abc');
};
var expectations = function () {
expect(fse.statSync('a').isFile()).toBe(true);
expect('a').toBeFileWithContent('');
};
// SYNC

@@ -305,5 +237,3 @@ preparations();

expectations();
helper.clearWorkingDir();
// ASYNC

@@ -317,16 +247,20 @@ preparations();

});
it("if directory for file doesn't exist creates it too", function (done) {
var preparations = function () {
helper.clearWorkingDir();
};
var expectations = function () {
expect(fse.existsSync('a/b/c.txt')).toBe(true);
expect('a/b/c.txt').toBeFileWithContent('');
};
// SYNC
preparations();
jetpack.file('a/b/c.txt');
expectations();
helper.clearWorkingDir();
// ASYNC
preparations();
jetpack.fileAsync('a/b/c.txt')

@@ -338,8 +272,8 @@ .then(function () {

});
it('returns jetpack object', function (done) {
it('returns currently used jetpack instance', function (done) {
// SYNC
var jetpackContext = jetpack.file('file.txt');
expect(jetpackContext).toBe(jetpack);
// ASYNC

@@ -352,51 +286,115 @@ jetpack.fileAsync('file.txt')

});
describe('parameters importance |', function () {
it('EXISTS=false takes precedence over EMPTY and CONTENT', function (done) {
it("respects internal CWD of jetpack instance", function (done) {
var preparations = function () {
helper.clearWorkingDir();
};
var expectations = function () {
expect('a/b.txt').toBeFileWithContent('');
};
var jetContext = jetpack.cwd('a');
// SYNC
preparations();
jetContext.file('b.txt');
expectations();
// ASYNC
preparations();
jetContext.fileAsync('b.txt')
.then(function () {
expectations();
done();
});
});
describe('windows specyfic |', function () {
if (process.platform !== 'win32') {
return;
}
it('specyfying mode should have no effect, and throw no error', function (done) {
// SYNC
jetpack.file('file.txt', { mode: '511' });
helper.clearWorkingDir();
// ASYNC
jetpack.fileAsync('file.txt', { mode: '511' })
.then(function () {
done();
});
});
});
describe('*nix specyfic |', function () {
if (process.platform === 'win32') {
return;
}
// Tests assume umask is not greater than 022
it('sets mode of newly created file', function (done) {
var preparations = function () {
fse.writeFileSync('file.txt', 'abc');
helper.clearWorkingDir();
};
var expectations = function () {
expect(fse.existsSync('file.txt')).toBe(false);
expect('file.txt').toHaveMode('511');
};
// SYNC
// mode as string
preparations();
jetpack.file('file.txt', { exists: false, empty: true, content: '123' });
jetpack.file('file.txt', { mode: '511' });
expectations();
helper.clearWorkingDir();
// ASYNC
// mode as number
preparations();
jetpack.fileAsync('file.txt', { exists: false, empty: true, content: '123' })
jetpack.file('file.txt', { mode: parseInt('511', 8) });
expectations();
// AYNC
// mode as string
preparations();
jetpack.fileAsync('file.txt', { mode: '511' })
.then(function () {
expectations();
// mode as number
preparations();
return jetpack.fileAsync('file.txt', { mode: parseInt('511', 8) });
})
.then(function () {
expectations();
done();
});
});
it('EMPTY=true takes precedence over CONTENT', function (done) {
it("changes mode of existing file if doesn't match", function (done) {
var preparations = function () {
fse.writeFileSync('file.txt', 'abc');
helper.clearWorkingDir();
fse.writeFileSync('file.txt', 'abc', { mode: '700' });
};
var expectations = function () {
expect(fse.readFileSync('file.txt', 'utf8')).toBe('');
expect('file.txt').toHaveMode('511');
};
// SYNC
preparations();
jetpack.file('file.txt', { empty: true, content: '123' });
jetpack.file('file.txt', { mode: '511' });
expectations();
helper.clearWorkingDir();
// ASYNC
preparations();
jetpack.fileAsync('file.txt', { empty: true, content: '123' })
jetpack.fileAsync('file.txt', { mode: '511' })
.then(function () {

@@ -407,135 +405,51 @@ expectations();

});
});
if (process.platform === 'win32') {
describe('windows specyfic |', function () {
it('specyfying mode should have no effect, and throw no error', function (done) {
// SYNC
jetpack.file('file.txt', { mode: '511' });
helper.clearWorkingDir();
// ASYNC
jetpack.fileAsync('file.txt', { mode: '511' })
.then(function () {
done();
});
});
});
} else {
describe('*nix specyfic |', function () {
// tests assume umask is not greater than 022
it('sets mode of created file', function (done) {
var expectations = function () {
expect(fse.statSync('file.txt').mode.toString(8)).toBe('100511');
};
// SYNC
// mode as string
jetpack.file('file.txt', { mode: '511' });
expectations();
helper.clearWorkingDir();
// mode as number
jetpack.file('file.txt', { mode: parseInt('511', 8) });
expectations();
helper.clearWorkingDir();
// AYNC
// mode as string
jetpack.fileAsync('file.txt', { mode: '511' })
.then(function () {
expectations();
helper.clearWorkingDir();
// mode as number
return jetpack.fileAsync('file.txt', { mode: parseInt('511', 8) });
})
.then(function () {
expectations();
done();
});
});
it("changes mode of existing file if doesn't match", function (done) {
var preparations = function () {
fse.writeFileSync('file.txt', 'abc', { mode: '700' });
};
var expectations = function () {
expect(fse.statSync('file.txt').mode.toString(8)).toBe('100511');
};
// SYNC
preparations();
jetpack.file('file.txt', { mode: '511' });
expectations();
helper.clearWorkingDir();
// ASYNC
preparations();
jetpack.fileAsync('file.txt', { mode: '511' })
.then(function () {
expectations();
done();
});
});
it('leaves mode of file intact if not explicitly specified', function (done) {
var expectations = function () {
expect(fse.statSync('file.txt').mode.toString(8)).toBe('100700');
};
it('leaves mode of file intact if not explicitly specified', function (done) {
var preparations = function () {
fse.writeFileSync('file.txt', 'abc', { mode: '700' });
// SYNC
// ensure exists
jetpack.file('file.txt');
};
var expectations = function () {
expect('file.txt').toHaveMode('700');
};
preparations();
// SYNC
// ensure exists
jetpack.file('file.txt');
expectations();
// make file empty
jetpack.file('file.txt', { empty: true });
expectations();
// set file content
jetpack.file('file.txt', { content: '123' });
expectations();
// AYNC
// ensure exists
jetpack.fileAsync('file.txt')
.then(function () {
expectations();
// make file empty
jetpack.file('file.txt', { empty: true });
return jetpack.fileAsync('file.txt', { empty: true });
})
.then(function () {
expectations();
// set file content
jetpack.file('file.txt', { content: '123' });
return jetpack.fileAsync('file.txt', { content: '123' });
})
.then(function () {
expectations();
// AYNC
// ensure exists
jetpack.fileAsync('file.txt')
.then(function () {
expectations();
// make file empty
return jetpack.fileAsync('file.txt', { empty: true });
})
.then(function () {
expectations();
// set file content
return jetpack.fileAsync('file.txt', { content: '123' });
})
.then(function () {
expectations();
done();
});
done();
});
});
}
});
});

@@ -7,3 +7,3 @@ "use strict";

var pathUtil = require('path');
var helper = require('./helper');
var helper = require('./support/spec_helper');
var jetpack = require('..');

@@ -15,12 +15,13 @@

it("moves file", function (done) {
var preparations = function () {
helper.clearWorkingDir();
fse.outputFileSync('a/b.txt', 'abc');
};
var expectations = function () {
expect(fse.existsSync('a/b.txt')).toBe(false);
expect(fse.readFileSync('c.txt', 'utf8')).toBe('abc');
expect('a/b.txt').not.toExist();
expect('c.txt').toBeFileWithContent('abc');
};
// SYNC

@@ -30,5 +31,3 @@ preparations();

expectations();
helper.clearWorkingDir();
// ASYNC

@@ -42,15 +41,16 @@ preparations();

});
it("moves directory", function (done) {
var preparations = function () {
helper.clearWorkingDir();
fse.outputFileSync('a/b/c.txt', 'abc');
fse.mkdirsSync('x');
};
var expectations = function () {
expect(fse.existsSync('a')).toBe(false);
expect(fse.readFileSync('x/y/b/c.txt', 'utf8')).toBe('abc');
expect('a').not.toExist();
expect('x/y/b/c.txt').toBeFileWithContent('abc');
};
// SYNC

@@ -60,5 +60,3 @@ preparations();

expectations();
helper.clearWorkingDir();
// ASYNC

@@ -72,21 +70,25 @@ preparations();

});
it("returns undefined", function (done) {
it("creates nonexistent directories for destination path", function (done) {
var preparations = function () {
fse.outputFileSync('file.txt', 'abc');
helper.clearWorkingDir();
fse.outputFileSync('a.txt', 'abc');
};
var expectations = function () {
expect('a.txt').not.toExist();
expect('a/b/z.txt').toBeFileWithContent('abc');
};
// SYNC
preparations();
var ret = jetpack.move('file.txt', 'fiole.txt');
expect(ret).toBe(undefined);
helper.clearWorkingDir();
jetpack.move('a.txt', 'a/b/z.txt');
expectations();
// ASYNC
preparations();
jetpack.moveAsync('file.txt', 'fiole.txt')
.then(function (ret) {
expect(ret).toBe(undefined);
jetpack.moveAsync('a.txt', 'a/b/z.txt')
.then(function () {
expectations();
done();

@@ -96,2 +98,53 @@ });

it("generates nice error when source path doesn't exist", function (done) {
var expectations = function (err) {
expect(err.code).toBe('ENOENT');
expect(err.message).toMatch(/^Path to move doesn't exist/);
};
// SYNC
try {
jetpack.move('a', 'b');
throw "to make sure this code throws"
} catch (err) {
expectations(err);
}
// ASYNC
jetpack.moveAsync('a', 'b')
.catch(function (err) {
expectations(err);
done();
});
});
it("respects internal CWD of jetpack instance", function (done) {
var preparations = function () {
helper.clearWorkingDir();
fse.outputFileSync('a/b.txt', 'abc');
};
var expectations = function () {
expect('a/b.txt').not.toExist();
expect('a/x.txt').toBeFileWithContent('abc');
};
var jetContext = jetpack.cwd('a');
// SYNC
preparations();
jetContext.move('b.txt', 'x.txt');
expectations();
// ASYNC
preparations();
jetContext.moveAsync('b.txt', 'x.txt')
.then(function () {
expectations();
done();
});
});
});
"use strict";
describe('path', function () {
var pathUtil = require('path');
var jetpack = require('..');
it('if empty returns same path as cwd()', function () {

@@ -13,12 +13,16 @@ expect(jetpack.path()).toBe(jetpack.cwd());

});
it('is absolute if prepending slash present', function () {
expect(jetpack.path('/blah')).toBe(pathUtil.resolve('/blah'));
});
it('resolves to cwd value', function () {
var blah = pathUtil.join(jetpack.cwd(), 'blah');
expect(jetpack.path('blah')).toBe(blah);
it('resolves to CWD path of this jetpack instance', function () {
var a = pathUtil.join(jetpack.cwd(), 'a');
expect(jetpack.path('a')).toBe(a);
// Create jetpack instance with other CWD
var jetpackSubdir = jetpack.cwd('subdir');
var b = pathUtil.join(jetpack.cwd(), 'subdir', 'b');
expect(jetpackSubdir.path('b')).toBe(b);
});
it('can take unlimited number of arguments as path parts', function () {

@@ -28,3 +32,3 @@ var abc = pathUtil.join(jetpack.cwd(), 'a', 'b', 'c');

});
});
"use strict";
describe('remove', function () {
var fse = require('fs-extra');
var pathUtil = require('path');
var helper = require('./helper');
var helper = require('./support/spec_helper');
var jetpack = require('..');
beforeEach(helper.beforeEach);
afterEach(helper.afterEach);
it("doesn't throw if path already doesn't exist", function (done) {
// SYNC
jetpack.remove('dir');
// ASYNC

@@ -23,31 +23,46 @@ jetpack.removeAsync('dir')

});
it("should delete file", function (done) {
var preparations = function () {
helper.clearWorkingDir();
fse.outputFileSync('file.txt', 'abc');
};
var expectations = function () {
expect('file.txt').not.toExist();
};
// SYNC
fse.outputFileSync('file.txt', 'abc');
preparations();
jetpack.remove('file.txt');
expect(fse.existsSync('file.txt')).toBe(false);
expectations();
// ASYNC
fse.outputFileSync('file.txt', 'abc');
preparations();
jetpack.removeAsync('file.txt')
.then(function () {
expect(fse.existsSync('file.txt')).toBe(false);
expectations();
done();
});
});
it("removes directory with stuff inside", function (done) {
function preparations() {
var preparations = function () {
helper.clearWorkingDir();
fse.mkdirsSync('a/b/c');
fse.outputFileSync('a/f.txt', 'abc');
fse.outputFileSync('a/b/f.txt', '123');
}
};
var expectations = function () {
expect('a').not.toExist();
};
// SYNC
preparations();
jetpack.remove('a');
expect(fse.existsSync('a')).toBe(false);
expectations();
// ASYNC

@@ -57,154 +72,35 @@ preparations();

.then(function () {
expect(fse.existsSync('a')).toBe(false);
expectations();
done();
});
});
it("returns undefined", function (done) {
it("respects internal CWD of jetpack instance", function (done) {
var preparations = function () {
fse.outputFileSync('file.txt', 'abc');
helper.clearWorkingDir();
fse.outputFileSync('a/b/c.txt', '123');
};
var expectations = function () {
expect('a').toExist();
expect('a/b').not.toExist();
};
var jetContext = jetpack.cwd('a');
// SYNC
preparations();
var ret = jetpack.remove('file.txt');
expect(ret).toBe(undefined);
jetContext.remove('b')
expectations();
// ASYNC
preparations();
jetpack.removeAsync('file.txt')
.then(function (context) {
expect(ret).toBe(undefined);
jetContext.removeAsync('b')
.then(function () {
expectations();
done();
});
});
describe('mask matching', function () {
it("deletes ONLY", function (done) {
function preparations() {
fse.outputFileSync('a/f.txt', 'abc');
fse.outputFileSync('a/f.doc', 'abc');
fse.outputFileSync('a/b/f.txt', 'abc');
fse.mkdirsSync('a/b/tmp');
fse.mkdirsSync('a/tmp/c');
}
function expectations() {
expect(fse.existsSync('a/b/tmp')).toBe(false);
expect(fse.existsSync('a/b')).toBe(true);
expect(fse.existsSync('a/tmp')).toBe(false);
expect(fse.existsSync('a/f.doc')).toBe(true);
expect(fse.existsSync('a/f.txt')).toBe(false);
expect(fse.existsSync('a/b/f.txt')).toBe(false);
}
// SYNC
preparations();
jetpack.remove('a', { only: ['*.txt', 'tmp'] });
expectations();
// ASYNC
preparations();
jetpack.removeAsync('a', { only: ['*.txt', 'tmp'] })
.then(function () {
expectations();
done();
});
});
it("tests ONLY also against root path", function (done) {
// SYNC
fse.mkdirSync('a');
jetpack.remove('a', { only: ['a'] });
expect(fse.existsSync('a')).toBe(false);
// ASYNC
fse.mkdirSync('a');
jetpack.removeAsync('a', { only: ['a'] })
.then(function () {
expect(fse.existsSync('a')).toBe(false);
done();
});
});
it("deletes ALLBUT", function (done) {
function preparations() {
fse.mkdirsSync('a/b/tmp');
fse.mkdirsSync('a/tmp/c');
fse.writeFileSync('a/f.txt', 'abc');
fse.writeFileSync('a/f.doc', 'abc');
fse.writeFileSync('a/b/f.txt', 'abc');
fse.mkdirsSync('a/x/y');
}
function expectations() {
expect(fse.existsSync('a/b/tmp')).toBe(true);
expect(fse.existsSync('a/tmp/c')).toBe(true);
expect(fse.existsSync('a/f.doc')).toBe(false);
expect(fse.existsSync('a/f.txt')).toBe(true);
expect(fse.existsSync('a/b/f.txt')).toBe(true);
expect(fse.existsSync('a/x')).toBe(false);
}
// SYNC
preparations();
jetpack.remove('a', { allBut: ['*.txt', 'tmp'] });
expectations();
// ASYNC
preparations();
jetpack.removeAsync('a', { allBut: ['*.txt', 'tmp'] })
.then(function () {
expectations();
done();
});
});
it("tests ALLBUT also agains root path", function (done) {
fse.mkdirSync('a');
// SYNC
jetpack.remove('a', { allBut: ['a'] });
expect(fse.existsSync('a')).toBe(true);
// ASYNC
jetpack.removeAsync('a', { allBut: ['a'] })
.then(function () {
expect(fse.existsSync('a')).toBe(true);
done();
});
});
it("ONLY takes precedence over ALLBUT", function (done) {
function preparations() {
fse.outputFileSync('a/f.txt', 'abc');
fse.outputFileSync('a/f.doc', 'abc');
}
function expectations() {
expect(fse.existsSync('a/f.txt')).toBe(true);
expect(fse.existsSync('a/f.doc')).toBe(false);
}
// SYNC
preparations();
jetpack.remove('a', { only: ['f.doc'], allBut: ['f.txt'] });
expectations();
// ASYNC
preparations();
jetpack.removeAsync('a', { only: ['f.doc'], allBut: ['f.txt'] })
.then(function () {
expectations();
done();
});
});
});
});

@@ -7,3 +7,3 @@ "use strict";

var pathUtil = require('path');
var helper = require('./helper');
var helper = require('./support/spec_helper');
var jetpack = require('..');

@@ -13,14 +13,15 @@

afterEach(helper.afterEach);
it("renames file", function (done) {
var preparations = function () {
helper.clearWorkingDir();
fse.outputFileSync('a/b.txt', 'abc');
};
var expectations = function () {
expect(fse.existsSync('a/b.txt')).toBe(false);
expect(fse.readFileSync('a/x.txt', 'utf8')).toBe('abc');
expect('a/b.txt').not.toExist();
expect('a/x.txt').toBeFileWithContent('abc');
};
// SYNC

@@ -30,5 +31,3 @@ preparations();

expectations();
helper.clearWorkingDir();
// ASYNC

@@ -42,14 +41,15 @@ preparations();

});
it("renames directory", function (done) {
var preparations = function () {
helper.clearWorkingDir();
fse.outputFileSync('a/b/c.txt', 'abc');
};
var expectations = function () {
expect(fse.existsSync('a/b')).toBe(false);
expect(fse.existsSync('a/x')).toBe(true);
expect('a/b').not.toExist();
expect('a/x').toBeDirectory();
};
// SYNC

@@ -59,5 +59,3 @@ preparations();

expectations();
helper.clearWorkingDir();
// ASYNC

@@ -71,21 +69,27 @@ preparations();

});
it("returns undefined", function (done) {
it("respects internal CWD of jetpack instance", function (done) {
var preparations = function () {
fse.outputFileSync('file.txt', 'abc');
helper.clearWorkingDir();
fse.outputFileSync('a/b/c.txt', 'abc');
};
var expectations = function () {
expect('a/b').not.toExist();
expect('a/x').toBeDirectory();
};
var jetContext = jetpack.cwd('a');
// SYNC
preparations();
var ret = jetpack.rename('file.txt', 'fiole.txt');
expect(ret).toBe(undefined);
helper.clearWorkingDir();
jetContext.rename('b', 'x');
expectations();
// ASYNC
preparations();
jetpack.renameAsync('file.txt', 'fiole.txt')
.then(function (ret) {
expect(ret).toBe(undefined);
jetContext.renameAsync('b', 'x')
.then(function () {
expectations();
done();

@@ -92,0 +96,0 @@ });

@@ -7,3 +7,3 @@ "use strict";

var pathUtil = require('path');
var helper = require('./helper');
var helper = require('./support/spec_helper');
var jetpack = require('..');

@@ -13,10 +13,10 @@

afterEach(helper.afterEach);
it("exposes vanilla stream methods", function (done) {
fse.outputFileSync('a.txt', 'abc');
var output = jetpack.createWriteStream('b.txt');
var input = jetpack.createReadStream('a.txt');
output.on('finish', function () {
expect(fse.readFileSync('b.txt', 'utf8')).toBe('abc');
expect('b.txt').toBeFileWithContent('abc');
done();

@@ -26,5 +26,6 @@ });

});
it("stream methods respect jetpack internal CWD", function (done) {
fse.outputFileSync('dir/a.txt', 'abc');
var dir = jetpack.cwd('dir');

@@ -34,3 +35,3 @@ var output = dir.createWriteStream('b.txt');

output.on('finish', function () {
expect(fse.readFileSync('dir/b.txt', 'utf8')).toBe('abc');
expect('dir/b.txt').toBeFileWithContent('abc');
done();

@@ -40,3 +41,3 @@ });

});
});
"use strict";
describe('matcher |', function () {
var matcher = require('../../lib/utils/matcher');
it("can test against many masks", function () {
it("can test against one pattern passed as a string", function () {
var test = matcher.create('a');
expect(test('/a')).toBe(true);
expect(test('/b')).toBe(false);
});
it("can test against many patterns passed as an array", function () {
var test = matcher.create(['a', 'b']);
expect(test('/a')).toBe(true);
expect(test('/b')).toBe(true);
expect(test('/c')).toBe(false);
});
describe('different mask types |', function () {
it("without any slash", function () {
// then just the last element on right has to match the mask
var test = matcher.create(['b']);
expect(test('/b')).toBe(true);
expect(test('/a/b')).toBe(true);
expect(test('/a/b/c')).toBe(false);
});
it("with any slash inside", function () {
// starts matching only from the left side
var test = matcher.create(['a/b']);
expect(test('/a/b')).toBe(true);
expect(test('/x/a/b')).toBe(false);
expect(test('/a/b/c')).toBe(false);
test = matcher.create(['/a/b']);
expect(test('/a/b')).toBe(true);
expect(test('/x/a/b')).toBe(false);
expect(test('/a/b/c')).toBe(false);
});
});
describe('exclude part of path from matching |', function () {
// if we are in dir "/a/b" and want to test contents inside
// "b" then we have to exclude this "/a/b" part from matching,
// otherwise it could mess up the match
it("without slash in front", function () {
var test = matcher.create(['a/**'], '/');
expect(test('/a/b/c')).toBe(true);
test = matcher.create(['a/**'], '/a');
expect(test('/a/b/c')).toBe(false);
test = matcher.create(['c'], '/');
expect(test('/a/b/c')).toBe(true);
test = matcher.create(['c'], '/a/b');
expect(test('/a/b/c')).toBe(true);
test = matcher.create(['c'], '/a/b/c');
expect(test('/a/b/c')).toBe(false);
});
it("with slash in front", function () {
var test = matcher.create(['/a/**'], '/');
expect(test('/a/b/c')).toBe(true);
test = matcher.create(['/a/**'], '/a');
expect(test('/a/b/c')).toBe(false);
test = matcher.create(['/c'], '/a');
expect(test('/a/b/c')).toBe(false);
test = matcher.create(['/c'], '/a/b');
expect(test('/a/b/c')).toBe(true);
test = matcher.create(['/c'], '/a/b/c');
expect(test('/a/b/c')).toBe(false);
});
});
describe('possible mask tokens |', function () {
it("*", function () {

@@ -86,3 +26,3 @@ var test = matcher.create(['*']);

expect(test('/a/b.txt')).toBe(true);
test = matcher.create(['a*b']);

@@ -93,3 +33,3 @@ expect(test('/ab')).toBe(true);

});
it("/*", function () {

@@ -100,3 +40,3 @@ var test = matcher.create(['/*']);

});
it("**", function () {

@@ -106,4 +46,4 @@ var test = matcher.create(['**']);

expect(test('/a/b')).toBe(true);
test = matcher.create(['a/**/d']);
test = matcher.create(['/a/**/d']);
expect(test('/a/d')).toBe(true);

@@ -115,5 +55,5 @@ expect(test('/a/b/d')).toBe(true);

});
it("**/", function () {
var test = matcher.create(['**/a']);
it("/**/something", function () {
var test = matcher.create(['/**/a']);
expect(test('/a')).toBe(true);

@@ -124,3 +64,3 @@ expect(test('/x/a')).toBe(true);

});
it("+(option1|option2)", function () {

@@ -132,3 +72,3 @@ var test = matcher.create(['*.+(txt|md)']);

});
it("?", function () {

@@ -140,3 +80,3 @@ var test = matcher.create(['a?c']);

});
it("characters #! have NO special meaning", function () {

@@ -146,61 +86,9 @@ // these characters have meaning in .gitignore, but here should have not

expect(test('/!a')).toBe(true);
test = matcher.create(['#a']);
expect(test('/#a')).toBe(true);
});
});
it("can mark tree branches", function () {
var tree = {
name: 'a',
type: 'dir',
children: [
{
name: 'a',
type: 'dir',
children: [
{
name: 'a.txt',
type: 'file',
}
]
},{
name: 'b.txt',
type: 'file',
},{
name: 'c',
type: 'dir',
children: [
{
name: 'a.txt',
type: 'file',
}
]
}
]
};
var patterns = ['a/a', 'b.txt'];
matcher.markTree(tree, patterns);
expect(tree.matchPath).toBe('/a');
expect(tree.matches).toBe(false);
expect(tree.children[0].matchPath).toBe('/a/a');
expect(tree.children[0].matches).toBe(true);
expect(tree.children[0].children[0].matchPath).toBe('/a/a/a.txt');
expect(tree.children[0].children[0].matches).toBe(true);
expect(tree.children[1].matchPath).toBe('/a/b.txt');
expect(tree.children[1].matches).toBe(true);
expect(tree.children[2].matchPath).toBe('/a/c');
expect(tree.children[2].matches).toBe(false);
expect(tree.children[2].children[0].matchPath).toBe('/a/c/a.txt');
expect(tree.children[2].children[0].matches).toBe(false);
});
});

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

SocketSocket SOC 2 Logo

Product

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

Packages

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc