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.3.0 to 0.4.0

CHANGELOG.md

289

lib/copy.js

@@ -6,26 +6,16 @@ "use strict";

var Q = require('q');
var mkdirp = require('mkdirp');
var file = require('./file');
var dir = require('./dir');
var matcher = require('./pathMatcher');
var utils = require('./utils');
var exists = require('./exists');
var matcher = require('./utils/matcher');
var inspector = require('./inspector');
var fileOps = require('./fileOps');
/**
* Normalizes and parses options passed into remove function.
*/
function processOptions(options, basePath) {
if (options === undefined) {
options = {};
var normalizeOptions = function (options) {
options = options || {};
if (typeof options.overwrite !== 'boolean') {
options.overwrite = false;
}
if (options.overwrite === undefined) {
options.overwrite = 'no';
}
if (options.only) {
options.only = matcher.create(pathUtil.dirname(basePath), options.only);
}
if (options.allBut) {
options.allBut = matcher.create(pathUtil.dirname(basePath), options.allBut);
}
return options;
}
};

@@ -36,53 +26,48 @@ //---------------------------------------------------------

module.exports.sync = function (from, to, options) {
// 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);
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);
}
};
var sync = function (from, to, options) {
function copy(fromPath, toPath) {
if (fs.existsSync(toPath) && options.overwrite === 'no') {
var err = new Error('Destination path already exists');
err.code = 'EEXIST';
throw err;
}
var stat = fs.statSync(fromPath);
if (stat.isDirectory()) {
dir.sync(toPath);
var list = fs.readdirSync(fromPath);
list.forEach(function (filename) {
var subFromPath = pathUtil.resolve(fromPath, filename);
var subToPath = pathUtil.resolve(toPath, filename);
check(subFromPath, subToPath);
});
} else {
var data = fs.readFileSync(fromPath);
file.sync(toPath, { content: data });
}
options = normalizeOptions(options);
if (exists.sync(to) && options.overwrite === false) {
var err = new Error('Destination path already exists.');
err.code = 'EEXIST';
throw err;
}
function check(fromPath, toPath) {
var copyOrNot = true;
if (options.only) {
var node = utils.getTreeItemForPath(options.onlyMatchTree, fromPath);
if (!(utils.hasMatchingParent(node) || utils.hasMatchingChild(node))) {
copyOrNot = false;
}
} else if (options.allBut) {
if (options.allBut(fromPath)) {
copyOrNot = false;
}
}
if (copyOrNot) {
copy(fromPath, toPath);
}
if (options.only === undefined && options.allBut === undefined) {
// No special options set, we can just copy the whole thing.
copySync(from, to);
return;
}
options = processOptions(options, from);
// Figure out what to copy, and what not to copy.
var tree = inspector.tree(from);
var pathsToCopy;
if (options.only) {
// creates object representation of whole tree and tests every object if matches file masks
options.onlyMatchTree = utils.createMatchTree(from, options.only);
pathsToCopy = matcher.treeToWhitelistPaths(from, tree, options.only);
} else if (options.allBut) {
pathsToCopy = matcher.treeToBlacklistPaths(from, tree, options.allBut);
}
check(from, to);
// Remove every path which is in array separately.
pathsToCopy.forEach(function (path) {
var internalPart = path.substring(from.length);
copySync(path, pathUtil.join(to, internalPart));
});
};

@@ -97,104 +82,96 @@

var qReadFile = Q.denodeify(fs.readFile);
var qMkdirp = Q.denodeify(mkdirp);
module.exports.async = function (from, to, options) {
// Soubroutine which copies a file or tree of files.
var copyAsync = function (from, to) {
var deferred = Q.defer();
function noop() {
var qd = Q.defer();
qd.resolve();
return qd.promise;
}
qStat(from).then(function (stat) {
if (stat.isDirectory()) {
// Prepare destination directory
qMkdirp(to)
.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);
})
.then(deferred.resolve, deferred.reject);
}
}, deferred.reject);
function copy(fromPath, toPath) {
var qd = Q.defer();
// test if destination file exists
fs.stat(toPath, function (err, stat) {
var exists = !(err && err.code === 'ENOENT');
if (exists && options.overwrite === 'no') {
// file already exists and overwtiting is not specified, terminate
err = new Error('Destination path already exists');
err.code = 'EEXIST';
qd.reject(err);
} else {
qStat(fromPath)
.then(function (stat) {
if (stat.isDirectory()) {
// make dir
dir.async(toPath)
.then(function () {
// scan all files inside directory...
return qReaddir(fromPath);
})
.then(function (list) {
// ...and decide if they shoulb be copied as well
var promises = list.map(function (filename) {
var subFromPath = pathUtil.resolve(fromPath, filename);
var subToPath = pathUtil.resolve(toPath, filename);
return check(subFromPath, subToPath);
});
return Q.all(promises);
})
.then(qd.resolve, qd.reject);
} else {
// this is file so just copy it
qReadFile(fromPath)
.then(function (data) {
return file.async(toPath, { content: data });
})
.then(qd.resolve, qd.reject);
}
}, qd.reject);
}
});
return qd.promise;
}
return deferred.promise;
};
var async = function (from, to, options) {
var deferred = Q.defer();
function check(fromPath, toPath) {
var copyOrNot = true;
if (options.only) {
var node = utils.getTreeItemForPath(options.onlyMatchTree, fromPath);
if (!(utils.hasMatchingParent(node) || utils.hasMatchingChild(node))) {
copyOrNot = false;
}
} else if (options.allBut) {
if (options.allBut(fromPath)) {
copyOrNot = false;
}
options = normalizeOptions(options);
exists.async(to)
.then(function (exists) {
if (exists && options.overwrite === false) {
var err = new Error('Destination path already exists.');
err.code = 'EEXIST';
deferred.reject(err);
} else {
startCopying();
}
if (copyOrNot) {
return copy(fromPath, toPath);
});
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);
} else {
figureOutWhatToCopy();
}
return noop();
}
};
var qd = Q.defer();
options = processOptions(options, from);
if (options.only) {
// creates object representation of whole tree and tests every object if matches file masks
utils.createMatchTreeAsync(from, options.only)
var figureOutWhatToCopy = function () {
// Figure out what to copy, and what not to copy.
inspector.treeAsync(from)
.then(function (tree) {
options.onlyMatchTree = tree;
return check(from, to);
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.
var promises = pathsToCopy.map(function (path) {
var internalPart = path.substring(from.length);
return copyAsync(path, pathUtil.join(to, internalPart));
});
return Q.all(promises);
})
.then(qd.resolve, qd.reject);
} else {
check(from, to)
.then(qd.resolve, qd.reject);
}
.then(done, deferred.reject);
};
return qd.promise;
};
var done = function () {
// Function to make sure we are returning undefined here.
deferred.resolve();
};
return deferred.promise;
};
//---------------------------------------------------------
// API
//---------------------------------------------------------
module.exports.sync = sync;
module.exports.async = async;

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

