Files

Return to Package Diff Home.
Brought to you by Intrinsic.

Package Diff: tmp @ 0.0.33 .. 0.1.0

lib/tmp.js

@@ -10,21 +10,18 @@
* Module dependencies.
*/
const fs = require('fs');
+const os = require('os');
const path = require('path');
const crypto = require('crypto');
-const osTmpDir = require('os-tmpdir');
-const _c = process.binding('constants');
+const _c = fs.constants && os.constants ?
+ { fs: fs.constants, os: os.constants } :
+ process.binding('constants');
+const rimraf = require('rimraf');
/*
* The working inner variables.
*/
const
- /**
- * The temporary directory.
- * @type {string}
- */
- tmpDir = osTmpDir(),
-
// the random characters to choose from
RANDOM_CHARS = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz',
@@ -40,12 +37,15 @@
DIR_MODE = 448 /* 0o700 */,
FILE_MODE = 384 /* 0o600 */,
+ EXIT = 'exit',
+
+ SIGINT = 'SIGINT',
+
// this will hold the objects need to be removed on exit
_removeObjects = [];
var
- _gracefulCleanup = false,
- _uncaughtException = false;
+ _gracefulCleanup = false;
/**
* Random name generator based on crypto.
@@ -96,10 +96,12 @@
* @private
*/
function _parseArguments(options, callback) {
- if (typeof options == 'function') {
- return [callback || {}, options];
+ /* istanbul ignore else */
+ if (typeof options === 'function') {
+ return [{}, options];
}
+ /* istanbul ignore else */
if (_isUndefined(options)) {
return [{}, callback];
}
@@ -115,21 +117,37 @@
* @private
*/
function _generateTmpName(opts) {
- if (opts.name) {
+
+ const tmpDir = _getTmpDir();
+
+ // fail early on missing tmp dir
+ if (isBlank(opts.dir) && isBlank(tmpDir)) {
+ throw new Error('No tmp dir specified');
+ }
+
+ /* istanbul ignore else */
+ if (!isBlank(opts.name)) {
return path.join(opts.dir || tmpDir, opts.name);
}
// mkstemps like template
+ // opts.template has already been guarded in tmpName() below
+ /* istanbul ignore else */
if (opts.template) {
- return opts.template.replace(TEMPLATE_PATTERN, _randomChars(6));
+ var template = opts.template;
+ // make sure that we prepend the tmp path if none was given
+ /* istanbul ignore else */
+ if (path.basename(template) === template)
+ template = path.join(opts.dir || tmpDir, template);
+ return template.replace(TEMPLATE_PATTERN, _randomChars(6));
}
// prefix and postfix
const name = [
- opts.prefix || 'tmp-',
+ (isBlank(opts.prefix) ? 'tmp-' : opts.prefix),
process.pid,
_randomChars(12),
- opts.postfix || ''
+ (opts.postfix ? opts.postfix : '')
].join('');
return path.join(opts.dir || tmpDir, name);
@@ -146,20 +164,25 @@
args = _parseArguments(options, callback),
opts = args[0],
cb = args[1],
- tries = opts.name ? 1 : opts.tries || DEFAULT_TRIES;
+ tries = !isBlank(opts.name) ? 1 : opts.tries || DEFAULT_TRIES;
+ /* istanbul ignore else */
if (isNaN(tries) || tries < 0)
return cb(new Error('Invalid tries'));
+ /* istanbul ignore else */
if (opts.template && !opts.template.match(TEMPLATE_PATTERN))
return cb(new Error('Invalid template provided'));
(function _getUniqueName() {
+ try {
const name = _generateTmpName(opts);
// check whether the path exists then retry if needed
fs.stat(name, function (err) {
+ /* istanbul ignore else */
if (!err) {
+ /* istanbul ignore else */
if (tries-- > 0) return _getUniqueName();
return cb(new Error('Could not get a unique tmp filename, max tries reached ' + name));
@@ -167,6 +190,9 @@
cb(null, name);
});
+ } catch (err) {
+ cb(err);
+ }
}());
}
@@ -181,11 +207,13 @@
var
args = _parseArguments(options),
opts = args[0],
- tries = opts.name ? 1 : opts.tries || DEFAULT_TRIES;
+ tries = !isBlank(opts.name) ? 1 : opts.tries || DEFAULT_TRIES;
+ /* istanbul ignore else */
if (isNaN(tries) || tries < 0)
throw new Error('Invalid tries');
+ /* istanbul ignore else */
if (opts.template && !opts.template.match(TEMPLATE_PATTERN))
throw new Error('Invalid template provided');
@@ -213,18 +241,19 @@
opts = args[0],
cb = args[1];
- opts.postfix = (_isUndefined(opts.postfix)) ? '.tmp' : opts.postfix;
-
// gets a temporary filename
tmpName(opts, function _tmpNameCreated(err, name) {
+ /* istanbul ignore else */
if (err) return cb(err);
// create and open the file
fs.open(name, CREATE_FLAGS, opts.mode || FILE_MODE, function _fileCreated(err, fd) {
+ /* istanbul ignore else */
if (err) return cb(err);
if (opts.discardDescriptor) {
return fs.close(fd, function _discardCallback(err) {
+ /* istanbul ignore else */
if (err) {
// Low probability, and the file exists, so this could be
// ignored. If it isn't we certainly need to unlink the
@@ -242,6 +271,7 @@
cb(null, name, undefined, _prepareTmpFileRemoveCallback(name, -1, opts));
});
}
+ /* istanbul ignore else */
if (opts.detachDescriptor) {
return cb(null, name, fd, _prepareTmpFileRemoveCallback(name, -1, opts));
}
@@ -262,11 +292,10 @@
args = _parseArguments(options),
opts = args[0];
- opts.postfix = opts.postfix || '.tmp';
-
const discardOrDetachDescriptor = opts.discardDescriptor || opts.detachDescriptor;
const name = tmpNameSync(opts);
var fd = fs.openSync(name, CREATE_FLAGS, opts.mode || FILE_MODE);
+ /* istanbul ignore else */
if (opts.discardDescriptor) {
fs.closeSync(fd);
fd = undefined;
@@ -280,43 +309,6 @@
}
/**
- * Removes files and folders in a directory recursively.
- *
- * @param {string} root
- * @private
- */
-function _rmdirRecursiveSync(root) {
- const dirs = [root];
-
- do {
- var
- dir = dirs.pop(),
- deferred = false,
- files = fs.readdirSync(dir);
-
- for (var i = 0, length = files.length; i < length; i++) {
- var
- file = path.join(dir, files[i]),
- stat = fs.lstatSync(file); // lstat so we don't recurse into symlinked directories
-
- if (stat.isDirectory()) {
- if (!deferred) {
- deferred = true;
- dirs.push(dir);
- }
- dirs.push(file);
- } else {
- fs.unlinkSync(file);
- }
- }
-
- if (!deferred) {
- fs.rmdirSync(dir);
- }
- } while (dirs.length !== 0);
-}
-
-/**
* Creates a temporary directory.
*
* @param {(Options|dirCallback)} options the options or the callback function
@@ -330,10 +322,12 @@
// gets a temporary filename
tmpName(opts, function _tmpNameCreated(err, name) {
+ /* istanbul ignore else */
if (err) return cb(err);
// create the directory
fs.mkdir(name, opts.mode || DIR_MODE, function _dirCreated(err) {
+ /* istanbul ignore else */
if (err) return cb(err);
cb(null, name, _prepareTmpDirRemoveCallback(name, opts));
@@ -363,49 +357,95 @@
}
/**
- * Prepares the callback for removal of the temporary file.
+ * Removes files asynchronously.
*
- * @param {string} name the path of the file
- * @param {number} fd file descriptor
- * @param {Object} opts
- * @returns {fileCallback}
+ * @param {Object} fdPath
+ * @param {Function} next
* @private
*/
-function _prepareTmpFileRemoveCallback(name, fd, opts) {
- const removeCallback = _prepareRemoveCallback(function _removeCallback(fdPath) {
- try {
- if (0 <= fdPath[0]) {
- fs.closeSync(fdPath[0]);
- }
- }
- catch (e) {
- // under some node/windows related circumstances, a temporary file
- // may have not be created as expected or the file was already closed
- // by the user, in which case we will simply ignore the error
- if (!isEBADF(e) && !isENOENT(e)) {
+function _removeFileAsync(fdPath, next) {
+ const _handler = function (err) {
+ if (err && !isENOENT(err)) {
// reraise any unanticipated error
- throw e;
+ return next(err);
}
+ next();
}
+
+ if (0 <= fdPath[0])
+ fs.close(fdPath[0], function (err) {
+ fs.unlink(fdPath[1], _handler);
+ });
+ else fs.unlink(fdPath[1], _handler);
+}
+
+/**
+ * Removes files synchronously.
+ *
+ * @param {Object} fdPath
+ * @private
+ */
+function _removeFileSync(fdPath) {
+ try {
+ if (0 <= fdPath[0]) fs.closeSync(fdPath[0]);
+ } catch (e) {
+ // reraise any unanticipated error
+ if (!isEBADF(e) && !isENOENT(e)) throw e;
+ } finally {
try {
fs.unlinkSync(fdPath[1]);
}
catch (e) {
- if (!isENOENT(e)) {
// reraise any unanticipated error
- throw e;
+ if (!isENOENT(e)) throw e;
}
}
- }, [fd, name]);
+}
- if (!opts.keep) {
- _removeObjects.unshift(removeCallback);
- }
+/**
+ * Prepares the callback for removal of the temporary file.
+ *
+ * @param {string} name the path of the file
+ * @param {number} fd file descriptor
+ * @param {Object} opts
+ * @returns {fileCallback}
+ * @private
+ */
+function _prepareTmpFileRemoveCallback(name, fd, opts) {
+ const removeCallbackSync = _prepareRemoveCallback(_removeFileSync, [fd, name]);
+ const removeCallback = _prepareRemoveCallback(_removeFileAsync, [fd, name], removeCallbackSync);
+
+ if (!opts.keep) _removeObjects.unshift(removeCallbackSync);
return removeCallback;
}
/**
+ * Simple wrapper for rimraf.
+ *
+ * @param {string} dirPath
+ * @param {Function} next
+ * @private
+ */
+function _rimrafRemoveDirWrapper(dirPath, next) {
+ rimraf(dirPath, next);
+}
+
+/**
+ * Simple wrapper for rimraf.sync.
+ *
+ * @param {string} dirPath
+ * @private
+ */
+function _rimrafRemoveDirSyncWrapper(dirPath, next) {
+ try {
+ return next(null, rimraf.sync(dirPath));
+ } catch (err) {
+ return next(err);
+ }
+}
+
+/**
* Prepares the callback for removal of the temporary directory.
*
* @param {string} name
@@ -414,12 +454,11 @@
* @private
*/
function _prepareTmpDirRemoveCallback(name, opts) {
- const removeFunction = opts.unsafeCleanup ? _rmdirRecursiveSync : fs.rmdirSync.bind(fs);
- const removeCallback = _prepareRemoveCallback(removeFunction, name);
-
- if (!opts.keep) {
- _removeObjects.unshift(removeCallback);
- }
+ const removeFunction = opts.unsafeCleanup ? _rimrafRemoveDirWrapper : fs.rmdir.bind(fs);
+ const removeFunctionSync = opts.unsafeCleanup ? _rimrafRemoveDirSyncWrapper : fs.rmdirSync.bind(fs);
+ const removeCallbackSync = _prepareRemoveCallback(removeFunctionSync, name);
+ const removeCallback = _prepareRemoveCallback(removeFunction, name, removeCallbackSync);
+ if (!opts.keep) _removeObjects.unshift(removeCallbackSync);
return removeCallback;
}
@@ -432,21 +471,32 @@
* @returns {Function}
* @private
*/
-function _prepareRemoveCallback(removeFunction, arg) {
+function _prepareRemoveCallback(removeFunction, arg, cleanupCallbackSync) {
var called = false;
return function _cleanupCallback(next) {
+ next = next || function () {};
if (!called) {
- const index = _removeObjects.indexOf(_cleanupCallback);
- if (index >= 0) {
- _removeObjects.splice(index, 1);
- }
+ const toRemove = cleanupCallbackSync || _cleanupCallback;
+ const index = _removeObjects.indexOf(toRemove);
+ /* istanbul ignore else */
+ if (index >= 0) _removeObjects.splice(index, 1);
called = true;
+ // sync?
+ if (removeFunction.length === 1) {
+ try {
removeFunction(arg);
+ return next(null);
}
-
- if (next) next(null);
+ catch (err) {
+ // if no next is provided and since we are
+ // in silent cleanup mode on process exit,
+ // we will ignore the error
+ return next(err);
+ }
+ } else return removeFunction(arg, next);
+ } else return next(new Error('cleanup callback has already been called'));
};
}
@@ -456,15 +506,14 @@
* @private
*/
function _garbageCollector() {
- if (_uncaughtException && !_gracefulCleanup) {
- return;
- }
+ /* istanbul ignore else */
+ if (!_gracefulCleanup) return;
// the function being called removes itself from _removeObjects,
// loop until _removeObjects is empty
while (_removeObjects.length) {
try {
- _removeObjects[0].call(null);
+ _removeObjects[0]();
} catch (e) {
// already removed?
}
@@ -490,51 +539,143 @@
* which will differ between the supported node versions.
*
* - Node >= 7.0:
- * error.code {String}
- * error.errno {String|Number} any numerical value will be negated
+ * error.code {string}
+ * error.errno {string|number} any numerical value will be negated
*
* - Node >= 6.0 < 7.0:
- * error.code {String}
- * error.errno {Number} negated
+ * error.code {string}
+ * error.errno {number} negated
*
* - Node >= 4.0 < 6.0: introduces SystemError
- * error.code {String}
- * error.errno {Number} negated
+ * error.code {string}
+ * error.errno {number} negated
*
* - Node >= 0.10 < 4.0:
- * error.code {Number} negated
+ * error.code {number} negated
* error.errno n/a
*/
function isExpectedError(error, code, errno) {
- return error.code == code || error.code == errno;
+ return error.code === code || error.code === errno;
}
/**
- * Sets the graceful cleanup.
+ * Helper which determines whether a string s is blank, that is undefined, or empty or null.
*
- * Also removes the created files and directories when an uncaught exception occurs.
+ * @private
+ * @param {string} s
+ * @returns {Boolean} true whether the string s is blank, false otherwise
+ */
+function isBlank(s) {
+ return s === null || s === undefined || !s.trim();
+}
+
+/**
+ * Sets the graceful cleanup.
*/
function setGracefulCleanup() {
_gracefulCleanup = true;
}
-const version = process.versions.node.split('.').map(function (value) {
- return parseInt(value, 10);
-});
+/**
+ * Returns the currently configured tmp dir from os.tmpdir().
+ *
+ * @private
+ * @returns {string} the currently configured tmp dir
+ */
+function _getTmpDir() {
+ return os.tmpdir();
+}
-if (version[0] === 0 && (version[1] < 9 || version[1] === 9 && version[2] < 5)) {
- process.addListener('uncaughtException', function _uncaughtExceptionThrown(err) {
- _uncaughtException = true;
- _garbageCollector();
+/**
+ * If there are multiple different versions of tmp in place, make sure that
+ * we recognize the old listeners.
+ *
+ * @param {Function} listener
+ * @private
+ * @returns {Boolean} true whether listener is a legacy listener
+ */
+function _is_legacy_listener(listener) {
+ return (listener.name === '_exit' || listener.name === '_uncaughtExceptionThrown')
+ && listener.toString().indexOf('_garbageCollector();') > -1;
+}
+
+/**
+ * Safely install SIGINT listener.
+ *
+ * NOTE: this will only work on OSX and Linux.
+ *
+ * @private
+ */
+function _safely_install_sigint_listener() {
- throw err;
+ const listeners = process.listeners(SIGINT);
+ const existingListeners = [];
+ for (let i = 0, length = listeners.length; i < length; i++) {
+ const lstnr = listeners[i];
+ /* istanbul ignore else */
+ if (lstnr.name === '_tmp$sigint_listener') {
+ existingListeners.push(lstnr);
+ process.removeListener(SIGINT, lstnr);
+ }
+ }
+ process.on(SIGINT, function _tmp$sigint_listener(doExit) {
+ for (let i = 0, length = existingListeners.length; i < length; i++) {
+ // let the existing listener do the garbage collection (e.g. jest sandbox)
+ try {
+ existingListeners[i](false);
+ } catch (err) {
+ // ignore
+ }
+ }
+ try {
+ // force the garbage collector even it is called again in the exit listener
+ _garbageCollector();
+ } finally {
+ if (!!doExit) {
+ process.exit(0);
+ }
+ }
});
}
-process.addListener('exit', function _exit(code) {
- if (code) _uncaughtException = true;
+/**
+ * Safely install process exit listener.
+ *
+ * @private
+ */
+function _safely_install_exit_listener() {
+ const listeners = process.listeners(EXIT);
+
+ // collect any existing listeners
+ const existingListeners = [];
+ for (let i = 0, length = listeners.length; i < length; i++) {
+ const lstnr = listeners[i];
+ /* istanbul ignore else */
+ // TODO: remove support for legacy listeners once release 1.0.0 is out
+ if (lstnr.name === '_tmp$safe_listener' || _is_legacy_listener(lstnr)) {
+ // we must forget about the uncaughtException listener, hopefully it is ours
+ if (lstnr.name !== '_uncaughtExceptionThrown') {
+ existingListeners.push(lstnr);
+ }
+ process.removeListener(EXIT, lstnr);
+ }
+ }
+ // TODO: what was the data parameter good for?
+ process.addListener(EXIT, function _tmp$safe_listener(data) {
+ for (let i = 0, length = existingListeners.length; i < length; i++) {
+ // let the existing listener do the garbage collection (e.g. jest sandbox)
+ try {
+ existingListeners[i](data);
+ } catch (err) {
+ // ignore
+ }
+ }
_garbageCollector();
-});
+ });
+}
+
+_safely_install_exit_listener();
+_safely_install_sigint_listener();
/**
* Configuration options.
@@ -546,6 +687,7 @@
* @property {?string} dir the tmp directory to use
* @property {?string} prefix prefix for the generated name
* @property {?string} postfix postfix for the generated name
+ * @property {?boolean} unsafeCleanup recursively removes the created temporary directory, even when it's not empty
*/
/**
@@ -597,7 +739,16 @@
*/
// exporting all the needed methods
-module.exports.tmpdir = tmpDir;
+
+// evaluate os.tmpdir() lazily, mainly for simplifying testing but it also will
+// allow users to reconfigure the temporary directory
+Object.defineProperty(module.exports, 'tmpdir', {
+ enumerable: true,
+ configurable: false,
+ get: function () {
+ return _getTmpDir();
+ }
+});
module.exports.dir = dir;
module.exports.dirSync = dirSync;

package.json

@@ -1,6 +1,6 @@
{
"name": "tmp",
- "version": "0.0.33",
+ "version": "0.1.0",
"description": "Temporary file and directory creator",
"author": "KARASZI István <github@spam.raszi.hu> (http://raszi.hu/)",
"keywords": [
@@ -19,20 +19,25 @@
"url": "http://github.com/raszi/node-tmp/issues"
},
"engines": {
- "node": ">=0.6.0"
+ "node": ">=6"
},
"dependencies": {
- "os-tmpdir": "~1.0.2"
+ "rimraf": "^2.6.3"
},
"devDependencies": {
- "vows": "~0.7.0"
+ "eslint": "^4.19.1",
+ "eslint-plugin-mocha": "^5.0.0",
+ "istanbul": "^0.4.5",
+ "mocha": "^5.1.1"
},
"main": "lib/tmp.js",
"files": [
"lib/"
],
"scripts": {
- "test": "vows test/*-test.js",
+ "lint": "eslint lib --env mocha test",
+ "clean": "rm -Rf ./coverage",
+ "test": "npm run clean && istanbul cover ./node_modules/mocha/bin/_mocha --report none --print none --dir ./coverage/json -u exports -R test/*-test.js && istanbul report --root ./coverage/json html && istanbul report text-summary",
"doc": "jsdoc -c .jsdoc.json"
}
}

README.md

@@ -15,14 +15,47 @@
Tmp offers both an asynchronous and a synchronous API. For all API calls, all
the parameters are optional. There also exists a promisified version of the
-API, see (5) under references below.
+API, see [tmp-promise][5].
Tmp uses crypto for determining random file names, or, when using templates,
a six letter random identifier. And just in case that you do not have that much
entropy left on your system, Tmp will fall back to pseudo random numbers.
You can set whether you want to remove the temporary file on process exit or
-not, and the destination directory can also be set.
+not.
+
+If you do not want to store your temporary directories and files in the
+standard OS temporary directory, then you are free to override that as well.
+
+## An Important Note on Compatibility
+
+### Version 0.1.0
+
+Since version 0.1.0, all support for node versions < 0.10.0 has been dropped.
+
+Most importantly, any support for earlier versions of node-tmp was also dropped.
+
+If you still require node versions < 0.10.0, then you must limit your node-tmp
+dependency to versions below 0.1.0.
+
+### Version 0.0.33
+
+Since version 0.0.33, all support for node versions < 0.8 has been dropped.
+
+If you still require node version 0.8, then you must limit your node-tmp
+dependency to version 0.0.33.
+
+For node versions < 0.8 you must limit your node-tmp dependency to
+versions < 0.0.33.
+
+### Node Versions < 8.12.0
+
+The SIGINT handler will not work correctly with versions of NodeJS < 8.12.0.
+
+### Windows
+
+Signal handlers for SIGINT will not work. Pressing CTRL-C will leave behind
+temporary files and directories.
## How to install
@@ -237,10 +270,14 @@
Creates a new temporary directory with mode `0700` and filename like `/tmp/tmp-nk2J1u`.
+IMPORTANT NOTE: template no longer accepts a path. Use the dir option instead if you
+require tmp to create your temporary filesystem object in a different place than the
+default `tmp.tmpdir`.
+
```javascript
var tmp = require('tmp');
-tmp.dir({ template: '/tmp/tmp-XXXXXX' }, function _tempDirCreated(err, path) {
+tmp.dir({ template: 'tmp-XXXXXX' }, function _tempDirCreated(err, path) {
if (err) throw err;
console.log('Dir: ', path);
@@ -254,18 +291,23 @@
```javascript
var tmp = require('tmp');
-var tmpobj = tmp.dirSync({ template: '/tmp/tmp-XXXXXX' });
+var tmpobj = tmp.dirSync({ template: 'tmp-XXXXXX' });
console.log('Dir: ', tmpobj.name);
```
### Asynchronous filename generation
-The `tmpName()` function accepts the `prefix`, `postfix`, `dir`, etc. parameters also:
+Using `tmpName()` you can create temporary file names asynchronously.
+The function accepts all standard options, e.g. `prefix`, `postfix`, `dir`, and so on.
+
+You can also leave out the options altogether and just call the function with a callback as first parameter.
```javascript
var tmp = require('tmp');
-tmp.tmpName({ template: '/tmp/tmp-XXXXXX' }, function _tempNameGenerated(err, path) {
+var options = {};
+
+tmp.tmpName(options, function _tempNameGenerated(err, path) {
if (err) throw err;
console.log('Created temporary filename: ', path);
@@ -275,10 +317,12 @@
### Synchronous filename generation
The `tmpNameSync()` function works similarly to `tmpName()`.
+Again, you can leave out the options altogether and just invoke the function without any parameters.
```javascript
var tmp = require('tmp');
-var tmpname = tmp.tmpNameSync({ template: '/tmp/tmp-XXXXXX' });
+var options = {};
+var tmpname = tmp.tmpNameSync(options);
console.log('Created temporary filename: ', tmpname);
```
@@ -303,8 +347,8 @@
* `template`: [`mkstemp`][3] like filename template, no default
* `dir`: the optional temporary directory, fallbacks to system default (guesses from environment)
* `tries`: how many times should the function try to get a unique filename before giving up, default `3`
- * `keep`: signals that the temporary file or directory should not be deleted on exit, default is `false`, means delete
- * Please keep in mind that it is recommended in this case to call the provided `cleanupCallback` function manually.
+ * `keep`: signals that the temporary file or directory should not be deleted on exit, default is `false`
+ * In order to clean up, you will have to call the provided `cleanupCallback` function manually.
* `unsafeCleanup`: recursively removes the created temporary directory, even when it's not empty. default is `false`
[1]: http://nodejs.org/