Comparing version 0.1.0 to 0.2.0
676
lib/tmp.js
@@ -16,5 +16,3 @@ /*! | ||
const crypto = require('crypto'); | ||
const _c = fs.constants && os.constants ? | ||
{ fs: fs.constants, os: os.constants } : | ||
process.binding('constants'); | ||
const _c = { fs: fs.constants, os: os.constants }; | ||
const rimraf = require('rimraf'); | ||
@@ -35,125 +33,23 @@ | ||
// constants are off on the windows platform and will not match the actual errno codes | ||
IS_WIN32 = os.platform() === 'win32', | ||
EBADF = _c.EBADF || _c.os.errno.EBADF, | ||
ENOENT = _c.ENOENT || _c.os.errno.ENOENT, | ||
DIR_MODE = 448 /* 0o700 */, | ||
FILE_MODE = 384 /* 0o600 */, | ||
DIR_MODE = 0o700 /* 448 */, | ||
FILE_MODE = 0o600 /* 384 */, | ||
EXIT = 'exit', | ||
SIGINT = 'SIGINT', | ||
// this will hold the objects need to be removed on exit | ||
_removeObjects = []; | ||
_removeObjects = [], | ||
var | ||
// API change in fs.rmdirSync leads to error when passing in a second parameter, e.g. the callback | ||
FN_RMDIR_SYNC = fs.rmdirSync.bind(fs), | ||
FN_RIMRAF_SYNC = rimraf.sync; | ||
let | ||
_gracefulCleanup = false; | ||
/** | ||
* Random name generator based on crypto. | ||
* Adapted from http://blog.tompawlak.org/how-to-generate-random-values-nodejs-javascript | ||
* | ||
* @param {number} howMany | ||
* @returns {string} the generated random name | ||
* @private | ||
*/ | ||
function _randomChars(howMany) { | ||
var | ||
value = [], | ||
rnd = null; | ||
// make sure that we do not fail because we ran out of entropy | ||
try { | ||
rnd = crypto.randomBytes(howMany); | ||
} catch (e) { | ||
rnd = crypto.pseudoRandomBytes(howMany); | ||
} | ||
for (var i = 0; i < howMany; i++) { | ||
value.push(RANDOM_CHARS[rnd[i] % RANDOM_CHARS.length]); | ||
} | ||
return value.join(''); | ||
} | ||
/** | ||
* Checks whether the `obj` parameter is defined or not. | ||
* | ||
* @param {Object} obj | ||
* @returns {boolean} true if the object is undefined | ||
* @private | ||
*/ | ||
function _isUndefined(obj) { | ||
return typeof obj === 'undefined'; | ||
} | ||
/** | ||
* Parses the function arguments. | ||
* | ||
* This function helps to have optional arguments. | ||
* | ||
* @param {(Options|Function)} options | ||
* @param {Function} callback | ||
* @returns {Array} parsed arguments | ||
* @private | ||
*/ | ||
function _parseArguments(options, callback) { | ||
/* istanbul ignore else */ | ||
if (typeof options === 'function') { | ||
return [{}, options]; | ||
} | ||
/* istanbul ignore else */ | ||
if (_isUndefined(options)) { | ||
return [{}, callback]; | ||
} | ||
return [options, callback]; | ||
} | ||
/** | ||
* Generates a new temporary name. | ||
* | ||
* @param {Object} opts | ||
* @returns {string} the new random name according to opts | ||
* @private | ||
*/ | ||
function _generateTmpName(opts) { | ||
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) { | ||
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 = [ | ||
(isBlank(opts.prefix) ? 'tmp-' : opts.prefix), | ||
process.pid, | ||
_randomChars(12), | ||
(opts.postfix ? opts.postfix : '') | ||
].join(''); | ||
return path.join(opts.dir || tmpDir, name); | ||
} | ||
/** | ||
* Gets a temporary file name. | ||
@@ -165,16 +61,14 @@ * | ||
function tmpName(options, callback) { | ||
var | ||
const | ||
args = _parseArguments(options, callback), | ||
opts = args[0], | ||
cb = args[1], | ||
tries = !isBlank(opts.name) ? 1 : opts.tries || DEFAULT_TRIES; | ||
cb = args[1]; | ||
/* istanbul ignore else */ | ||
if (isNaN(tries) || tries < 0) | ||
return cb(new Error('Invalid tries')); | ||
try { | ||
_assertAndSanitizeOptions(opts); | ||
} catch (err) { | ||
return cb(err); | ||
} | ||
/* istanbul ignore else */ | ||
if (opts.template && !opts.template.match(TEMPLATE_PATTERN)) | ||
return cb(new Error('Invalid template provided')); | ||
let tries = opts.tries; | ||
(function _getUniqueName() { | ||
@@ -210,15 +104,9 @@ try { | ||
function tmpNameSync(options) { | ||
var | ||
const | ||
args = _parseArguments(options), | ||
opts = args[0], | ||
tries = !isBlank(opts.name) ? 1 : opts.tries || DEFAULT_TRIES; | ||
opts = args[0]; | ||
/* istanbul ignore else */ | ||
if (isNaN(tries) || tries < 0) | ||
throw new Error('Invalid tries'); | ||
_assertAndSanitizeOptions(opts); | ||
/* istanbul ignore else */ | ||
if (opts.template && !opts.template.match(TEMPLATE_PATTERN)) | ||
throw new Error('Invalid template provided'); | ||
let tries = opts.tries; | ||
do { | ||
@@ -239,7 +127,7 @@ const name = _generateTmpName(opts); | ||
* | ||
* @param {(Options|fileCallback)} options the config options or the callback function | ||
* @param {(Options|null|undefined|fileCallback)} options the config options or the callback function or null or undefined | ||
* @param {?fileCallback} callback | ||
*/ | ||
function file(options, callback) { | ||
var | ||
const | ||
args = _parseArguments(options, callback), | ||
@@ -256,30 +144,16 @@ opts = args[0], | ||
fs.open(name, CREATE_FLAGS, opts.mode || FILE_MODE, function _fileCreated(err, fd) { | ||
/* istanbul ignore else */ | ||
/* istanbu 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 | ||
// file, and if that fails too its error is more | ||
// important. | ||
try { | ||
fs.unlinkSync(name); | ||
} catch (e) { | ||
if (!isENOENT(e)) { | ||
err = e; | ||
} | ||
} | ||
return cb(err); | ||
} | ||
cb(null, name, undefined, _prepareTmpFileRemoveCallback(name, -1, opts)); | ||
return fs.close(fd, function _discardCallback(possibleErr) { | ||
// the chance of getting an error on close here is rather low and might occur in the most edgiest cases only | ||
return cb(possibleErr, name, undefined, _prepareTmpFileRemoveCallback(name, -1, opts, false)); | ||
}); | ||
} else { | ||
// detachDescriptor passes the descriptor whereas discardDescriptor closes it, either way, we no longer care | ||
// about the descriptor | ||
const discardOrDetachDescriptor = opts.discardDescriptor || opts.detachDescriptor; | ||
cb(null, name, fd, _prepareTmpFileRemoveCallback(name, discardOrDetachDescriptor ? -1 : fd, opts, false)); | ||
} | ||
/* istanbul ignore else */ | ||
if (opts.detachDescriptor) { | ||
return cb(null, name, fd, _prepareTmpFileRemoveCallback(name, -1, opts)); | ||
} | ||
cb(null, name, fd, _prepareTmpFileRemoveCallback(name, fd, opts)); | ||
}); | ||
@@ -297,3 +171,3 @@ }); | ||
function fileSync(options) { | ||
var | ||
const | ||
args = _parseArguments(options), | ||
@@ -314,3 +188,3 @@ opts = args[0]; | ||
fd: fd, | ||
removeCallback: _prepareTmpFileRemoveCallback(name, discardOrDetachDescriptor ? -1 : fd, opts) | ||
removeCallback: _prepareTmpFileRemoveCallback(name, discardOrDetachDescriptor ? -1 : fd, opts, true) | ||
}; | ||
@@ -326,3 +200,3 @@ } | ||
function dir(options, callback) { | ||
var | ||
const | ||
args = _parseArguments(options, callback), | ||
@@ -342,3 +216,3 @@ opts = args[0], | ||
cb(null, name, _prepareTmpDirRemoveCallback(name, opts)); | ||
cb(null, name, _prepareTmpDirRemoveCallback(name, opts, false)); | ||
}); | ||
@@ -356,3 +230,3 @@ }); | ||
function dirSync(options) { | ||
var | ||
const | ||
args = _parseArguments(options), | ||
@@ -366,3 +240,3 @@ opts = args[0]; | ||
name: name, | ||
removeCallback: _prepareTmpDirRemoveCallback(name, opts) | ||
removeCallback: _prepareTmpDirRemoveCallback(name, opts, true) | ||
}; | ||
@@ -380,3 +254,3 @@ } | ||
const _handler = function (err) { | ||
if (err && !isENOENT(err)) { | ||
if (err && !_isENOENT(err)) { | ||
// reraise any unanticipated error | ||
@@ -386,6 +260,6 @@ return next(err); | ||
next(); | ||
} | ||
}; | ||
if (0 <= fdPath[0]) | ||
fs.close(fdPath[0], function (err) { | ||
fs.close(fdPath[0], function () { | ||
fs.unlink(fdPath[1], _handler); | ||
@@ -403,2 +277,3 @@ }); | ||
function _removeFileSync(fdPath) { | ||
let rethrownException = null; | ||
try { | ||
@@ -408,3 +283,3 @@ if (0 <= fdPath[0]) fs.closeSync(fdPath[0]); | ||
// reraise any unanticipated error | ||
if (!isEBADF(e) && !isENOENT(e)) throw e; | ||
if (!_isEBADF(e) && !_isENOENT(e)) throw e; | ||
} finally { | ||
@@ -416,5 +291,8 @@ try { | ||
// reraise any unanticipated error | ||
if (!isENOENT(e)) throw e; | ||
if (!_isENOENT(e)) rethrownException = e; | ||
} | ||
} | ||
if (rethrownException !== null) { | ||
throw rethrownException; | ||
} | ||
} | ||
@@ -425,58 +303,41 @@ | ||
* | ||
* Returns either a sync callback or a async callback depending on whether | ||
* fileSync or file was called, which is expressed by the sync parameter. | ||
* | ||
* @param {string} name the path of the file | ||
* @param {number} fd file descriptor | ||
* @param {Object} opts | ||
* @returns {fileCallback} | ||
* @param {boolean} sync | ||
* @returns {fileCallback | fileCallbackSync} | ||
* @private | ||
*/ | ||
function _prepareTmpFileRemoveCallback(name, fd, opts) { | ||
const removeCallbackSync = _prepareRemoveCallback(_removeFileSync, [fd, name]); | ||
const removeCallback = _prepareRemoveCallback(_removeFileAsync, [fd, name], removeCallbackSync); | ||
function _prepareTmpFileRemoveCallback(name, fd, opts, sync) { | ||
const removeCallbackSync = _prepareRemoveCallback(_removeFileSync, [fd, name], sync); | ||
const removeCallback = _prepareRemoveCallback(_removeFileAsync, [fd, name], sync, removeCallbackSync); | ||
if (!opts.keep) _removeObjects.unshift(removeCallbackSync); | ||
return removeCallback; | ||
return sync ? removeCallbackSync : removeCallback; | ||
} | ||
/** | ||
* Simple wrapper for rimraf. | ||
* Prepares the callback for removal of the temporary directory. | ||
* | ||
* @param {string} dirPath | ||
* @param {Function} next | ||
* @private | ||
*/ | ||
function _rimrafRemoveDirWrapper(dirPath, next) { | ||
rimraf(dirPath, next); | ||
} | ||
/** | ||
* Simple wrapper for rimraf.sync. | ||
* Returns either a sync callback or a async callback depending on whether | ||
* tmpFileSync or tmpFile was called, which is expressed by the sync parameter. | ||
* | ||
* @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 | ||
* @param {Object} opts | ||
* @param {boolean} sync | ||
* @returns {Function} the callback | ||
* @private | ||
*/ | ||
function _prepareTmpDirRemoveCallback(name, opts) { | ||
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); | ||
function _prepareTmpDirRemoveCallback(name, opts, sync) { | ||
const removeFunction = opts.unsafeCleanup ? rimraf : fs.rmdir.bind(fs); | ||
const removeFunctionSync = opts.unsafeCleanup ? FN_RIMRAF_SYNC : FN_RMDIR_SYNC; | ||
const removeCallbackSync = _prepareRemoveCallback(removeFunctionSync, name, sync); | ||
const removeCallback = _prepareRemoveCallback(removeFunction, name, sync, removeCallbackSync); | ||
if (!opts.keep) _removeObjects.unshift(removeCallbackSync); | ||
return removeCallback; | ||
return sync ? removeCallbackSync : removeCallback; | ||
} | ||
@@ -487,13 +348,21 @@ | ||
* | ||
* The cleanup callback is save to be called multiple times. | ||
* Subsequent invocations will be ignored. | ||
* | ||
* @param {Function} removeFunction | ||
* @param {Object} arg | ||
* @returns {Function} | ||
* @param {string} fileOrDirName | ||
* @param {boolean} sync | ||
* @param {cleanupCallbackSync?} cleanupCallbackSync | ||
* @returns {cleanupCallback | cleanupCallbackSync} | ||
* @private | ||
*/ | ||
function _prepareRemoveCallback(removeFunction, arg, cleanupCallbackSync) { | ||
var called = false; | ||
function _prepareRemoveCallback(removeFunction, fileOrDirName, sync, cleanupCallbackSync) { | ||
let called = false; | ||
// if sync is true, the next parameter will be ignored | ||
return function _cleanupCallback(next) { | ||
next = next || function () {}; | ||
/* istanbul ignore else */ | ||
if (!called) { | ||
// remove cleanupCallback from cache | ||
const toRemove = cleanupCallbackSync || _cleanupCallback; | ||
@@ -505,16 +374,8 @@ const index = _removeObjects.indexOf(toRemove); | ||
called = true; | ||
// sync? | ||
if (removeFunction.length === 1) { | ||
try { | ||
removeFunction(arg); | ||
return 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')); | ||
if (sync || removeFunction === FN_RMDIR_SYNC || removeFunction == FN_RIMRAF_SYNC) { | ||
return removeFunction(fileOrDirName); | ||
} else { | ||
return removeFunction(fileOrDirName, next || function() {}); | ||
} | ||
} | ||
}; | ||
@@ -544,169 +405,293 @@ } | ||
/** | ||
* Helper for testing against EBADF to compensate changes made to Node 7.x under Windows. | ||
* Random name generator based on crypto. | ||
* Adapted from http://blog.tompawlak.org/how-to-generate-random-values-nodejs-javascript | ||
* | ||
* @param {number} howMany | ||
* @returns {string} the generated random name | ||
* @private | ||
*/ | ||
function isEBADF(error) { | ||
return isExpectedError(error, -EBADF, 'EBADF'); | ||
function _randomChars(howMany) { | ||
let | ||
value = [], | ||
rnd = null; | ||
// make sure that we do not fail because we ran out of entropy | ||
try { | ||
rnd = crypto.randomBytes(howMany); | ||
} catch (e) { | ||
rnd = crypto.pseudoRandomBytes(howMany); | ||
} | ||
for (var i = 0; i < howMany; i++) { | ||
value.push(RANDOM_CHARS[rnd[i] % RANDOM_CHARS.length]); | ||
} | ||
return value.join(''); | ||
} | ||
/** | ||
* Helper for testing against ENOENT to compensate changes made to Node 7.x under Windows. | ||
* Helper which determines whether a string s is blank, that is undefined, or empty or null. | ||
* | ||
* @private | ||
* @param {string} s | ||
* @returns {Boolean} true whether the string s is blank, false otherwise | ||
*/ | ||
function isENOENT(error) { | ||
return isExpectedError(error, -ENOENT, 'ENOENT'); | ||
function _isBlank(s) { | ||
return s === null || _isUndefined(s) || !s.trim(); | ||
} | ||
/** | ||
* Helper to determine whether the expected error code matches the actual code and errno, | ||
* which will differ between the supported node versions. | ||
* Checks whether the `obj` parameter is defined or not. | ||
* | ||
* - Node >= 7.0: | ||
* error.code {string} | ||
* error.errno {string|number} any numerical value will be negated | ||
* @param {Object} obj | ||
* @returns {boolean} true if the object is undefined | ||
* @private | ||
*/ | ||
function _isUndefined(obj) { | ||
return typeof obj === 'undefined'; | ||
} | ||
/** | ||
* Parses the function arguments. | ||
* | ||
* - Node >= 6.0 < 7.0: | ||
* error.code {string} | ||
* error.errno {number} negated | ||
* This function helps to have optional arguments. | ||
* | ||
* - Node >= 4.0 < 6.0: introduces SystemError | ||
* error.code {string} | ||
* error.errno {number} negated | ||
* | ||
* - Node >= 0.10 < 4.0: | ||
* error.code {number} negated | ||
* error.errno n/a | ||
* @param {(Options|null|undefined|Function)} options | ||
* @param {?Function} callback | ||
* @returns {Array} parsed arguments | ||
* @private | ||
*/ | ||
function isExpectedError(error, code, errno) { | ||
return error.code === code || error.code === errno; | ||
function _parseArguments(options, callback) { | ||
/* istanbul ignore else */ | ||
if (typeof options === 'function') { | ||
return [{}, options]; | ||
} | ||
/* istanbul ignore else */ | ||
if (_isUndefined(options)) { | ||
return [{}, callback]; | ||
} | ||
return [options, callback]; | ||
} | ||
/** | ||
* Helper which determines whether a string s is blank, that is undefined, or empty or null. | ||
* Generates a new temporary name. | ||
* | ||
* @param {Object} opts | ||
* @returns {string} the new random name according to opts | ||
* @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(); | ||
function _generateTmpName(opts) { | ||
const tmpDir = _getTmpDir(); | ||
/* istanbul ignore else */ | ||
if (!_isUndefined(opts.name)) | ||
return path.join(tmpDir, opts.dir, opts.name); | ||
/* istanbul ignore else */ | ||
if (!_isUndefined(opts.template)) | ||
return path.join(tmpDir, opts.dir, opts.template).replace(TEMPLATE_PATTERN, _randomChars(6)); | ||
// prefix and postfix | ||
const name = [ | ||
opts.prefix ? opts.prefix : 'tmp', | ||
'-', | ||
process.pid, | ||
'-', | ||
_randomChars(12), | ||
opts.postfix ? '-' + opts.postfix : '' | ||
].join(''); | ||
return path.join(tmpDir, opts.dir, name); | ||
} | ||
/** | ||
* Sets the graceful cleanup. | ||
* Asserts whether the specified options are valid, also sanitizes options and provides sane defaults for missing | ||
* options. | ||
* | ||
* @param {Options} options | ||
* @private | ||
*/ | ||
function setGracefulCleanup() { | ||
_gracefulCleanup = true; | ||
function _assertAndSanitizeOptions(options) { | ||
const tmpDir = _getTmpDir(); | ||
/* istanbul ignore else */ | ||
if (!_isUndefined(options.name)) | ||
_assertIsRelative(options.name, 'name', tmpDir); | ||
/* istanbul ignore else */ | ||
if (!_isUndefined(options.dir)) | ||
_assertIsRelative(options.dir, 'dir', tmpDir); | ||
/* istanbul ignore else */ | ||
if (!_isUndefined(options.template)) { | ||
_assertIsRelative(options.template, 'template', tmpDir); | ||
if (!options.template.match(TEMPLATE_PATTERN)) | ||
throw new Error(`Invalid template, found "${options.template}".`); | ||
} | ||
/* istanbul ignore else */ | ||
if (!_isUndefined(options.tries) && isNaN(options.tries) || options.tries < 0) | ||
throw new Error(`Invalid tries, found "${options.tries}".`); | ||
// if a name was specified we will try once | ||
options.tries = _isUndefined(options.name) ? options.tries || DEFAULT_TRIES : 1; | ||
options.keep = !!options.keep; | ||
options.detachDescriptor = !!options.detachDescriptor; | ||
options.discardDescriptor = !!options.discardDescriptor; | ||
options.unsafeCleanup = !!options.unsafeCleanup; | ||
// sanitize dir, also keep (multiple) blanks if the user, purportedly sane, requests us to | ||
options.dir = _isUndefined(options.dir) ? '' : path.relative(tmpDir, _resolvePath(options.dir, tmpDir)); | ||
options.template = _isUndefined(options.template) ? undefined : path.relative(tmpDir, _resolvePath(options.template, tmpDir)); | ||
// sanitize further if template is relative to options.dir | ||
options.template = _isBlank(options.template) ? undefined : path.relative(options.dir, options.template); | ||
// for completeness' sake only, also keep (multiple) blanks if the user, purportedly sane, requests us to | ||
options.name = _isUndefined(options.name) ? undefined : _sanitizeName(options.name); | ||
options.prefix = _isUndefined(options.prefix) ? '' : options.prefix; | ||
options.postfix = _isUndefined(options.postfix) ? '' : options.postfix; | ||
} | ||
/** | ||
* Returns the currently configured tmp dir from os.tmpdir(). | ||
* Resolve the specified path name in respect to tmpDir. | ||
* | ||
* The specified name might include relative path components, e.g. ../ | ||
* so we need to resolve in order to be sure that is is located inside tmpDir | ||
* | ||
* @param name | ||
* @param tmpDir | ||
* @returns {string} | ||
* @private | ||
* @returns {string} the currently configured tmp dir | ||
*/ | ||
function _getTmpDir() { | ||
return os.tmpdir(); | ||
function _resolvePath(name, tmpDir) { | ||
const sanitizedName = _sanitizeName(name); | ||
if (sanitizedName.startsWith(tmpDir)) { | ||
return path.resolve(sanitizedName); | ||
} else { | ||
return path.resolve(path.join(tmpDir, sanitizedName)); | ||
} | ||
} | ||
/** | ||
* If there are multiple different versions of tmp in place, make sure that | ||
* we recognize the old listeners. | ||
* Sanitize the specified path name by removing all quote characters. | ||
* | ||
* @param {Function} listener | ||
* @param name | ||
* @returns {string} | ||
* @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; | ||
function _sanitizeName(name) { | ||
if (_isBlank(name)) { | ||
return name; | ||
} | ||
return name.replace(/["']/g, ''); | ||
} | ||
/** | ||
* Safely install SIGINT listener. | ||
* Asserts whether specified name is relative to the specified tmpDir. | ||
* | ||
* NOTE: this will only work on OSX and Linux. | ||
* | ||
* @param {string} name | ||
* @param {string} option | ||
* @param {string} tmpDir | ||
* @throws {Error} | ||
* @private | ||
*/ | ||
function _safely_install_sigint_listener() { | ||
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); | ||
function _assertIsRelative(name, option, tmpDir) { | ||
if (option === 'name') { | ||
// assert that name is not absolute and does not contain a path | ||
if (path.isAbsolute(name)) | ||
throw new Error(`${option} option must not contain an absolute path, found "${name}".`); | ||
// must not fail on valid .<name> or ..<name> or similar such constructs | ||
let basename = path.basename(name); | ||
if (basename === '..' || basename === '.' || basename !== name) | ||
throw new Error(`${option} option must not contain a path, found "${name}".`); | ||
} | ||
else { // if (option === 'dir' || option === 'template') { | ||
// assert that dir or template are relative to tmpDir | ||
if (path.isAbsolute(name) && !name.startsWith(tmpDir)) { | ||
throw new Error(`${option} option must be relative to "${tmpDir}", found "${name}".`); | ||
} | ||
let resolvedPath = _resolvePath(name, tmpDir); | ||
if (!resolvedPath.startsWith(tmpDir)) | ||
throw new Error(`${option} option must be relative to "${tmpDir}", found "${resolvedPath}".`); | ||
} | ||
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); | ||
} | ||
} | ||
}); | ||
} | ||
/** | ||
* Safely install process exit listener. | ||
* Helper for testing against EBADF to compensate changes made to Node 7.x under Windows. | ||
* | ||
* @private | ||
*/ | ||
function _safely_install_exit_listener() { | ||
const listeners = process.listeners(EXIT); | ||
function _isEBADF(error) { | ||
return _isExpectedError(error, -EBADF, 'EBADF'); | ||
} | ||
// 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(); | ||
}); | ||
/** | ||
* Helper for testing against ENOENT to compensate changes made to Node 7.x under Windows. | ||
* | ||
* @private | ||
*/ | ||
function _isENOENT(error) { | ||
return _isExpectedError(error, -ENOENT, 'ENOENT'); | ||
} | ||
_safely_install_exit_listener(); | ||
_safely_install_sigint_listener(); | ||
/** | ||
* Helper to determine whether the expected error code matches the actual code and errno, | ||
* which will differ between the supported node versions. | ||
* | ||
* - Node >= 7.0: | ||
* error.code {string} | ||
* error.errno {number} any numerical value will be negated | ||
* | ||
* CAVEAT | ||
* | ||
* On windows, the errno for EBADF is -4083 but os.constants.errno.EBADF is different and we must assume that ENOENT | ||
* is no different here. | ||
* | ||
* @param {SystemError} error | ||
* @param {number} errno | ||
* @param {string} code | ||
* @private | ||
*/ | ||
function _isExpectedError(error, errno, code) { | ||
return IS_WIN32 ? error.code === code : error.code === code && error.errno === errno; | ||
} | ||
/** | ||
* Sets the graceful cleanup. | ||
* | ||
* If graceful cleanup is set, tmp will remove all controlled temporary objects on process exit, otherwise the | ||
* temporary objects will remain in place, waiting to be cleaned up on system restart or otherwise scheduled temporary | ||
* object removals. | ||
*/ | ||
function setGracefulCleanup() { | ||
_gracefulCleanup = true; | ||
} | ||
/** | ||
* Returns the currently configured tmp dir from os.tmpdir(). | ||
* | ||
* @private | ||
* @returns {string} the currently configured tmp dir | ||
*/ | ||
function _getTmpDir() { | ||
return path.resolve(_sanitizeName(os.tmpdir())); | ||
} | ||
// Install process exit listener | ||
process.addListener(EXIT, _garbageCollector); | ||
/** | ||
* Configuration options. | ||
* | ||
* @typedef {Object} Options | ||
* @property {?boolean} keep the temporary object (file or dir) will not be garbage collected | ||
* @property {?number} tries the number of tries before give up the name generation | ||
* @property (?int) mode the access mode, defaults are 0o700 for directories and 0o600 for files | ||
* @property {?string} template the "mkstemp" like filename template | ||
* @property {?string} name fix name | ||
* @property {?string} dir the tmp directory to use | ||
* @property {?string} name fixed name relative to tmpdir or the specified dir option | ||
* @property {?string} dir tmp directory relative to the system 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 | ||
* @property {?boolean} detachDescriptor detaches the file descriptor, caller is responsible for closing the file, tmp will no longer try closing the file during garbage collection | ||
* @property {?boolean} discardDescriptor discards the file descriptor (closes file, fd is -1), tmp will no longer try closing the file during garbage collection | ||
*/ | ||
@@ -717,3 +702,3 @@ | ||
* @property {string} name the name of the file | ||
* @property {string} fd the file descriptor | ||
* @property {string} fd the file descriptor or -1 if the fd has been discarded | ||
* @property {fileCallback} removeCallback the callback function to remove the file | ||
@@ -738,3 +723,3 @@ */ | ||
* @param {string} name the temporary file name | ||
* @param {number} fd the file descriptor | ||
* @param {number} fd the file descriptor or -1 if the fd had been discarded | ||
* @param {cleanupCallback} fn the cleanup callback function | ||
@@ -744,2 +729,10 @@ */ | ||
/** | ||
* @callback fileCallbackSync | ||
* @param {?Error} err the error object if anything goes wrong | ||
* @param {string} name the temporary file name | ||
* @param {number} fd the file descriptor or -1 if the fd had been discarded | ||
* @param {cleanupCallbackSync} fn the cleanup callback function | ||
*/ | ||
/** | ||
* @callback dirCallback | ||
@@ -752,9 +745,22 @@ * @param {?Error} err the error object if anything goes wrong | ||
/** | ||
* @callback dirCallbackSync | ||
* @param {?Error} err the error object if anything goes wrong | ||
* @param {string} name the temporary file name | ||
* @param {cleanupCallbackSync} fn the cleanup callback function | ||
*/ | ||
/** | ||
* Removes the temporary created file or directory. | ||
* | ||
* @callback cleanupCallback | ||
* @param {simpleCallback} [next] function to call after entry was removed | ||
* @param {simpleCallback} [next] function to call whenever the tmp object needs to be removed | ||
*/ | ||
/** | ||
* Removes the temporary created file or directory. | ||
* | ||
* @callback cleanupCallbackSync | ||
*/ | ||
/** | ||
* Callback function for function composition. | ||
@@ -761,0 +767,0 @@ * @see {@link https://github.com/raszi/node-tmp/issues/57|raszi/node-tmp#57} |
{ | ||
"name": "tmp", | ||
"version": "0.1.0", | ||
"version": "0.2.0", | ||
"description": "Temporary file and directory creator", | ||
"author": "KARASZI István <github@spam.raszi.hu> (http://raszi.hu/)", | ||
"contributors": [ | ||
"Carsten Klein <trancesilken@gmail.com> (https://github.com/silkentrance)" | ||
], | ||
"keywords": [ | ||
@@ -22,12 +25,12 @@ "temporary", | ||
"engines": { | ||
"node": ">=6" | ||
"node": ">=8.17.0" | ||
}, | ||
"dependencies": { | ||
"rimraf": "^2.6.3" | ||
"rimraf": "^3.0.0" | ||
}, | ||
"devDependencies": { | ||
"eslint": "^4.19.1", | ||
"eslint-plugin-mocha": "^5.0.0", | ||
"eslint": "^6.3.0", | ||
"eslint-plugin-mocha": "^6.1.1", | ||
"istanbul": "^0.4.5", | ||
"mocha": "^5.1.1" | ||
"mocha": "^6.2.0" | ||
}, | ||
@@ -34,0 +37,0 @@ "main": "lib/tmp.js", |
@@ -32,2 +32,4 @@ # Tmp | ||
See the [CHANGELOG](./CHANGELOG.md) for more information. | ||
### Version 0.1.0 | ||
@@ -52,11 +54,2 @@ | ||
### 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 | ||
@@ -77,3 +70,3 @@ | ||
```javascript | ||
var tmp = require('tmp'); | ||
const tmp = require('tmp'); | ||
@@ -98,5 +91,5 @@ tmp.file(function _tempFileCreated(err, path, fd, cleanupCallback) { | ||
```javascript | ||
var tmp = require('tmp'); | ||
const tmp = require('tmp'); | ||
var tmpobj = tmp.fileSync(); | ||
const tmpobj = tmp.fileSync(); | ||
console.log('File: ', tmpobj.name); | ||
@@ -122,3 +115,3 @@ console.log('Filedescriptor: ', tmpobj.fd); | ||
```javascript | ||
var tmp = require('tmp'); | ||
const tmp = require('tmp'); | ||
@@ -143,5 +136,5 @@ tmp.dir(function _tempDirCreated(err, path, cleanupCallback) { | ||
```javascript | ||
var tmp = require('tmp'); | ||
const tmp = require('tmp'); | ||
var tmpobj = tmp.dirSync(); | ||
const tmpobj = tmp.dirSync(); | ||
console.log('Dir: ', tmpobj.name); | ||
@@ -162,3 +155,3 @@ // Manual cleanup | ||
```javascript | ||
var tmp = require('tmp'); | ||
const tmp = require('tmp'); | ||
@@ -177,5 +170,5 @@ tmp.tmpName(function _tempNameGenerated(err, path) { | ||
```javascript | ||
var tmp = require('tmp'); | ||
const tmp = require('tmp'); | ||
var name = tmp.tmpNameSync(); | ||
const name = tmp.tmpNameSync(); | ||
console.log('Created temporary filename: ', name); | ||
@@ -191,5 +184,5 @@ ``` | ||
```javascript | ||
var tmp = require('tmp'); | ||
const tmp = require('tmp'); | ||
tmp.file({ mode: 0644, prefix: 'prefix-', postfix: '.txt' }, function _tempFileCreated(err, path, fd) { | ||
tmp.file({ mode: 0o644, prefix: 'prefix-', postfix: '.txt' }, function _tempFileCreated(err, path, fd) { | ||
if (err) throw err; | ||
@@ -207,5 +200,5 @@ | ||
```javascript | ||
var tmp = require('tmp'); | ||
const tmp = require('tmp'); | ||
var tmpobj = tmp.fileSync({ mode: 0644, prefix: 'prefix-', postfix: '.txt' }); | ||
const tmpobj = tmp.fileSync({ mode: 0o644, prefix: 'prefix-', postfix: '.txt' }); | ||
console.log('File: ', tmpobj.name); | ||
@@ -232,3 +225,3 @@ console.log('Filedescriptor: ', tmpobj.fd); | ||
```javascript | ||
var tmp = require('tmp'); | ||
const tmp = require('tmp'); | ||
@@ -243,3 +236,3 @@ tmp.file({ discardDescriptor: true }, function _tempFileCreated(err, path, fd, cleanupCallback) { | ||
```javascript | ||
var tmp = require('tmp'); | ||
const tmp = require('tmp'); | ||
@@ -261,5 +254,5 @@ tmp.file({ detachDescriptor: true }, function _tempFileCreated(err, path, fd, cleanupCallback) { | ||
```javascript | ||
var tmp = require('tmp'); | ||
const tmp = require('tmp'); | ||
tmp.dir({ mode: 0750, prefix: 'myTmpDir_' }, function _tempDirCreated(err, path) { | ||
tmp.dir({ mode: 0o750, prefix: 'myTmpDir_' }, function _tempDirCreated(err, path) { | ||
if (err) throw err; | ||
@@ -276,5 +269,5 @@ | ||
```javascript | ||
var tmp = require('tmp'); | ||
const tmp = require('tmp'); | ||
var tmpobj = tmp.dirSync({ mode: 0750, prefix: 'myTmpDir_' }); | ||
const tmpobj = tmp.dirSync({ mode: 0750, prefix: 'myTmpDir_' }); | ||
console.log('Dir: ', tmpobj.name); | ||
@@ -292,3 +285,3 @@ ``` | ||
```javascript | ||
var tmp = require('tmp'); | ||
const tmp = require('tmp'); | ||
@@ -307,5 +300,5 @@ tmp.dir({ template: 'tmp-XXXXXX' }, function _tempDirCreated(err, path) { | ||
```javascript | ||
var tmp = require('tmp'); | ||
const tmp = require('tmp'); | ||
var tmpobj = tmp.dirSync({ template: 'tmp-XXXXXX' }); | ||
const tmpobj = tmp.dirSync({ template: 'tmp-XXXXXX' }); | ||
console.log('Dir: ', tmpobj.name); | ||
@@ -322,5 +315,5 @@ ``` | ||
```javascript | ||
var tmp = require('tmp'); | ||
const tmp = require('tmp'); | ||
var options = {}; | ||
const options = {}; | ||
@@ -340,5 +333,5 @@ tmp.tmpName(options, function _tempNameGenerated(err, path) { | ||
```javascript | ||
var tmp = require('tmp'); | ||
var options = {}; | ||
var tmpname = tmp.tmpNameSync(options); | ||
const tmp = require('tmp'); | ||
const options = {}; | ||
const tmpname = tmp.tmpNameSync(options); | ||
console.log('Created temporary filename: ', tmpname); | ||
@@ -349,7 +342,10 @@ ``` | ||
One may want to cleanup the temporary files even when an uncaught exception | ||
occurs. To enforce this, you can call the `setGracefulCleanup()` method: | ||
If graceful cleanup is set, tmp will remove all controlled temporary objects on process exit, otherwise the | ||
temporary objects will remain in place, waiting to be cleaned up on system restart or otherwise scheduled temporary | ||
object removal. | ||
To enforce this, you can call the `setGracefulCleanup()` method: | ||
```javascript | ||
var tmp = require('tmp'); | ||
const tmp = require('tmp'); | ||
@@ -363,7 +359,15 @@ tmp.setGracefulCleanup(); | ||
* `mode`: the file mode to create with, it fallbacks to `0600` on file creation and `0700` on directory creation | ||
* `prefix`: the optional prefix, fallbacks to `tmp-` if not provided | ||
* `postfix`: the optional postfix, fallbacks to `.tmp` on file creation | ||
* `template`: [`mkstemp`][3] like filename template, no default | ||
* `dir`: the optional temporary directory, fallbacks to system default (guesses from environment) | ||
* `name`: a fixed name that overrides random name generation, the name must be relative and must not contain path segments | ||
* `mode`: the file mode to create with, falls back to `0o600` on file creation and `0o700` on directory creation | ||
* `prefix`: the optional prefix, defaults to `tmp` | ||
* `postfix`: the optional postfix | ||
* `template`: [`mkstemp`][3] like filename template, no default, can be either an absolute or a relative path that resolves | ||
to a relative path of the system's default temporary directory, must include `XXXXXX` once for random name generation, e.g. | ||
'foo/bar/XXXXXX'. Absolute paths are also fine as long as they are relative to os.tmpdir(). | ||
Any directories along the so specified path must exist, otherwise a ENOENT error will be thrown upon access, | ||
as tmp will not check the availability of the path, nor will it establish the requested path for you. | ||
* `dir`: the optional temporary directory that must be relative to the system's default temporary directory. | ||
absolute paths are fine as long as they point to a location under the system's default temporary directory. | ||
Any directories along the so specified path must exist, otherwise a ENOENT error will be thrown upon access, | ||
as tmp will not check the availability of the path, nor will it establish the requested path for you. | ||
* `tries`: how many times should the function try to get a unique filename before giving up, default `3` | ||
@@ -373,2 +377,4 @@ * `keep`: signals that the temporary file or directory should not be deleted on exit, default is `false` | ||
* `unsafeCleanup`: recursively removes the created temporary directory, even when it's not empty. default is `false` | ||
* `detachDescriptor`: detaches the file descriptor, caller is responsible for closing the file, tmp will no longer try closing the file during garbage collection | ||
* `discardDescriptor`: discards the file descriptor (closes file, fd is -1), tmp will no longer try closing the file during garbage collection | ||
@@ -375,0 +381,0 @@ [1]: http://nodejs.org/ |
39345
5
674
365
+ Addedrimraf@3.0.2(transitive)
- Removedrimraf@2.7.1(transitive)
Updatedrimraf@^3.0.0