var Q = require('q');
var mkdirp = require('mkdirp');
var rimraf = require('rimraf');
var remove = require('./remove');
var utils = require('./utils');
var modeUtil = require('./utils/mode');

@@ -15,10 +16,10 @@ function getCriteriaDefaults(criteria) {

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

@@ -32,51 +33,48 @@ return criteria;

function mkdirpSync(path, mode) {
var parts = path.split(pathUtil.sep);
var currPath = pathUtil.sep;
while (parts.length > 0) {
currPath = pathUtil.resolve(currPath, parts.shift());
if (!fs.existsSync(currPath)) {
fs.mkdirSync(currPath, mode);
}
}
}
module.exports.sync = function (path, criteria) {
var exists, stat, mode;
var sync = function (path, criteria) {
criteria = getCriteriaDefaults(criteria);
exists = fs.existsSync(path);
if (criteria.exists === false) {
if (exists === true) {
remove.sync(path);
try {
var stat = fs.statSync(path);
} catch (err) {
// detection if path exists
if (err.code !== 'ENOENT') {
throw err;
}
return;
}
if (exists === true) {
// 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
stat = undefined;
}
if (stat) {
stat = fs.statSync(path);
// directory already exists
// this have to be directory, so if is directory must be removed
if (!stat.isDirectory()) {
remove.sync(path);
exists = false;
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;
}
}
if (exists === true) {
// ensure existing directory
// ensure existing directory matches criteria
if (criteria.empty === true) {
// delete everything inside this directory
var list = fs.readdirSync(path);
list.forEach(function (filename) {
remove.sync(pathUtil.resolve(path, filename));
rimraf.sync(pathUtil.resolve(path, filename));
});
}
mode = utils.normalizeFileMode(stat.mode);
var mode = modeUtil.normalizeFileMode(stat.mode);
if (criteria.mode !== undefined && criteria.mode !== mode) {
// mode is different than specified in criteria, fix that
fs.chmodSync(path, criteria.mode);

@@ -86,4 +84,9 @@ }

} else {
// create new directroy
mkdirpSync(path, criteria.mode);
// directory doesn't exist
if (criteria.exists === true) {
// create this directory
mkdirp.sync(path, { mode: criteria.mode });
}
}

@@ -96,84 +99,87 @@ };

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);
function mkdirp(path, mode) {
var qd = Q.defer();
var parts = path.split(pathUtil.sep);
var currPath = pathUtil.sep;
// delete all files and directores inside a directory,
// but leave the main directory intact
var empty = function (path) {
var deferred = Q.defer();
function nextTick() {
if (parts.length > 0) {
tick();
} else {
qd.resolve();
}
}
function tick() {
currPath = pathUtil.resolve(currPath, parts.shift());
fs.exists(currPath, function (exists) {
if (exists) {
nextTick();
} else {
fs.mkdir(currPath, mode, function (err) {
if (err) {
qd.reject(err);
} else {
nextTick();
}
});
}
qReaddir(path)
.then(function (list) {
var promises = list.map(function (filename) {
return qRimraf(pathUtil.resolve(path, filename));
});
}
return Q.all(promises);
})
.then(deferred.resolve, deferred.reject);
nextTick();
return qd.promise;
}
return deferred.promise;
};
function ensureExistingDir(path, criteria, stat) {
var qd = Q.defer();
var async = function (path, criteria) {
var deferred = Q.defer();
function setProperContent() {
if (criteria.empty) {
qReaddir(path)
.then(function (list) {
var promises = [];
list.forEach(function (filename) {
promises.push(remove.async(pathUtil.resolve(path, filename)));
});
Q.all(promises)
.then(qd.resolve, qd.reject);
}, qd.reject);
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 {
qd.resolve();
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 mode = utils.normalizeFileMode(stat.mode);
if (criteria.mode !== undefined && criteria.mode !== mode) {
qChmod(path, criteria.mode)
.then(setProperContent, qd.reject);
} else {
setProperContent();
}
return qd.promise;
}
module.exports.async = function (path, criteria) {
var qd = Q.defer();
criteria = getCriteriaDefaults(criteria);
fs.exists(path, function (exists) {
var ensureCriteria = function (stat) {
if (criteria.exists === false) {
if (stat) {
if (exists === true) {
remove.async(path)
.then(qd.resolve, qd.reject);
// 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;
}
// ensure existing directory matches criteria
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);
} else {
// OMG, done!
deferred.resolve();
}
};
if (criteria.empty === true) {
// delete everything inside this directory
empty(path)
.then(checkMode, deferred.reject);
} else {
qd.resolve();
checkMode();
}

@@ -183,29 +189,23 @@

if (exists === true) {
fs.stat(path, function (err, stat) {
if (err) {
qd.reject(err);
} else {
if (stat.isDirectory()) {
ensureExistingDir(path, criteria, stat)
.then(qd.resolve, qd.reject);
} else {
// if is not directory try to remove and replace dir instead
remove.async(path).then(function () {
mkdirp(path, criteria.mode)
.then(qd.resolve, qd.reject);
}, qd.reject);
}
}
});
// we know directory doesn't exist
if (criteria.exists === true) {
// create this directory
qMkdirp(path, { mode: criteria.mode })
.then(deferred.resolve, deferred.reject);
} else {
mkdirp(path, criteria.mode)
.then(qd.resolve, qd.reject);
deferred.resolve();
}
}
});
};
return qd.promise;
return deferred.promise;
};
//---------------------------------------------------------
// API
//---------------------------------------------------------
module.exports.sync = sync;
module.exports.async = async;

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

module.exports.sync = function (path) {
var sync = function (path) {

@@ -36,4 +36,4 @@ try {

module.exports.async = function (path) {
var qd = Q.defer();
var async = function (path) {
var deferred = Q.defer();

@@ -43,16 +43,23 @@ fs.stat(path, function (err, stat) {

if (err.code === 'ENOENT') {
qd.resolve(false);
deferred.resolve(false);
} else {
qd.reject(err);
deferred.reject(err);
}
} else if (stat.isDirectory()) {
qd.resolve('dir');
deferred.resolve('dir');
} else if (stat.isFile()) {
qd.resolve('file');
deferred.resolve('file');
} else {
qd.resolve('other');
deferred.resolve('other');
}
});
return qd.promise;
return deferred.promise;
};
//---------------------------------------------------------
// API
//---------------------------------------------------------
module.exports.sync = sync;
module.exports.async = async;

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

var Q = require('q');
var rimraf = require('rimraf');
var dir = require('./dir');
var remove = require('./remove');
var utils = require('./utils');
var modeUtils = require('./utils/mode');
var fileOps = require('./fileOps');

@@ -16,10 +16,10 @@ function getCriteriaDefaults(criteria) {

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

@@ -36,3 +36,3 @@ return criteria;

}
return content;
return content || '';
}

@@ -44,36 +44,39 @@

module.exports.sync = function (path, criteria) {
var exists, stat, mode;
var sync = function (path, criteria) {
criteria = getCriteriaDefaults(criteria);
exists = fs.existsSync(path);
if (criteria.exists === false) {
if (exists === true) {
fs.unlinkSync(path);
try {
var stat = fs.statSync(path);
} catch (err) {
// Detection if path exists
if (err.code !== 'ENOENT') {
throw err;
}
return;
}
if (exists === true) {
if (stat && !stat.isFile()) {
// This have to be file, so if is directory must be removed.
rimraf.sync(path);
// Clear stat variable to indicate now nothing is there.
stat = undefined;
}
if (stat) {
stat = fs.statSync(path);
// Ensure existing file
// this have to be file, so if is directory must be removed
if (!stat.isFile()) {
remove.sync(path);
exists = false;
if (criteria.exists === false) {
// Path exists and we want for it not to, so delete and end there.
fs.unlinkSync(path);
return;
}
}
if (exists === true) {
// ensure existing file
if (criteria.empty === true && stat.size > 0) {
if (criteria.empty === true) {
// Use truncate because it doesn't change mode of file.
fs.truncateSync(path, 0);
}
mode = utils.normalizeFileMode(stat.mode);
// Ensure file mode
var mode = modeUtils.normalizeFileMode(stat.mode);
if (criteria.mode !== undefined && criteria.mode !== mode) {

@@ -83,20 +86,10 @@ fs.chmodSync(path, criteria.mode);

if (criteria.empty !== true && criteria.content !== undefined) {
fs.writeFileSync(path, normalizeContent(criteria.content));
// Ensure file content
if (criteria.empty === false && criteria.content !== undefined) {
fileOps.write(path, criteria.content, { mode: mode });
}
} else {
// create new file
// ensure parent directories exist
if (fs.existsSync(pathUtil.dirname(path)) === false) {
dir.sync(pathUtil.dirname(path));
}
if (criteria.content === undefined) {
criteria.content = '';
}
fs.writeFileSync(path, normalizeContent(criteria.content), { mode: criteria.mode });
} else if (criteria.exists === true) {
// Create new file.
fileOps.write(path, criteria.content || '', { mode: criteria.mode });
}

@@ -110,109 +103,99 @@ };

var qUnlink = Q.denodeify(fs.unlink);
var qWriteFile = Q.denodeify(fs.writeFile);
var qStat = Q.denodeify(fs.stat);
var qTruncate = Q.denodeify(fs.truncate);
var qChmod = Q.denodeify(fs.chmod);
var qRimraf = Q.denodeify(rimraf);
function makeNewFile(path, criteria) {
var qd = Q.defer();
var async = function (path, criteria) {
var deferred = Q.defer();
// ensure parent directories exist
fs.exists(pathUtil.dirname(path), function (existsParent) {
if (existsParent === false) {
dir.async(pathUtil.dirname(path))
.then(writeFile, qd.reject);
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);
} else {
writeFile();
ensureCriteria(stat);
}
});
function writeFile() {
if (criteria.content === undefined) {
criteria.content = '';
}
qWriteFile(path, normalizeContent(criteria.content), { mode: criteria.mode })
.then(qd.resolve, qd.reject);
}
return qd.promise;
}
function ensureExistingFile(path, criteria, stat) {
var qd = Q.defer();
function setProperContent() {
if (criteria.empty) {
if (stat.size > 0) {
qTruncate(path, 0)
.then(qd.resolve, qd.reject);
} else {
qd.resolve();
}
}, function (err) {
// Detection if path exists.
if (err.code !== 'ENOENT') {
// This is other error that nonexistent path, so end here.
deferred.reject(err);
} else {
if (criteria.content !== undefined) {
qWriteFile(path, normalizeContent(criteria.content))
.then(qd.resolve, qd.reject);
} else {
qd.resolve();
}
ensureCriteria(undefined);
}
}
});
var mode = utils.normalizeFileMode(stat.mode);
if (criteria.mode !== undefined && criteria.mode !== mode) {
qChmod(path, criteria.mode)
.then(setProperContent, qd.reject);
} else {
setProperContent();
}
return qd.promise;
}
module.exports.async = function (path, criteria) {
var qd = Q.defer();
criteria = getCriteriaDefaults(criteria);
fs.exists(path, function (exists) {
var ensureCriteria = function (stat) {
if (criteria.exists === false) {
if (stat) {
if (exists === true) {
qUnlink(path)
.then(qd.resolve, qd.reject);
} else {
qd.resolve();
// 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;
}
} else {
var mode = modeUtils.normalizeFileMode(stat.mode);
if (exists === true) {
fs.stat(path, function (err, stat) {
if (err) {
qd.reject(err);
} else {
if (stat.isFile()) {
ensureExistingFile(path, criteria, stat)
.then(qd.resolve, qd.reject);
} else {
// if is not file try to remove and replace file instead
remove.async(path).then(function () {
makeNewFile(path, criteria)
.then(qd.resolve, qd.reject);
}, qd.reject);
}
}
});
} else {
makeNewFile(path, criteria)
.then(qd.resolve, qd.reject);
}
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);
} else {
ensureMode();
}
};
var ensureMode = 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 })
.then(deferred.resolve, deferred.reject);
} else {
deferred.resolve();
}
};
// this starts the sequence of asynchronous functions declared above
ensureEmptiness();
} else if (criteria.exists === true) {
fileOps.writeAsync(path, criteria.content || '', { mode: criteria.mode })
.then(deferred.resolve, deferred.reject);
} else {
// File doesn't exist and this is the desired condition, end here.
deferred.resolve();
}
});
};
return qd.promise;
return deferred.promise;
};
//---------------------------------------------------------
// API
//---------------------------------------------------------
module.exports.sync = sync;
module.exports.async = async;

@@ -0,1 +1,3 @@

// The main thing. Here everything starts.
"use strict";

@@ -6,2 +8,4 @@

var fileOps = require('./fileOps');
var inspector = require('./inspector');
var dir = require('./dir');

@@ -11,13 +15,15 @@ var file = require('./file');

var exists = require('./exists');
var read = require('./read');
var move = require('./move');
var remove = require('./remove');
var list = require('./list');
function jetpackContext(cwdPath) {
// 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.
var jetpackContext = function (cwdPath) {
function getCwdPath() {
var getCwdPath = function () {
return cwdPath || process.cwd();
}
function cwd(newCwdPath) {
var cwd = function (newCwdPath) {
// return current CWD if no parameter...

@@ -27,3 +33,2 @@ if (newCwdPath === undefined) {

}
// ...create new CWD context otherwise

@@ -37,10 +42,13 @@ if (typeof newCwdPath === 'string') {

}
function path() {
var path = getCwdPath();
for (var i = 0; i < arguments.length; i++){
path = pathUtil.resolve(path, arguments[i]);
}
return path;
// resolves path to inner CWD path of this jetpack instance
var resolvePath = function (path) {
return pathUtil.resolve(getCwdPath(), path);
}
var path = function () {
// add CWD base path as first element of arguments array
Array.prototype.unshift.call(arguments, getCwdPath());
return pathUtil.resolve.apply(null, arguments);
}

@@ -52,29 +60,24 @@ // API

path: path,
append: function (path, data) {
fileOps.append(resolvePath(path), data);
},
appendAsync: function (path, data) {
return fileOps.appendAsync(resolvePath(path), data);
},
copy: function (from, to, options) {
var normalizedFromPath = pathUtil.resolve(getCwdPath(), from);
var normalizedToPath = pathUtil.resolve(getCwdPath(), to);
copy.sync(normalizedFromPath, normalizedToPath, options);
return this;
copy.sync(resolvePath(from), resolvePath(to), options);
},
copyAsync: function (from, to, options) {
var qd = Q.defer();
var that = this;
var normalizedFromPath = pathUtil.resolve(getCwdPath(), from);
var normalizedToPath = pathUtil.resolve(getCwdPath(), to);
copy.async(normalizedFromPath, normalizedToPath, options)
.then(function () {
qd.resolve(that);
}, qd.reject);
return qd.promise;
return copy.async(resolvePath(from), resolvePath(to), options)
},
dir: function (path, criteria) {
var normalizedPath = pathUtil.resolve(getCwdPath(), path);
dir.sync(normalizedPath, criteria);
var newPath = normalizedPath;
if (criteria !== undefined && criteria.exists === false) {
newPath = pathUtil.dirname(newPath);
return undefined;
}
return cwd(newPath);
return cwd(normalizedPath);
},

@@ -86,11 +89,11 @@ dirAsync: function (path, criteria) {

.then(function () {
var newPath = normalizedPath;
if (criteria !== undefined && criteria.exists === false) {
newPath = pathUtil.dirname(newPath);
qd.resolve(undefined);
} else {
qd.resolve(cwd(normalizedPath));
}
qd.resolve(cwd(newPath));
}, qd.reject);
return qd.promise;
},
exists: function (path) {

@@ -102,3 +105,3 @@ return exists.sync(pathUtil.resolve(getCwdPath(), path));

},
file: function (path, criteria) {

@@ -111,3 +114,3 @@ file.sync(pathUtil.resolve(getCwdPath(), path), criteria);

var that = this;
file.async(pathUtil.resolve(getCwdPath(), path), criteria)
file.async(resolvePath(path), criteria)
.then(function () {

@@ -118,46 +121,65 @@ qd.resolve(that);

},
read: function (path, mode) {
var normalizedPath = pathUtil.resolve(getCwdPath(), path);
return read.sync(normalizedPath, mode);
inspect: function (path) {
return inspector.inspect(resolvePath(path));
},
readAsync: function (path, mode) {
var normalizedPath = pathUtil.resolve(getCwdPath(), path);
return read.async(normalizedPath, mode);
inspectAsync: function (path) {
return inspector.inspectAsync(resolvePath(path));
},
write: function (path, content) {
return this.file(path, { content: content });
list: function (path, options) {
return inspector.list(resolvePath(path), options);
},
writeAsync: function (path, content) {
return this.fileAsync(path, { content: content });
listAsync: function (path, options) {
return inspector.listAsync(resolvePath(path), options);
},
move: function (from, to) {
move.sync(resolvePath(from), resolvePath(to));
},
moveAsync: function (from, to) {
return move.async(resolvePath(from), resolvePath(to));
},
read: function (path, returnAs, options) {
return fileOps.read(resolvePath(path), returnAs, options);
},
readAsync: function (path, returnAs, options) {
return fileOps.readAsync(resolvePath(path), returnAs, options);
},
remove: function (path, options) {
var normalizedPath = pathUtil.resolve(getCwdPath(), path);
remove.sync(normalizedPath, options);
var newPath = pathUtil.dirname(normalizedPath);
return cwd(newPath);
remove.sync(pathUtil.resolve(getCwdPath(), path), options);
},
removeAsync: function (path, options) {
var qd = Q.defer();
var normalizedPath = pathUtil.resolve(getCwdPath(), path);
remove.async(normalizedPath, options)
.then(function () {
qd.resolve(cwd(pathUtil.dirname(normalizedPath)));
}, qd.reject);
return qd.promise;
return remove.async(pathUtil.resolve(getCwdPath(), path), options)
},
list: function (path, options) {
var normalizedPath = pathUtil.resolve(getCwdPath(), path);
return list.sync(normalizedPath, options);
rename: function (path, newName) {
path = resolvePath(path)
var newPath = pathUtil.join(pathUtil.dirname(path), newName);
move.sync(path, newPath);
},
listAsync: function (path, options) {
var normalizedPath = pathUtil.resolve(getCwdPath(), path);
return list.async(normalizedPath, options);
renameAsync: function (path, newName) {
path = resolvePath(path)
var newPath = pathUtil.join(pathUtil.dirname(path), newName);
return move.async(path, newPath);
},
tree: function (path) {
return inspector.tree(resolvePath(path));
},
treeAsync: function (path) {
return inspector.treeAsync(resolvePath(path));
},
write: function (path, data, options) {
fileOps.write(resolvePath(path), data, options);
},
writeAsync: function (path, data, options) {
return fileOps.writeAsync(resolvePath(path), data, options);
},
};
}
module.exports = jetpackContext;
module.exports = jetpackContext;
"use strict";
var pathUtil = require('path');
var fs = require('fs');
var Q = require('q');
var rimraf = require('rimraf');
var matcher = require('./pathMatcher');
var utils = require('./utils');
var matcher = require('./utils/matcher');
var inspector = require('./inspector');
/**
* Normalizes and parses options passed into remove function.
*/
function processOptions(options, basePath) {
if (options === undefined) {
options = {};
}
if (options.only) {
options.only = matcher.create(pathUtil.dirname(basePath), options.only);
}
if (options.allBut) {
options.allBut = matcher.create(pathUtil.dirname(basePath), options.allBut);
}
return options;
}
//---------------------------------------------------------

@@ -30,60 +14,25 @@ // Sync

module.exports.sync = function (basePath, options) {
var sync = function (path, options) {
function remove(path, isDir) {
if (isDir) {
fs.rmdirSync(path);
} else {
fs.unlinkSync(path);
}
}
options = options || {};
function removeAll(path) {
doAction(path, remove, removeAll);
if (options.only === undefined && options.allBut === undefined) {
// No special options set, we can just remove the bastard.
rimraf.sync(path);
return;
}
function noop() {}
function doAction(path, actionOnThisPath, actionOnChildren) {
var stat = fs.statSync(path);
if (stat.isDirectory()) {
var list = fs.readdirSync(path);
list.forEach(function (filename) {
actionOnChildren(pathUtil.resolve(path, filename));
});
}
actionOnThisPath(path, stat.isDirectory());
// 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);
}
function check(path) {
if (options.only) {
if (options.only(path)) {
// this element and all children have to be removed
doAction(path, remove, removeAll);
} else {
// this element have to stay, but check all its children
doAction(path, noop, check);
}
} else if (options.allBut) {
var node = utils.getTreeItemForPath(options.allButMatchTree, path);
var actionOnThisNode = remove;
if (utils.hasMatchingParent(node) || utils.hasMatchingChild(node)) {
// this element have to stay, but check all its children
actionOnThisNode = noop;
}
doAction(path, actionOnThisNode, check);
} else {
// default action
doAction(path, remove, check);
}
}
options = processOptions(options, basePath);
if (options.allBut) {
// creates object representation of whole tree and tests every object if matches file masks
options.allButMatchTree = utils.createMatchTree(basePath, options.allBut);
}
check(basePath);
// Remove every path which is in array separately.
pathsToRemove.forEach(function (path) {
rimraf.sync(path);
});
};

@@ -95,97 +44,44 @@

var qStat = Q.denodeify(fs.stat);
var qReaddir = Q.denodeify(fs.readdir);
var qRmdir = Q.denodeify(fs.rmdir);
var qUnlink = Q.denodeify(fs.unlink);
var qRimraf = Q.denodeify(rimraf);
function removeAsync(basePath, options) {
var async = function(path, options) {
var deferred = Q.defer();
function remove(path, isDir) {
if (isDir) {
return qRmdir(path);
}
return qUnlink(path);
}
options = options || {};
function removeAll(path) {
return doAction(path, remove, removeAll);
}
function noop() {
var qd = Q.defer();
qd.resolve();
return qd.promise;
}
function doAction(path, actionOnThisPath, actionOnChildren) {
var qd = Q.defer();
qStat(path)
.then(function (stat) {
if (stat.isDirectory()) {
qReaddir(path)
.then(function (list) {
var promises = list.map(function (filename) {
return actionOnChildren(pathUtil.resolve(path, filename));
});
return Q.all(promises);
})
.then(function () {
return actionOnThisPath(path, true);
})
.then(qd.resolve, qd.reject);
} else {
actionOnThisPath(path, false)
.then(qd.resolve, qd.reject);
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);
}
}, qd.reject);
return qd.promise;
}
function check(path) {
if (options.only) {
if (options.only(path)) {
// this element and all children have to be removed
return doAction(path, remove, removeAll);
}
// this element have to stay, but check all its children
return doAction(path, noop, check);
}
if (options.allBut) {
var node = utils.getTreeItemForPath(options.allButMatchTree, path);
var actionOnThisNode = remove;
if (utils.hasMatchingParent(node) || utils.hasMatchingChild(node)) {
// this element have to stay, but check all its children
actionOnThisNode = noop;
}
// this element have to stay, but check all its children
return doAction(path, actionOnThisNode, check);
}
// default action
return doAction(path, remove, check);
}
var qd = Q.defer();
options = processOptions(options, basePath);
if (options.allBut) {
// creates object representation of whole tree and tests every object if matches file masks
utils.createMatchTreeAsync(basePath, options.allBut)
.then(function (tree) {
options.allButMatchTree = tree;
return check(basePath);
// Remove every path which is in array separately.
var promises = pathsToRemove.map(function (path) {
return qRimraf(path);
});
return Q.all(promises);
})
.then(qd.resolve, qd.reject);
} else {
check(basePath)
.then(qd.resolve, qd.reject);
.then(function () {
// Wrapped into function to prevent returning array from Q.all
deferred.resolve();
}, deferred.reject);
}
return qd.promise;
return deferred.promise;
}
module.exports.async = removeAsync;
//---------------------------------------------------------
// API
//---------------------------------------------------------
module.exports.sync = sync;
module.exports.async = async;
{
"name": "fs-jetpack",
"description": "Higher level API for 'fs' library",
"version": "0.3.0",
"version": "0.4.0",
"author": "Jakub Szwacz <jakub@szwacz.com>",
"dependencies": {
"minimatch": "0.2.x",
"q": "0.9.x"
"minimatch": "0.3.0",
"mkdirp": "0.5.0",
"q": "1.0.1",
"rimraf": "2.2.8"
},
"devDependencies": {
"fs-extra": "0.7.x"
"jasmine-node": "1.14.x",
"fs-extra": "0.9.1"
},
"scripts": {
"test": "jasmine-node spec"
"test": "node_modules/.bin/jasmine-node spec"
},
"main": "main.js",
"keywords": [
"fs", "file system", "mkdirp", "wrench", "fs-extra"
],
"homepage": "https://github.com/szwacz/fs-jetpack",

@@ -25,3 +25,3 @@ "repository": {

},
"licence": "MIT"
"license": "MIT"
}

@@ -1,6 +0,7 @@

#fs-jetpack
fs-jetpack
==========
Attempt to make comprehensive, higher level API for node's [fs library](http://nodejs.org/api/fs.html).
This is an attempt to make comprehensive, higher level API for node's [fs library](http://nodejs.org/api/fs.html), which will be fun to use (see ["Neat tricks fs-jetpack knows"](#how-fun) as a starter).
###Installation
### Installation
```

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

###Usage
### Usage
```javascript

@@ -16,122 +17,143 @@ 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).
#API
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).
All asynchronous methods are promise based, and are using [Q library](https://github.com/kriskowal/q) for that purpose.
```javascript
// Usage of blocking API
try {
jetpack.dir('foo');
} catch (err) {
// Something went wrong
}
Commonly used naming convention in node world is reversed in this library. Asynchronous methods are those with "Async" suffix, all methods without "Async" in 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 with this approach all methods without word "Async" are synchronous so you can very easily distinguish one from another.
// Usage of non-blocking API
jetpack.dirAsync('foo')
.then(function () {
// Done!
}, function (err) {
// Something went wrong
});
```
**Index**
* <a href="#cwdpath">cwd([path])</a>
* <a href="#copyfrom-to-options">copy(from, to, [options])</a>
* <a href="#copyasyncfrom-to-options">copyAsync(from, to, [options])</a>
* <a href="#dirpath-criteria">dir(path, [criteria])</a>
* <a href="#dirasyncpath-criteria">dirAsync(path, [criteria])</a>
* <a href="#existspath">exists(path)</a>
* <a href="#existsasyncpath">existsAsync(path)</a>
* <a href="#filepath-criteria">file(path, [criteria])</a>
* <a href="#fileasyncpath-criteria">fileAsync(path, [criteria])</a>
* <a href="#listpath-options">list(path, [options])</a>
* <a href="#listasyncpath-options">listAsync(path, [options])</a>
* <a href="#pathparts">path([parts...])</a>
* <a href="#readpath-mode">read(path, [mode])</a>
* <a href="#readasyncpath-mode">readAsync(path, [mode])</a>
* <a href="#removepath-options">remove(path, [options])</a>
* <a href="#removeasyncpath-options">removeAsync(path, [options])</a>
* <a href="#writepath-content">write(path, content)</a>
* <a href="#writeasyncpath-content">writeAsync(path, content)</a>
**Methods:**
* [append(path, data)](#append)
* [copy(from, to, [options])](#copy)
* [cwd([path])](#cwd)
* [dir(path, [criteria])](#dir)
* [exists(path)](#exists)
* [file(path, [criteria])](#file)
* [inspect(path)](#inspect)
* [list(path, [mode])](#list)
* [move(from, to)](#move)
* [path(parts...)](#path)
* [read(path, [returnAs], [options])](#read)
* [remove(path, [options])](#remove)
* [rename(path, newName)](#rename)
* [tree(path)](#tree)
* [write(path, data)](#write)
###cwd([path])
Returns Current Working Directory (CWD) path, or creates new jetpack object with different CWD.
## <a name="append"></a> append(path, data)
also **appendAsync(path, data)**
Appends given data to the end of file. If file (or any parent directory) doesn't exist, creates it (or them).
**parameters:**
`path` (optional) path to become new CWD. Could be absolute, or relative. If relative path given new CWD will be resolved basing on current CWD.
`path` the path to file.
`data` data to append (could be `String` or `Buffer`).
**returns:**
If `path` not specified, returns CWD path of this jetpack object. For main instance of fs-jetpack it is always `process.cwd()`.
If `path` specified, returns new jetpack object (totally the same thing as main jetpack). The new object resolves paths according to its inner CWD, not the global one (`process.cwd()`).
Nothing.
## <a name="copy"></a> copy(from, to, [options])
also **copyAsync(from, to, [options])**
Copies given file or directory (with everything inside).
**parameters:**
`from` path to location you want to copy.
`to` path to destination location, where the copy should be placed.
`options` (optional) additional options for customization. Is an `Object` with possible fields:
* `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 patterns) will copy **only** items matching any of specified pattern. Pattern is a `String` of .gitignore-like notation [(read more)](#matching-paths).
* `allBut` (`Array` of patterns) will copy **everything except** items matching any of specified pattern. Pattern is a `String` of .gitignore-like notation [(read more)](#matching-paths). If `only` was also specified this field is ignored.
**returns:**
Nothing.
**examples:**
```javascript
// let's assume that process.cwd() outputs...
console.log(process.cwd()); // '/one/two/three'
// jetpack.cwd() will always return the same value as process.cwd()
console.log(jetpack.cwd()); // '/one/two/three'
// Copies a file (and replaces it if one already exists in "somewhere" direcotry)
jetpack.copy('file.txt', 'somwhere/file.txt', { overwrite: true });
// now let's create new CWD context...
var jetParent = jetpack.cwd('..');
console.log(jetParent.cwd()); // '/one/two'
// ...and use this new context
jetParent.dir('four'); // we just created directory '/one/two/four'
// Copies only ".jpg" files from my_dir
jetpack.copy('my_dir', 'somewhere/my_dir', { only: ['*.jpg'] });
// one CWD context can be used to create next CWD context
var jetParentParent = jetpackContext.cwd('..');
console.log(jetParentParent.cwd()); // '/one'
// Copies everything except "logs" directory inside my_dir
jetpack.copy('my_dir', 'somewhere/my_dir', { allBut: ['my_dir/logs'] });
```
###copy(from, to, [options])
Copies given file or directory.
## <a name="cwd"></a> cwd([path])
Returns Current Working Directory (CWD) for this instance of jetpack, or creates new jetpack object with given path as its internal CWD.
**Note:** fs-jetpack never changes value of `process.cwd()`, the CWD we are talking about here is internal value inside every jetpack instance, and could be completely different than `process.cwd()`.
**parameters:**
`from` path to location you want to copy.
`to` destination path where copy should be placed.
`options` (optional) additional options for customization. Is an `object` with possible fields:
* `overwrite` (default: `'no'`) mode to use if file already exists in destination location. Is a `string` with possible values:
* `'no'` don't allow to replace any file or directory in destination location.
* `'yes'` replace every file already existing.
* `only` (`array` of masks) will copy **only** items matching any of specified masks. Mask is `string` with .gitignore-like notation (see section *"Matching paths .gitignore style"*).
* `allBut` (`array` of masks) will copy **everything except** items matching any of specified masks. If `only` is specified this field is ignored.
* `symlinks` *(TODO, not implemented yet)*
`path` (optional) path to become new CWD. Could be absolute, or relative. If relative path given new CWD will be resolved basing on current CWD of this jetpack instance.
**returns:**
Recently used CWD context.
If `path` not specified, returns CWD path of this jetpack object. For main instance of fs-jetpack it is always `process.cwd()`.
If `path` specified, returns new jetpack object (totally the same thing as main jetpack). The new object resolves paths according to its internal CWD, not the global one (`process.cwd()`).
**examples:**
```javascript
// copy file and replace it if exists
jetpack.copy('/my_file.txt', '/somwhere/my_file.txt', { overwrite: 'yes' });
// Let's assume that process.cwd() outputs...
console.log(process.cwd()); // '/one/two/three'
// jetpack.cwd() will always return the same value as process.cwd()
console.log(jetpack.cwd()); // '/one/two/three'
// copy only .txt files inside my_dir
jetpack.copy('/my_dir', '/somewhere/my_dir', { only: ['*.txt'] });
// Now let's create new CWD context...
var jetParent = jetpack.cwd('..');
console.log(jetParent.cwd()); // '/one/two'
// ...and use this new context.
jetParent.dir('four'); // we just created directory '/one/two/four'
// copy everything except temp directory inside my_dir
jetpack.copy('/my_dir', '/somewhere/my_dir', { allBut: ['my_dir/temp'] });
// One CWD context can be used to create next CWD context.
var jetParentParent = jetParent.cwd('..');
console.log(jetParentParent.cwd()); // '/one'
```
###copyAsync(from, to, [options])
Asynchronous equivalent of `copy()` method. The only difference is that it returns promise.
## <a name="dir"></a> dir(path, [criteria])
also **dirAsync(path, [criteria])**
###dir(path, [criteria])
Ensures that directory meets given criteria. If any criterium is not met it will be after this call.
Ensures that directory on given path meets given criteria. If any criterium is not met it will be after this call.
**parameters:**
`path` path to directory to examine.
`criteria` (optional) criteria to be met by the directory. Is an `object` with possible fields:
`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.
* `mode` ensures directory has specified mode. If not set and directory already exists, current mode will be intact. Value could be number (eg. `0700`) or string (eg. `'700'`).
* `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'`).
**returns:**
New CWD context with directory specified in `path` as CWD.
If `exists` field was set to `false` returned CWD context points to parent directory of given `path`.
Or `undefined` if `exists` was set to `false`.
**examples:**
```javascript
// creates directory if not exists
// Creates directory if doesn't exist
jetpack.dir('new_dir');
// make sure that directory does NOT exist
var notExistsCwd = jetpack.dir('/my_stuff/some_dir', { exists: false });
// if exists == false, returned CWD context refers to parent of specified directory
console.log(notExistsCwd.cwd()) // '/my_stuff'
// Makes sure that directory does NOT exist
jetpack.dir('/my_stuff/some_dir', { exists: false });
// creates directory with mode 0700 (if not exists)
// or make sure that it's empty and has mode 0700 (if exists)
// Makes sure directory mode is 0700 and that it's empty
jetpack.dir('empty_dir', { empty: true, mode: '700' });
// because dir returns new CWD context pointing to just
// created directory you can create dir chains
// Because dir returns new CWD context pointing to just
// created directory you can create dir chains.
jetpack

@@ -143,7 +165,5 @@ .dir('main_dir') // creates 'main_dir'

###dirAsync(path, [criteria])
Asynchronous equivalent of `dir()` method. The only difference is that it returns promise.
## <a name="exists"></a> exists(path)
also **existsAsync(path)**
###exists(path)
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".

@@ -155,10 +175,8 @@

* `"file"` if path is a file.
* `"other"` if path exists, but is of different "type".
* `"other"` if none of the above.
###existsAsync(path)
Asynchronous equivalent of `exists()` method. The only difference is that it returns promise.
## <a name="file"></a> file(path, [criteria])
also **fileAsync(path, [criteria])**
###file(path, [criteria])
Ensures that file meets given criteria. If any criterium is not met it will be after this call.

@@ -168,20 +186,20 @@

`path` path to file to examine.
`criteria` (optional) criteria to be met by the directory. Is an `object` with possible fields:
`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 forced to be empty. If `exists = false` this field is ignored.
* `content` (`string`, `buffer`, `object` ot `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.
* `mode` ensures file has specified mode. If not set and file already exists, current mode will be intact. Value could be number (eg. `0700`) or string (eg. `'700'`).
* `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.
* `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'`).
**returns:**
Recently used CWD context.
Jetpack object you called this method on (self).
**examples:**
```javascript
// creates file if not exists
// Creates file if doesn't exist
jetpack.file('something.txt');
// ensure file does NOT exist (if exists will be deleted)
// 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!'
// Creates file with mode '777' and content 'Hello World!'
jetpack.file('hello.txt', { mode: '777', content: 'Hello World!' });

@@ -191,57 +209,60 @@ ```

###fileAsync(path, [criteria])
Asynchronous equivalent of `file()` method. The only difference is that it returns promise.
## <a name="inspect"></a> inspect(path)
also **inspectAsync(path)**
Inspects given path (replacement for fs.stat).
###list(path, [options])
Creates list of files inside given path (and more).
**parameters:**
`path` path to file/directory to list.
`options` (optional) additional options for customization. Is an `object` with possible fields:
* `includeRoot` (default: `false`) whether returned data should contain root directory (dir provided in `path`), or only its children.
* `subDirs` (default: `false`) whether subdirectories should be also listed.
* `symlinks` *(TODO, not implemented yet)*
`path` path to inspect.
**returns:**
`array` of `objects` with most basic settings, and tree structure with more sophisticated settings.
**examples:**
**returns:**
`null` if given path doens't exist.
Otherwise `Object` of structure:
```javascript
jetpack.list('rootDir'); // will return array of objects
jetpack.list('rootDir', { includeRoot: true, subDirs: true });
// will return tree structure of this shape:
{
name: 'rootDir', // there is one root object if includeRoot was set to true
type: 'dir',
path: '/myStuff/rootDir', // absolute path to this location
size: 150, // (in bytes) in case of directory this number is combined size of all children
parent: null, // this directory actually has parent, but it was not scanned, so is not reachable
children: [ // contains Array of all dirs and files inside this directory
{
name: 'myFile.txt',
type: 'file',
path: '/myStuff/rootDir/myFile.txt',
parent: [Object], // reference to parent object
size: 150
}
]
name: "my_dir",
type: "file", // possible values: "file", "dir"
size: 123 // size in bytes, this is returned only for files
}
```
Yep, not so much for now. Will be extended in the future.
###listAsync(path, [options])
Asynchronous equivalent of `list()` method. The only difference is that it returns promise.
## <a name="list"></a> list(path, [mode])
also **listAsync(path, [mode])**
Lists the contents of directory.
###path([parts...])
Returns path resolved to current CWD.
**parameters:**
`path` path to directory you would like to list.
`mode` (optional) the degree of accuracy you would like to get back. Possible values:
* `'simple'` (default) returns just a list of filenames (the same as `fs.readdir()`)
* `'inspect'` performs [inspect](#inspect) on every item, and returns array of those objects
**returns:**
Array of strings or objects depending on call properies.
## <a name="move"></a> move(from, to)
also **moveAsync(from, to)**
Moves given path to new location.
**parameters:**
`parts` (optional) strings to join and resolve as path (as many as you like).
`from` path to directory or file you want to move.
`to` path where the thing should be moved.
**returns:**
Resolved path as String.
Nothing.
## <a name="path"></a> path(parts...)
Returns path resolved to internal CWD of this jetpack object.
**parameters:**
`parts` strings to join and resolve as path (as many as you like).
**returns:**
Resolved path as string.
**examples:**

@@ -256,125 +277,203 @@ ```javascript

###read(path, [mode])
Reads content of file.
## <a name="read"></a> read(path, [returnAs], [options])
also **readAsync(path, [returnAs], [options])**
Reads content of file. If file on given path doesn't exist returns `null` instead of throwing `ENOENT` error.
**parameters:**
`path` path to file.
`mode` (optional) how the content of file should be returned. Is a String with possible values:
`returnAs` (optional) how the content of file should be returned. Is a string with possible values:
* `'utf8'` (default) content will be returned as UTF-8 String.
* `'buf'` content will be returned as Buffer.
* `'json'` content will be returned as parsed JSON object.
* `'jsonWithDates'` content will be returned as parsed JSON object, and date strings in [ISO format](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/toISOString) will be automatically turned into Date objects.
`options` (optional) is an object with possible fields:
* `safe` if set to `true` the file will be read in ["safe mode"](#safe-mode).
**returns:**
File content in specified format.
File content in specified format, or `null` if file doesn't exist.
###readAsync(path, [mode])
Asynchronous equivalent of `read()` method. The only difference is that it returns promise.
## <a name="remove"></a> remove(path, [options])
also **removeAsync(path, [options])**
###remove(path, [options])
Deletes given path, no matter what it is (file or directory).
**parameters:**
`path` path to file/directory to remove.
`options` (optional) additional conditions to removal process. Is an `object` with possible fields:
* `only` (`array` of masks) will delete **only** items matching any of specified masks. Mask is `string` with .gitignore-like notation (see section *"Matching paths .gitignore style"*).
* `allBut` (`array` of masks) will delete **everything except** items matching any of specified masks. Mask is `string` with .gitignore-like notation (see section *"Matching paths .gitignore style"*). If `only` is specified this field is ignored.
`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 patterns) will delete **only** items matching any of specified pattern. Pattern is a `String` of .gitignore-like notation [(read more on that)](#matching-paths).
* `allBut` (`Array` of patterns) will delete **everything except** items matching any of specified pattern. Pattern is a `String` of .gitignore-like notation [(read more on that)](#matching-paths). If `only` was also specified this field is ignored.
**returns:**
CWD context of directory parent to removed path.
Nothing.
**examples:**
```javascript
// will delete 'notes.txt'
// Deletes file
jetpack.remove('my_work/notes.txt');
// will delete directory 'important_stuff' and everything inside
// Deletes directory "important_stuff" and everything inside
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
// 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 'my_app/user_data' intact
// 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' ] });
```
###removeAsync(path, [options])
Asynchronous equivalent of `remove()` method. The only difference is that it returns promise.
## <a name="rename"></a> rename(path, newName)
also **renameAsync(path, newName)**
###write(path, content)
Writes content to file.
Renames given file or directory.
**parameters:**
`path` path to file.
`content` data to be written. This could be `string`, `buffer`, `object` or `array` (if last two used, the data will be outputed into file as JSON).
`path` path to thing you want to change name.
`newName` new name for this thing (not full path, just a name).
**returns:**
Recently used CWD context.
Nothing.
###writeAsync(path, content)
Asynchronous equivalent of `write()` method. The only difference is that it returns promise.
## <a name="tree"></a> tree(path)
also **treeAsync(path)**
Calls [inspect](#inspect) recursively on given path so it creates tree of all directories and sub-directories inside it.
#Matching paths .gitignore style
**parameters:**
`path` the path to inspect.
For filtering options (`only` and `allBut` properties) this library uses notation familiar to you from .gitignore file (thanks to [minimatch](https://github.com/isaacs/minimatch)).
Few examples:
**returns:**
`null` if given path doesn't exist.
Otherwise tree of inspect objects like:
```javascript
'work' // matches any item (file or dir) named "work", nevermind in which subdirectory it is
'*.txt' // matches any .txt file, nevermind in which subdirectory it is
'my_documents/*' // matches any file inside directory "my_documents"
'logs/2013-*.log' // matches any log file from 2013
'logs/2013-12-??.log' // matches any log file from december 2013
'my_documents/**/work' // matches any item named "work" inside "my_documents" and its subdirs
{
name: 'my_dir',
type: 'dir',
size: 123, // this is combined size of all items in this directory
children: [
{
name: 'empty',
type: 'dir',
size: 0, // the directory is empty
children: []
},{
name: 'file.txt',
type: 'file',
size: 123
}
]
}
```
#Chaining jetpack commands
## <a name="write"></a> write(path, data)
also **writeAsync(path, data)**
Because almost every jetpack method returns CWD context, you can chain commands together. What lets you operate on files in more declarative style, for example:
```javascript
/*
We want to create file structure:
my_stuff
|- work
| |- hello.txt
|- photos
| |- me.jpg
*/
Writes data to file.
// Synchronous way
**parameters:**
`path` path to file.
`content` data to be written. This could be `String`, `Buffer`, `Object` or `Array` (if last two used, the data will be outputed into file as JSON).
jetpack
.dir('my_stuff')
.dir('work')
.file('hello.txt', { content: 'Hello world!' })
.cwd('..') // go back to parent directory
.dir('photos')
.file('me.jpg', { content: new Buffer('should be image bytes') });
**returns:**
Nothing.
// Asynchronous way (unfortunately not that pretty)
# <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
.dirAsync('my_stuff')
.then(function (context) {
return context.dirAsync('work');
.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 (context) {
return context.fileAsync('hello.txt', { content: 'Hello world!' });
})
.then(function (context) {
return context.cwd('..').dirAsync('photos');
})
.then(function (context) {
return context.fileAsync('me.jpg', {
content: new Buffer('should be image bytes')
});
});
.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
[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 process will crash during the operation, you are basically srewed. The old file content is lost, because you overwritten it. And the new file is empty or written only partially. Fs-jetpack has built-in "safe mode", which helps you get rid of 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', { safe: true, content: 'Hello universe!' });
```
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.

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