-
-
Notifications
You must be signed in to change notification settings - Fork 938
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Use write-file-atomic for writing fixed styles to filesystem (#2992)
* Use write-file-atomic for writing fixed styles to filesystem * Replace write-file-atomic with graceful-fs-less fork * Move writeFileAtomic to a vendor folder so it is ignored by coverage
- Loading branch information
1 parent
5d79418
commit 07b4480
Showing
4 changed files
with
234 additions
and
2 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,2 +1,3 @@ | ||
.coverage | ||
decls | ||
lib/vendor |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,228 @@ | ||
// This is a fork of https://github.com/npm/write-file-atomic v2.3.0 | ||
// with graceful-fs replaced with fs to avoid memory leak during testing | ||
// See: https://github.com/stylelint/stylelint/pull/2992 | ||
|
||
"use strict"; | ||
module.exports = writeFile; | ||
module.exports.sync = writeFileSync; | ||
module.exports._getTmpname = getTmpname; // for testing | ||
module.exports._cleanupOnExit = cleanupOnExit; | ||
|
||
var fs = require("fs"); | ||
var MurmurHash3 = require("imurmurhash"); | ||
var onExit = require("signal-exit"); | ||
var path = require("path"); | ||
var activeFiles = {}; | ||
|
||
var invocations = 0; | ||
function getTmpname(filename) { | ||
return ( | ||
filename + | ||
"." + | ||
MurmurHash3(__filename) | ||
.hash(String(process.pid)) | ||
.hash(String(++invocations)) | ||
.result() | ||
); | ||
} | ||
|
||
function cleanupOnExit(tmpfile) { | ||
return function() { | ||
try { | ||
fs.unlinkSync(typeof tmpfile === "function" ? tmpfile() : tmpfile); | ||
} catch (_) {} | ||
}; | ||
} | ||
|
||
function writeFile(filename, data, options, callback) { | ||
if (options instanceof Function) { | ||
callback = options; | ||
options = null; | ||
} | ||
if (!options) options = {}; | ||
|
||
var Promise = options.Promise || global.Promise; | ||
var truename; | ||
var fd; | ||
var tmpfile; | ||
var removeOnExit = cleanupOnExit(() => tmpfile); | ||
var absoluteName = path.resolve(filename); | ||
|
||
new Promise(function serializeSameFile(resolve) { | ||
// make a queue if it doesn't already exist | ||
if (!activeFiles[absoluteName]) activeFiles[absoluteName] = []; | ||
|
||
activeFiles[absoluteName].push(resolve); // add this job to the queue | ||
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) { | ||
options.mode = stats.mode; | ||
} | ||
if (!options.chown && process.getuid) { | ||
options.chown = { uid: stats.uid, gid: stats.gid }; | ||
} | ||
resolve(); | ||
} | ||
}); | ||
} | ||
}); | ||
}) | ||
.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() { | ||
if (options.fsync !== false) { | ||
return new Promise(function(resolve, reject) { | ||
fs.fsync(fd, function(err) { | ||
if (err) reject(err); | ||
else fs.close(fd, resolve); | ||
}); | ||
}); | ||
} | ||
}) | ||
.then(function chown() { | ||
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(); | ||
}); | ||
}); | ||
} | ||
}) | ||
.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(); | ||
}); | ||
}); | ||
} | ||
}) | ||
.then(function rename() { | ||
return new Promise(function(resolve, reject) { | ||
fs.rename(tmpfile, truename, function(err) { | ||
if (err) reject(err); | ||
else resolve(); | ||
}); | ||
}); | ||
}) | ||
.then(function success() { | ||
removeOnExit(); | ||
callback(); | ||
}) | ||
.catch(function fail(err) { | ||
removeOnExit(); | ||
fs.unlink(tmpfile, function() { | ||
callback(err); | ||
}); | ||
}) | ||
.then(function checkQueue() { | ||
activeFiles[absoluteName].shift(); // remove the element added by serializeSameFile | ||
if (activeFiles[absoluteName].length > 0) { | ||
activeFiles[absoluteName][0](); // start next job if one is pending | ||
} else delete activeFiles[absoluteName]; | ||
}); | ||
} | ||
|
||
function writeFileSync(filename, data, options) { | ||
if (!options) options = {}; | ||
try { | ||
filename = fs.realpathSync(filename); | ||
} catch (ex) { | ||
// it's ok, it'll happen on a not yet existing file | ||
} | ||
var tmpfile = getTmpname(filename); | ||
|
||
try { | ||
if (!options.mode || !options.chown) { | ||
// Either mode or chown is not explicitly set | ||
// Default behavior is to copy it from original file | ||
try { | ||
var stats = fs.statSync(filename); | ||
options = Object.assign({}, options); | ||
if (!options.mode) { | ||
options.mode = stats.mode; | ||
} | ||
if (!options.chown && process.getuid) { | ||
options.chown = { uid: stats.uid, gid: stats.gid }; | ||
} | ||
} catch (ex) { | ||
// ignore stat errors | ||
} | ||
} | ||
|
||
var removeOnExit = onExit(cleanupOnExit(tmpfile)); | ||
var fd = fs.openSync(tmpfile, "w", options.mode); | ||
if (Buffer.isBuffer(data)) { | ||
fs.writeSync(fd, data, 0, data.length, 0); | ||
} else if (data != null) { | ||
fs.writeSync(fd, String(data), 0, String(options.encoding || "utf8")); | ||
} | ||
if (options.fsync !== false) { | ||
fs.fsyncSync(fd); | ||
} | ||
fs.closeSync(fd); | ||
if (options.chown) | ||
fs.chownSync(tmpfile, options.chown.uid, options.chown.gid); | ||
if (options.mode) fs.chmodSync(tmpfile, options.mode); | ||
fs.renameSync(tmpfile, filename); | ||
removeOnExit(); | ||
} catch (err) { | ||
removeOnExit(); | ||
try { | ||
fs.unlinkSync(tmpfile); | ||
} catch (e) {} | ||
throw err; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters