write-file-atomic
Advanced tools
Comparing version 2.4.3 to 3.0.0
234
index.js
@@ -7,13 +7,16 @@ 'use strict' | ||
var fs = require('graceful-fs') | ||
var MurmurHash3 = require('imurmurhash') | ||
var onExit = require('signal-exit') | ||
var path = require('path') | ||
var activeFiles = {} | ||
const fs = require('fs') | ||
const MurmurHash3 = require('imurmurhash') | ||
const onExit = require('signal-exit') | ||
const path = require('path') | ||
const isTypedArray = require('is-typedarray') | ||
const typedArrayToBuffer = require('typedarray-to-buffer') | ||
const { promisify } = require('util') | ||
const activeFiles = {} | ||
// if we run inside of a worker_thread, `process.pid` is not unique | ||
/* istanbul ignore next */ | ||
var threadId = (function getId () { | ||
const threadId = (function getId () { | ||
try { | ||
var workerThreads = require('worker_threads') | ||
const workerThreads = require('worker_threads') | ||
@@ -28,3 +31,3 @@ /// if we are in main thread, this is set to `0` | ||
var invocations = 0 | ||
let invocations = 0 | ||
function getTmpname (filename) { | ||
@@ -40,3 +43,3 @@ return filename + '.' + | ||
function cleanupOnExit (tmpfile) { | ||
return function () { | ||
return () => { | ||
try { | ||
@@ -48,23 +51,4 @@ fs.unlinkSync(typeof tmpfile === 'function' ? tmpfile() : tmpfile) | ||
function writeFile (filename, data, options, callback) { | ||
if (options) { | ||
if (options instanceof Function) { | ||
callback = options | ||
options = {} | ||
} else if (typeof options === 'string') { | ||
options = { encoding: options } | ||
} | ||
} else { | ||
options = {} | ||
} | ||
var Promise = options.Promise || global.Promise | ||
var truename | ||
var fd | ||
var tmpfile | ||
/* istanbul ignore next -- The closure only gets called when onExit triggers */ | ||
var removeOnExitHandler = onExit(cleanupOnExit(() => tmpfile)) | ||
var absoluteName = path.resolve(filename) | ||
new Promise(function serializeSameFile (resolve) { | ||
function serializeActiveFile (absoluteName) { | ||
return new Promise(resolve => { | ||
// make a queue if it doesn't already exist | ||
@@ -75,104 +59,74 @@ if (!activeFiles[absoluteName]) activeFiles[absoluteName] = [] | ||
if (activeFiles[absoluteName].length === 1) resolve() // kick off the first one | ||
}).then(function getRealPath () { | ||
return new Promise(function (resolve) { | ||
fs.realpath(filename, function (_, realname) { | ||
truename = realname || filename | ||
tmpfile = getTmpname(truename) | ||
resolve() | ||
}) | ||
}) | ||
}).then(function stat () { | ||
return new Promise(function stat (resolve) { | ||
if (options.mode && options.chown) resolve() | ||
else { | ||
// Either mode or chown is not explicitly set | ||
// Default behavior is to copy it from original file | ||
fs.stat(truename, function (err, stats) { | ||
if (err || !stats) resolve() | ||
else { | ||
options = Object.assign({}, options) | ||
}) | ||
} | ||
if (options.mode == null) { | ||
options.mode = stats.mode | ||
} | ||
if (options.chown == null && process.getuid) { | ||
options.chown = { uid: stats.uid, gid: stats.gid } | ||
} | ||
resolve() | ||
} | ||
}) | ||
async function writeFileAsync (filename, data, options = {}) { | ||
if (typeof options === 'string') { | ||
options = { encoding: options } | ||
} | ||
let fd | ||
let tmpfile | ||
/* istanbul ignore next -- The closure only gets called when onExit triggers */ | ||
const removeOnExitHandler = onExit(cleanupOnExit(() => tmpfile)) | ||
const absoluteName = path.resolve(filename) | ||
try { | ||
await serializeActiveFile(absoluteName) | ||
const truename = await promisify(fs.realpath)(filename).catch(() => filename) | ||
tmpfile = getTmpname(truename) | ||
if (!options.mode || !options.chown) { | ||
// Either mode or chown is not explicitly set | ||
// Default behavior is to copy it from original file | ||
const stats = await promisify(fs.stat)(truename).catch(() => {}) | ||
if (stats) { | ||
if (options.mode == null) { | ||
options.mode = stats.mode | ||
} | ||
if (options.chown == null && process.getuid) { | ||
options.chown = { uid: stats.uid, gid: stats.gid } | ||
} | ||
} | ||
}) | ||
}).then(function thenWriteFile () { | ||
return new Promise(function (resolve, reject) { | ||
fs.open(tmpfile, 'w', options.mode, function (err, _fd) { | ||
fd = _fd | ||
if (err) reject(err) | ||
else resolve() | ||
}) | ||
}) | ||
}).then(function write () { | ||
return new Promise(function (resolve, reject) { | ||
if (Buffer.isBuffer(data)) { | ||
fs.write(fd, data, 0, data.length, 0, function (err) { | ||
if (err) reject(err) | ||
else resolve() | ||
}) | ||
} else if (data != null) { | ||
fs.write(fd, String(data), 0, String(options.encoding || 'utf8'), function (err) { | ||
if (err) reject(err) | ||
else resolve() | ||
}) | ||
} else resolve() | ||
}) | ||
}).then(function syncAndClose () { | ||
return new Promise(function (resolve, reject) { | ||
if (options.fsync !== false) { | ||
fs.fsync(fd, function (err) { | ||
if (err) fs.close(fd, () => reject(err)) | ||
else fs.close(fd, resolve) | ||
}) | ||
} else { | ||
fs.close(fd, resolve) | ||
} | ||
}) | ||
}).then(function chown () { | ||
} | ||
fd = await promisify(fs.open)(tmpfile, 'w', options.mode) | ||
if (options.tmpfileCreated) { | ||
await options.tmpfileCreated(tmpfile) | ||
} | ||
if (isTypedArray(data)) { | ||
data = typedArrayToBuffer(data) | ||
} | ||
if (Buffer.isBuffer(data)) { | ||
await promisify(fs.write)(fd, data, 0, data.length, 0) | ||
} else if (data != null) { | ||
await promisify(fs.write)(fd, String(data), 0, String(options.encoding || 'utf8')) | ||
} | ||
if (options.fsync !== false) { | ||
await promisify(fs.fsync)(fd) | ||
} | ||
fd = null | ||
if (options.chown) { | ||
return new Promise(function (resolve, reject) { | ||
fs.chown(tmpfile, options.chown.uid, options.chown.gid, function (err) { | ||
if (err) reject(err) | ||
else resolve() | ||
}) | ||
}) | ||
await promisify(fs.chown)(tmpfile, options.chown.uid, options.chown.gid) | ||
} | ||
}).then(function chmod () { | ||
if (options.mode) { | ||
return new Promise(function (resolve, reject) { | ||
fs.chmod(tmpfile, options.mode, function (err) { | ||
if (err) reject(err) | ||
else resolve() | ||
}) | ||
}) | ||
await promisify(fs.chmod)(tmpfile, options.mode) | ||
} | ||
}).then(function rename () { | ||
return new Promise(function (resolve, reject) { | ||
fs.rename(tmpfile, truename, function (err) { | ||
if (err) reject(err) | ||
else resolve() | ||
}) | ||
}) | ||
}).then(function success () { | ||
await promisify(fs.rename)(tmpfile, truename) | ||
removeOnExitHandler() | ||
callback() | ||
}, function fail (err) { | ||
return new Promise(resolve => { | ||
return fd ? fs.close(fd, resolve) : resolve() | ||
}).then(() => { | ||
removeOnExitHandler() | ||
fs.unlink(tmpfile, function () { | ||
callback(err) | ||
}) | ||
}) | ||
}).then(function checkQueue () { | ||
} finally { | ||
if (fd) { | ||
await promisify(fs.close)(fd).catch( | ||
/* istanbul ignore next */ | ||
() => {} | ||
) | ||
} | ||
removeOnExitHandler() | ||
await promisify(fs.unlink)(tmpfile).catch(() => {}) | ||
activeFiles[absoluteName].shift() // remove the element added by serializeSameFile | ||
@@ -182,5 +136,19 @@ if (activeFiles[absoluteName].length > 0) { | ||
} else delete activeFiles[absoluteName] | ||
}) | ||
} | ||
} | ||
function writeFile (filename, data, options, callback) { | ||
if (options instanceof Function) { | ||
callback = options | ||
options = {} | ||
} | ||
const promise = writeFileAsync(filename, data, options) | ||
if (callback) { | ||
promise.then(callback, callback) | ||
} | ||
return promise | ||
} | ||
function writeFileSync (filename, data, options) { | ||
@@ -194,3 +162,3 @@ if (typeof options === 'string') options = { encoding: options } | ||
} | ||
var tmpfile = getTmpname(filename) | ||
const tmpfile = getTmpname(filename) | ||
@@ -201,3 +169,3 @@ if (!options.mode || !options.chown) { | ||
try { | ||
var stats = fs.statSync(filename) | ||
const stats = fs.statSync(filename) | ||
options = Object.assign({}, options) | ||
@@ -215,8 +183,14 @@ if (!options.mode) { | ||
var fd | ||
var cleanup = cleanupOnExit(tmpfile) | ||
var removeOnExitHandler = onExit(cleanup) | ||
let fd | ||
const cleanup = cleanupOnExit(tmpfile) | ||
const removeOnExitHandler = onExit(cleanup) | ||
try { | ||
fd = fs.openSync(tmpfile, 'w', options.mode) | ||
if (options.tmpfileCreated) { | ||
options.tmpfileCreated(tmpfile) | ||
} | ||
if (isTypedArray(data)) { | ||
data = typedArrayToBuffer(data) | ||
} | ||
if (Buffer.isBuffer(data)) { | ||
@@ -223,0 +197,0 @@ fs.writeSync(fd, data, 0, data.length, 0) |
{ | ||
"name": "write-file-atomic", | ||
"version": "2.4.3", | ||
"version": "3.0.0", | ||
"description": "Write files in an atomic fashion w/configurable ownership", | ||
"main": "index.js", | ||
"scripts": { | ||
"test": "standard && tap --100 test/*.js", | ||
"pretest": "standard", | ||
"test": "tap", | ||
"posttest": "rimraf chowncopy good nochmod nochown nofsync nofsyncopt noopen norename \"norename nounlink\" nowrite", | ||
"preversion": "npm test", | ||
@@ -14,3 +16,3 @@ "postversion": "npm publish", | ||
"type": "git", | ||
"url": "git@github.com:iarna/write-file-atomic.git" | ||
"url": "git://github.com/npm/write-file-atomic.git" | ||
}, | ||
@@ -24,20 +26,24 @@ "keywords": [ | ||
"bugs": { | ||
"url": "https://github.com/iarna/write-file-atomic/issues" | ||
"url": "https://github.com/npm/write-file-atomic/issues" | ||
}, | ||
"homepage": "https://github.com/iarna/write-file-atomic", | ||
"homepage": "https://github.com/npm/write-file-atomic", | ||
"dependencies": { | ||
"graceful-fs": "^4.1.11", | ||
"imurmurhash": "^0.1.4", | ||
"signal-exit": "^3.0.2" | ||
"is-typedarray": "^1.0.0", | ||
"signal-exit": "^3.0.2", | ||
"typedarray-to-buffer": "^3.1.5" | ||
}, | ||
"devDependencies": { | ||
"mkdirp": "^0.5.1", | ||
"require-inject": "^1.4.0", | ||
"rimraf": "^2.5.4", | ||
"require-inject": "^1.4.4", | ||
"rimraf": "^2.6.3", | ||
"standard": "^12.0.1", | ||
"tap": "^12.1.3" | ||
"tap": "^14.1.1" | ||
}, | ||
"files": [ | ||
"index.js" | ||
] | ||
], | ||
"tap": { | ||
"100": true | ||
} | ||
} |
@@ -7,3 +7,3 @@ write-file-atomic | ||
### var writeFileAtomic = require('write-file-atomic')<br>writeFileAtomic(filename, data, [options], callback) | ||
### var writeFileAtomic = require('write-file-atomic')<br>writeFileAtomic(filename, data, [options], [callback]) | ||
@@ -19,3 +19,3 @@ * filename **String** | ||
* mode **Number** default, from existing file, if any | ||
* Promise **Object** default = native Promise object | ||
* tmpfileCreated **Function** called when the tmpfile is created | ||
* callback **Function** | ||
@@ -32,3 +32,3 @@ | ||
pass the error back to the caller. | ||
If multiple writes are concurrently issued to the same file, the write operations are put into a queue and serialized in the order they were called, using Promises. Native promises are used by default, but you can inject your own promise-like object with the **Promise** option. Writes to different files are still executed in parallel. | ||
If multiple writes are concurrently issued to the same file, the write operations are put into a queue and serialized in the order they were called, using Promises. Writes to different files are still executed in parallel. | ||
@@ -48,2 +48,4 @@ If provided, the **chown** option requires both **uid** and **gid** properties or else | ||
If the **tmpfileCreated** option is specified it will be called with the name of the tmpfile when created. | ||
Example: | ||
@@ -58,4 +60,18 @@ | ||
This function also supports async/await: | ||
```javascript | ||
(async () => { | ||
try { | ||
await writeFileAtomic('message.txt', 'Hello Node', {chown:{uid:100,gid:50}}); | ||
console.log('It\'s saved!'); | ||
} catch (err) { | ||
console.error(err); | ||
process.exit(1); | ||
} | ||
})(); | ||
``` | ||
### var writeFileAtomicSync = require('write-file-atomic').sync<br>writeFileAtomicSync(filename, data, [options]) | ||
The synchronous version of **writeFileAtomic**. |
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
Filesystem access
Supply chain riskAccesses the file system, and could potentially read sensitive data.
Found 1 instance in 1 package
New author
Supply chain riskA new npm collaborator published a version of the package for the first time. New collaborators are usually benign additions to a project, but do indicate a change to the security surface area of a package.
Found 1 instance in 1 package
73
11723
4
187
1
+ Addedis-typedarray@^1.0.0
+ Addedtypedarray-to-buffer@^3.1.5
+ Addedis-typedarray@1.0.0(transitive)
+ Addedtypedarray-to-buffer@3.1.5(transitive)
- Removedgraceful-fs@^4.1.11
- Removedgraceful-fs@4.2.11(transitive)