push all website files

This commit is contained in:
Jacob Levine
2019-01-06 13:14:45 -06:00
parent d7301e26c3
commit d2d5d4c04e
15662 changed files with 2166516 additions and 0 deletions

View File

@@ -0,0 +1,18 @@
{
"env" : {
"node" : true
},
"rules" : {
"curly" : 0,
"no-lonely-if" : 1,
"no-mixed-requires" : 0,
"no-underscore-dangle" : 0,
"no-unused-vars" : [2, {"vars" : "all", "args" : "after-used"}],
"no-use-before-define" : [2, "nofunc"],
"quotes" : [1, "double", "avoid-escape"],
"semi" : [2, "never"],
"space-after-keywords" : 1,
"space-infix-ops" : 0,
"strict" : 0
}
}

View File

@@ -0,0 +1 @@
node_modules

View File

@@ -0,0 +1,14 @@
language: node_js
node_js:
- "5"
- "4"
- iojs
- "0.12"
- "0.10"
- "0.8"
sudo: false
script: "npm test"
before_install:
- "npm -g install npm"
notifications:
slack: npm-inc:kRqQjto7YbINqHPb1X6nS3g8

View File

@@ -0,0 +1,13 @@
Copyright (c) 2015, Forrest L Norvell
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
PERFORMANCE OF THIS SOFTWARE.

View File

@@ -0,0 +1,33 @@
# fs-vacuum
Remove the empty branches of a directory tree, optionally up to (but not
including) a specified base directory. Optionally nukes the leaf directory.
## Usage
```javascript
var logger = require("npmlog");
var vacuum = require("fs-vacuum");
var options = {
base : "/path/to/my/tree/root",
purge : true,
log : logger.silly.bind(logger, "myCleanup")
};
/* Assuming there are no other files or directories in "out", "to", or "my",
* the final path will just be "/path/to/my/tree/root".
*/
vacuum("/path/to/my/tree/root/out/to/my/files", options, function (error) {
if (error) console.error("Unable to cleanly vacuum:", error.message);
});
```
# vacuum(directory, options, callback)
* `directory` {String} Leaf node to remove. **Must be a directory, symlink, or file.**
* `options` {Object}
* `base` {String} No directories at or above this level of the filesystem will be removed.
* `purge` {Boolean} If set, nuke the whole leaf directory, including its contents.
* `log` {Function} A logging function that takes `npmlog`-compatible argument lists.
* `callback` {Function} Function to call once vacuuming is complete.
* `error` {Error} What went wrong along the way, if anything.

View File

@@ -0,0 +1,69 @@
{
"_args": [
[
"fs-vacuum@1.2.10",
"/Users/rebecca/code/npm"
]
],
"_from": "fs-vacuum@1.2.10",
"_id": "fs-vacuum@1.2.10",
"_inBundle": true,
"_integrity": "sha1-t2Kb7AekAxolSP35n17PHMizHjY=",
"_location": "/npm/fs-vacuum",
"_phantomChildren": {},
"_requested": {
"type": "version",
"registry": true,
"raw": "fs-vacuum@1.2.10",
"name": "fs-vacuum",
"escapedName": "fs-vacuum",
"rawSpec": "1.2.10",
"saveSpec": null,
"fetchSpec": "1.2.10"
},
"_requiredBy": [
"/npm",
"/npm/gentle-fs"
],
"_resolved": "https://registry.npmjs.org/fs-vacuum/-/fs-vacuum-1.2.10.tgz",
"_spec": "1.2.10",
"_where": "/Users/rebecca/code/npm",
"author": {
"name": "Forrest L Norvell",
"email": "ogd@aoaioxxysz.net"
},
"bugs": {
"url": "https://github.com/npm/fs-vacuum/issues"
},
"dependencies": {
"graceful-fs": "^4.1.2",
"path-is-inside": "^1.0.1",
"rimraf": "^2.5.2"
},
"description": "recursively remove empty directories -- to a point",
"devDependencies": {
"errno": "~0.1.0",
"mkdirp": "^0.5.1",
"require-inject": "~1.3.0",
"standard": "^6.0.8",
"tap": "^5.7.1",
"tmp": "0.0.28"
},
"homepage": "https://github.com/npm/fs-vacuum",
"keywords": [
"rm",
"rimraf",
"clean"
],
"license": "ISC",
"main": "vacuum.js",
"name": "fs-vacuum",
"repository": {
"type": "git",
"url": "git+https://github.com/npm/fs-vacuum.git"
},
"scripts": {
"test": "standard && tap test/*.js"
},
"version": "1.2.10"
}

View File

@@ -0,0 +1,24 @@
var test = require('tap').test
var vacuum = require('../vacuum.js')
test('vacuum throws on missing parameters', function (t) {
t.throws(vacuum, 'called with no parameters')
t.throws(function () { vacuum('directory', {}) }, 'called with no callback')
t.end()
})
test('vacuum throws on incorrect types ("Forrest is pedantic" section)', function (t) {
t.throws(function () {
vacuum({}, {}, function () {})
}, 'called with path parameter of incorrect type')
t.throws(function () {
vacuum('directory', 'directory', function () {})
}, 'called with options of wrong type')
t.throws(function () {
vacuum('directory', {}, 'whoops')
}, "called with callback that isn't callable")
t.end()
})

View File

@@ -0,0 +1,16 @@
var test = require('tap').test
var vacuum = require('../vacuum.js')
test('vacuum errors when base is set and path is not under it', function (t) {
vacuum('/a/made/up/path', {base: '/root/elsewhere'}, function (er) {
t.ok(er, 'got an error')
t.equal(
er.message,
'/a/made/up/path is not a child of /root/elsewhere',
'got the expected error message'
)
t.end()
})
})

View File

@@ -0,0 +1,78 @@
var path = require('path')
var test = require('tap').test
var statSync = require('graceful-fs').statSync
var writeFile = require('graceful-fs').writeFile
var readdirSync = require('graceful-fs').readdirSync
var mkdtemp = require('tmp').dir
var mkdirp = require('mkdirp')
var vacuum = require('../vacuum.js')
// CONSTANTS
var TEMP_OPTIONS = {
unsafeCleanup: true,
mode: '0700'
}
var SHORT_PATH = path.join('i', 'am', 'a', 'path')
var PARTIAL_PATH = path.join(SHORT_PATH, 'that', 'ends', 'at', 'a')
var FULL_PATH = path.join(PARTIAL_PATH, 'file')
var messages = []
function log () { messages.push(Array.prototype.slice.call(arguments).join(' ')) }
var testBase, partialPath, fullPath
test('xXx setup xXx', function (t) {
mkdtemp(TEMP_OPTIONS, function (er, tmpdir) {
t.ifError(er, 'temp directory exists')
testBase = path.resolve(tmpdir, SHORT_PATH)
partialPath = path.resolve(tmpdir, PARTIAL_PATH)
fullPath = path.resolve(tmpdir, FULL_PATH)
mkdirp(partialPath, function (er) {
t.ifError(er, 'made test path')
writeFile(fullPath, new Buffer('hi'), function (er) {
t.ifError(er, 'made file')
t.end()
})
})
})
})
test('remove up to a point', function (t) {
vacuum(fullPath, {purge: false, base: testBase, log: log}, function (er) {
t.ifError(er, 'cleaned up to base')
t.equal(messages.length, 6, 'got 5 removal & 1 finish message')
t.equal(messages[5], 'finished vacuuming up to ' + testBase)
var stat
var verifyPath = fullPath
function verify () { stat = statSync(verifyPath) }
// handle the file separately
t.throws(verify, verifyPath + ' cannot be statted')
t.notOk(stat && stat.isFile(), verifyPath + ' is totally gone')
verifyPath = path.dirname(verifyPath)
for (var i = 0; i < 4; i++) {
t.throws(verify, verifyPath + ' cannot be statted')
t.notOk(stat && stat.isDirectory(), verifyPath + ' is totally gone')
verifyPath = path.dirname(verifyPath)
}
t.doesNotThrow(function () {
stat = statSync(testBase)
}, testBase + ' can be statted')
t.ok(stat && stat.isDirectory(), testBase + ' is still a directory')
var files = readdirSync(testBase)
t.equal(files.length, 0, 'nothing left in base directory')
t.end()
})
})

View File

@@ -0,0 +1,78 @@
var path = require('path')
var test = require('tap').test
var statSync = require('graceful-fs').statSync
var symlinkSync = require('graceful-fs').symlinkSync
var readdirSync = require('graceful-fs').readdirSync
var mkdtemp = require('tmp').dir
var mkdirp = require('mkdirp')
var vacuum = require('../vacuum.js')
// CONSTANTS
var TEMP_OPTIONS = {
unsafeCleanup: true,
mode: '0700'
}
var SHORT_PATH = path.join('i', 'am', 'a', 'path')
var TARGET_PATH = path.join('target-link', 'in', 'the', 'middle')
var PARTIAL_PATH = path.join(SHORT_PATH, 'with', 'a')
var FULL_PATH = path.join(PARTIAL_PATH, 'link')
var EXPANDO_PATH = path.join(SHORT_PATH, 'with', 'a', 'link', 'in', 'the', 'middle')
var messages = []
function log () { messages.push(Array.prototype.slice.call(arguments).join(' ')) }
var testBase, targetPath, partialPath, fullPath, expandoPath
test('xXx setup xXx', function (t) {
mkdtemp(TEMP_OPTIONS, function (er, tmpdir) {
t.ifError(er, 'temp directory exists')
testBase = path.resolve(tmpdir, SHORT_PATH)
targetPath = path.resolve(tmpdir, TARGET_PATH)
partialPath = path.resolve(tmpdir, PARTIAL_PATH)
fullPath = path.resolve(tmpdir, FULL_PATH)
expandoPath = path.resolve(tmpdir, EXPANDO_PATH)
mkdirp(partialPath, function (er) {
t.ifError(er, 'made test path')
mkdirp(targetPath, function (er) {
t.ifError(er, 'made target path')
symlinkSync(path.join(tmpdir, 'target-link'), fullPath)
t.end()
})
})
})
})
test('remove up to a point', function (t) {
vacuum(expandoPath, {purge: false, base: testBase, log: log}, function (er) {
t.ifError(er, 'cleaned up to base')
t.equal(messages.length, 7, 'got 6 removal & 1 finish message')
t.equal(messages[6], 'finished vacuuming up to ' + testBase)
var stat
var verifyPath = expandoPath
function verify () { stat = statSync(verifyPath) }
for (var i = 0; i < 6; i++) {
t.throws(verify, verifyPath + ' cannot be statted')
t.notOk(stat && stat.isDirectory(), verifyPath + ' is totally gone')
verifyPath = path.dirname(verifyPath)
}
t.doesNotThrow(function () {
stat = statSync(testBase)
}, testBase + ' can be statted')
t.ok(stat && stat.isDirectory(), testBase + ' is still a directory')
var files = readdirSync(testBase)
t.equal(files.length, 0, 'nothing left in base directory')
t.end()
})
})

View File

@@ -0,0 +1,61 @@
var path = require('path')
var test = require('tap').test
var statSync = require('graceful-fs').statSync
var mkdtemp = require('tmp').dir
var mkdirp = require('mkdirp')
var vacuum = require('../vacuum.js')
// CONSTANTS
var TEMP_OPTIONS = {
unsafeCleanup: true,
mode: '0700'
}
var SHORT_PATH = path.join('i', 'am', 'a', 'path')
var LONG_PATH = path.join(SHORT_PATH, 'of', 'a', 'certain', 'length')
var messages = []
function log () { messages.push(Array.prototype.slice.call(arguments).join(' ')) }
var testPath, testBase
test('xXx setup xXx', function (t) {
mkdtemp(TEMP_OPTIONS, function (er, tmpdir) {
t.ifError(er, 'temp directory exists')
testBase = path.resolve(tmpdir, SHORT_PATH)
testPath = path.resolve(tmpdir, LONG_PATH)
mkdirp(testPath, function (er) {
t.ifError(er, 'made test path')
t.end()
})
})
})
test('remove up to a point', function (t) {
vacuum(testPath, {purge: false, base: testBase, log: log}, function (er) {
t.ifError(er, 'cleaned up to base')
t.equal(messages.length, 5, 'got 4 removal & 1 finish message')
t.equal(messages[4], 'finished vacuuming up to ' + testBase)
var stat
var verifyPath = testPath
function verify () { stat = statSync(verifyPath) }
for (var i = 0; i < 4; i++) {
t.throws(verify, verifyPath + ' cannot be statted')
t.notOk(stat && stat.isDirectory(), verifyPath + ' is totally gone')
verifyPath = path.dirname(verifyPath)
}
t.doesNotThrow(function () {
stat = statSync(testBase)
}, testBase + ' can be statted')
t.ok(stat && stat.isDirectory(), testBase + ' is still a directory')
t.end()
})
})

View File

@@ -0,0 +1,78 @@
var path = require('path')
var test = require('tap').test
var statSync = require('graceful-fs').statSync
var writeFileSync = require('graceful-fs').writeFileSync
var symlinkSync = require('graceful-fs').symlinkSync
var mkdtemp = require('tmp').dir
var mkdirp = require('mkdirp')
var vacuum = require('../vacuum.js')
// CONSTANTS
var TEMP_OPTIONS = {
unsafeCleanup: true,
mode: '0700'
}
var SHORT_PATH = path.join('i', 'am', 'a', 'path')
var TARGET_PATH = 'link-target'
var FIRST_FILE = path.join(TARGET_PATH, 'monsieurs')
var SECOND_FILE = path.join(TARGET_PATH, 'mesdames')
var PARTIAL_PATH = path.join(SHORT_PATH, 'with', 'a', 'definite')
var FULL_PATH = path.join(PARTIAL_PATH, 'target')
var messages = []
function log () { messages.push(Array.prototype.slice.call(arguments).join(' ')) }
var testBase, partialPath, fullPath, targetPath
test('xXx setup xXx', function (t) {
mkdtemp(TEMP_OPTIONS, function (er, tmpdir) {
t.ifError(er, 'temp directory exists')
testBase = path.resolve(tmpdir, SHORT_PATH)
targetPath = path.resolve(tmpdir, TARGET_PATH)
partialPath = path.resolve(tmpdir, PARTIAL_PATH)
fullPath = path.resolve(tmpdir, FULL_PATH)
mkdirp(partialPath, function (er) {
t.ifError(er, 'made test path')
mkdirp(targetPath, function (er) {
t.ifError(er, 'made target path')
writeFileSync(path.resolve(tmpdir, FIRST_FILE), new Buffer("c'est vraiment joli"))
writeFileSync(path.resolve(tmpdir, SECOND_FILE), new Buffer('oui oui'))
symlinkSync(targetPath, fullPath)
t.end()
})
})
})
})
test('remove up to a point', function (t) {
vacuum(fullPath, {purge: true, base: testBase, log: log}, function (er) {
t.ifError(er, 'cleaned up to base')
t.equal(messages.length, 5, 'got 4 removal & 1 finish message')
t.equal(messages[0], 'purging ' + fullPath)
t.equal(messages[4], 'finished vacuuming up to ' + testBase)
var stat
var verifyPath = fullPath
function verify () { stat = statSync(verifyPath) }
for (var i = 0; i < 4; i++) {
t.throws(verify, verifyPath + ' cannot be statted')
t.notOk(stat && stat.isDirectory(), verifyPath + ' is totally gone')
verifyPath = path.dirname(verifyPath)
}
t.doesNotThrow(function () {
stat = statSync(testBase)
}, testBase + ' can be statted')
t.ok(stat && stat.isDirectory(), testBase + ' is still a directory')
t.end()
})
})

View File

@@ -0,0 +1,67 @@
var path = require('path')
var test = require('tap').test
var statSync = require('graceful-fs').statSync
var writeFileSync = require('graceful-fs').writeFileSync
var mkdtemp = require('tmp').dir
var mkdirp = require('mkdirp')
var vacuum = require('../vacuum.js')
// CONSTANTS
var TEMP_OPTIONS = {
unsafeCleanup: true,
mode: '0700'
}
var SHORT_PATH = path.join('i', 'am', 'a', 'path')
var LONG_PATH = path.join(SHORT_PATH, 'of', 'a', 'certain', 'kind')
var FIRST_FILE = path.join(LONG_PATH, 'monsieurs')
var SECOND_FILE = path.join(LONG_PATH, 'mesdames')
var messages = []
function log () { messages.push(Array.prototype.slice.call(arguments).join(' ')) }
var testPath, testBase
test('xXx setup xXx', function (t) {
mkdtemp(TEMP_OPTIONS, function (er, tmpdir) {
t.ifError(er, 'temp directory exists')
testBase = path.resolve(tmpdir, SHORT_PATH)
testPath = path.resolve(tmpdir, LONG_PATH)
mkdirp(testPath, function (er) {
t.ifError(er, 'made test path')
writeFileSync(path.resolve(tmpdir, FIRST_FILE), new Buffer("c'est vraiment joli"))
writeFileSync(path.resolve(tmpdir, SECOND_FILE), new Buffer('oui oui'))
t.end()
})
})
})
test('remove up to a point', function (t) {
vacuum(testPath, {purge: true, base: testBase, log: log}, function (er) {
t.ifError(er, 'cleaned up to base')
t.equal(messages.length, 5, 'got 4 removal & 1 finish message')
t.equal(messages[0], 'purging ' + testPath)
t.equal(messages[4], 'finished vacuuming up to ' + testBase)
var stat
var verifyPath = testPath
function verify () { stat = statSync(verifyPath) }
for (var i = 0; i < 4; i++) {
t.throws(verify, verifyPath + ' cannot be statted')
t.notOk(stat && stat.isDirectory(), verifyPath + ' is totally gone')
verifyPath = path.dirname(verifyPath)
}
t.doesNotThrow(function () {
stat = statSync(testBase)
}, testBase + ' can be statted')
t.ok(stat && stat.isDirectory(), testBase + ' is still a directory')
t.end()
})
})

View File

@@ -0,0 +1,46 @@
var path = require('path')
var test = require('tap').test
var mkdtemp = require('tmp').dir
var mkdirp = require('mkdirp')
var vacuum = require('../vacuum.js')
// CONSTANTS
var TEMP_OPTIONS = {
unsafeCleanup: true,
mode: '0700'
}
var BASE_PATH = path.join('foo')
var HOME_PATH = path.join(BASE_PATH, 'foo', 'bar')
var messages = []
function log () { messages.push(Array.prototype.slice.call(arguments).join(' ')) }
var homePath, basePath, realHome
test('xXx setup xXx', function (t) {
mkdtemp(TEMP_OPTIONS, function (er, tmpdir) {
t.ifError(er, 'temp directory exists')
homePath = path.resolve(tmpdir, HOME_PATH)
basePath = path.resolve(tmpdir, BASE_PATH)
realHome = process.env.HOME
process.env.HOME = homePath
mkdirp(homePath, function (er) {
t.ifError(er, 'made test path')
t.end()
})
})
})
test('do not remove home directory', function (t) {
vacuum(homePath, {purge: false, base: basePath, log: log}, function (er) {
t.ifError(er, 'cleaned up to base')
t.equal(messages[0], 'quitting because cannot remove home directory ' + homePath)
process.env.HOME = realHome
t.end()
})
})

View File

@@ -0,0 +1,76 @@
var path = require('path')
var test = require('tap').test
var statSync = require('graceful-fs').statSync
var mkdtemp = require('tmp').dir
var mkdirp = require('mkdirp')
var vacuum = require('../vacuum.js')
// CONSTANTS
var TEMP_OPTIONS = {
unsafeCleanup: true,
mode: '0700'
}
var SHORT_PATH = path.join('i', 'am', 'a', 'path')
var REMOVE_PATH = path.join(SHORT_PATH, 'of', 'a', 'certain', 'length')
var OTHER_PATH = path.join(SHORT_PATH, 'of', 'no', 'qualities')
var messages = []
function log () { messages.push(Array.prototype.slice.call(arguments).join(' ')) }
var testBase, testPath, otherPath
test('xXx setup xXx', function (t) {
mkdtemp(TEMP_OPTIONS, function (er, tmpdir) {
t.ifError(er, 'temp directory exists')
testBase = path.resolve(tmpdir, SHORT_PATH)
testPath = path.resolve(tmpdir, REMOVE_PATH)
otherPath = path.resolve(tmpdir, OTHER_PATH)
mkdirp(testPath, function (er) {
t.ifError(er, 'made test path')
mkdirp(otherPath, function (er) {
t.ifError(er, 'made other path')
t.end()
})
})
})
})
test('remove up to a point', function (t) {
vacuum(testPath, {purge: false, base: testBase, log: log}, function (er) {
t.ifError(er, 'cleaned up to base')
t.equal(messages.length, 4, 'got 3 removal & 1 finish message')
t.equal(
messages[3], 'quitting because other entries in ' + testBase + '/of',
'got expected final message'
)
var stat
var verifyPath = testPath
function verify () { stat = statSync(verifyPath) }
for (var i = 0; i < 3; i++) {
t.throws(verify, verifyPath + ' cannot be statted')
t.notOk(stat && stat.isDirectory(), verifyPath + ' is totally gone')
verifyPath = path.dirname(verifyPath)
}
t.doesNotThrow(function () {
stat = statSync(otherPath)
}, otherPath + ' can be statted')
t.ok(stat && stat.isDirectory(), otherPath + ' is still a directory')
var intersection = path.join(testBase, 'of')
t.doesNotThrow(function () {
stat = statSync(intersection)
}, intersection + ' can be statted')
t.ok(stat && stat.isDirectory(), intersection + ' is still a directory')
t.end()
})
})

View File

@@ -0,0 +1,119 @@
var path = require('path')
var test = require('tap').test
var readdir = require('graceful-fs').readdir
var readdirSync = require('graceful-fs').readdirSync
var rmdir = require('graceful-fs').rmdir
var statSync = require('graceful-fs').statSync
var writeFile = require('graceful-fs').writeFile
var mkdirp = require('mkdirp')
var mkdtemp = require('tmp').dir
var tmpFile = require('tmp').file
var EEXIST = require('errno').code.EEXIST
var ENOTEMPTY = require('errno').code.ENOTEMPTY
var requireInject = require('require-inject')
var vacuum = requireInject('../vacuum.js', {
'graceful-fs': {
'lstat': require('graceful-fs').lstat,
'readdir': function (dir, cb) {
readdir(dir, function (err, files) {
// Simulate racy removal by creating a file after vacuum
// thinks the directory is empty
if (dir === partialPath && files.length === 0) {
tmpFile({dir: dir}, function (err, path, fd) {
if (err) throw err
cb(err, files)
})
} else {
cb(err, files)
}
})
},
'rmdir': function (dir, cb) {
rmdir(dir, function (err) {
// Force EEXIST error from rmdir if the directory is non-empty
var mockErr = EEXIST
if (err) {
if (err.code === ENOTEMPTY.code) {
err.code = err.errno = mockErr.code
err.message = mockErr.code + ': ' + mockErr.description + ', ' + err.syscall + ' \'' + dir + '\''
}
}
cb(err)
})
},
'unlink': require('graceful-fs').unlink
}
})
// CONSTANTS
var TEMP_OPTIONS = {
unsafeCleanup: true,
mode: '0700'
}
var SHORT_PATH = path.join('i', 'am', 'a', 'path')
var PARTIAL_PATH = path.join(SHORT_PATH, 'that', 'ends', 'at', 'a')
var FULL_PATH = path.join(PARTIAL_PATH, 'file')
var messages = []
function log () { messages.push(Array.prototype.slice.call(arguments).join(' ')) }
var testBase, partialPath, fullPath
test('xXx setup xXx', function (t) {
mkdtemp(TEMP_OPTIONS, function (er, tmpdir) {
t.ifError(er, 'temp directory exists')
testBase = path.resolve(tmpdir, SHORT_PATH)
partialPath = path.resolve(tmpdir, PARTIAL_PATH)
fullPath = path.resolve(tmpdir, FULL_PATH)
mkdirp(partialPath, function (er) {
t.ifError(er, 'made test path')
writeFile(fullPath, new Buffer('hi'), function (er) {
t.ifError(er, 'made file')
t.end()
})
})
})
})
test('racy removal should quit gracefully', function (t) {
vacuum(fullPath, {purge: false, base: testBase, log: log}, function (er) {
t.ifError(er, 'cleaned up to base')
t.equal(messages.length, 3, 'got 2 removal & 1 quit message')
t.equal(messages[2], 'quitting because new (racy) entries in ' + partialPath)
var stat
var verifyPath = fullPath
function verify () { stat = statSync(verifyPath) }
// handle the file separately
t.throws(verify, verifyPath + ' cannot be statted')
t.notOk(stat && stat.isFile(), verifyPath + ' is totally gone')
verifyPath = path.dirname(verifyPath)
for (var i = 0; i < 4; i++) {
t.doesNotThrow(function () {
stat = statSync(verifyPath)
}, verifyPath + ' can be statted')
t.ok(stat && stat.isDirectory(), verifyPath + ' is still a directory')
verifyPath = path.dirname(verifyPath)
}
t.doesNotThrow(function () {
stat = statSync(testBase)
}, testBase + ' can be statted')
t.ok(stat && stat.isDirectory(), testBase + ' is still a directory')
var files = readdirSync(testBase)
t.equal(files.length, 1, 'base directory untouched')
t.end()
})
})

View File

@@ -0,0 +1,119 @@
var path = require('path')
var test = require('tap').test
var readdir = require('graceful-fs').readdir
var readdirSync = require('graceful-fs').readdirSync
var rmdir = require('graceful-fs').rmdir
var statSync = require('graceful-fs').statSync
var writeFile = require('graceful-fs').writeFile
var mkdirp = require('mkdirp')
var mkdtemp = require('tmp').dir
var tmpFile = require('tmp').file
var EEXIST = require('errno').code.EEXIST
var ENOTEMPTY = require('errno').code.ENOTEMPTY
var requireInject = require('require-inject')
var vacuum = requireInject('../vacuum.js', {
'graceful-fs': {
'lstat': require('graceful-fs').lstat,
'readdir': function (dir, cb) {
readdir(dir, function (err, files) {
// Simulate racy removal by creating a file after vacuum
// thinks the directory is empty
if (dir === partialPath && files.length === 0) {
tmpFile({dir: dir}, function (err, path, fd) {
if (err) throw err
cb(err, files)
})
} else {
cb(err, files)
}
})
},
'rmdir': function (dir, cb) {
rmdir(dir, function (err) {
// Force ENOTEMPTY error from rmdir if the directory is non-empty
var mockErr = ENOTEMPTY
if (err) {
if (err.code === EEXIST.code) {
err.code = err.errno = mockErr.code
err.message = mockErr.code + ': ' + mockErr.description + ', ' + err.syscall + ' \'' + dir + '\''
}
}
cb(err)
})
},
'unlink': require('graceful-fs').unlink
}
})
// CONSTANTS
var TEMP_OPTIONS = {
unsafeCleanup: true,
mode: '0700'
}
var SHORT_PATH = path.join('i', 'am', 'a', 'path')
var PARTIAL_PATH = path.join(SHORT_PATH, 'that', 'ends', 'at', 'a')
var FULL_PATH = path.join(PARTIAL_PATH, 'file')
var messages = []
function log () { messages.push(Array.prototype.slice.call(arguments).join(' ')) }
var testBase, partialPath, fullPath
test('xXx setup xXx', function (t) {
mkdtemp(TEMP_OPTIONS, function (er, tmpdir) {
t.ifError(er, 'temp directory exists')
testBase = path.resolve(tmpdir, SHORT_PATH)
partialPath = path.resolve(tmpdir, PARTIAL_PATH)
fullPath = path.resolve(tmpdir, FULL_PATH)
mkdirp(partialPath, function (er) {
t.ifError(er, 'made test path')
writeFile(fullPath, new Buffer('hi'), function (er) {
t.ifError(er, 'made file')
t.end()
})
})
})
})
test('racy removal should quit gracefully', function (t) {
vacuum(fullPath, {purge: false, base: testBase, log: log}, function (er) {
t.ifError(er, 'cleaned up to base')
t.equal(messages.length, 3, 'got 2 removal & 1 quit message')
t.equal(messages[2], 'quitting because new (racy) entries in ' + partialPath)
var stat
var verifyPath = fullPath
function verify () { stat = statSync(verifyPath) }
// handle the file separately
t.throws(verify, verifyPath + ' cannot be statted')
t.notOk(stat && stat.isFile(), verifyPath + ' is totally gone')
verifyPath = path.dirname(verifyPath)
for (var i = 0; i < 4; i++) {
t.doesNotThrow(function () {
stat = statSync(verifyPath)
}, verifyPath + ' can be statted')
t.ok(stat && stat.isDirectory(), verifyPath + ' is still a directory')
verifyPath = path.dirname(verifyPath)
}
t.doesNotThrow(function () {
stat = statSync(testBase)
}, testBase + ' can be statted')
t.ok(stat && stat.isDirectory(), testBase + ' is still a directory')
var files = readdirSync(testBase)
t.equal(files.length, 1, 'base directory untouched')
t.end()
})
})

View File

@@ -0,0 +1,104 @@
var path = require('path')
var test = require('tap').test
var readdir = require('graceful-fs').readdir
var readdirSync = require('graceful-fs').readdirSync
var statSync = require('graceful-fs').statSync
var writeFile = require('graceful-fs').writeFile
var mkdirp = require('mkdirp')
var mkdtemp = require('tmp').dir
var tmpFile = require('tmp').file
var requireInject = require('require-inject')
var vacuum = requireInject('../vacuum.js', {
'graceful-fs': {
'lstat': require('graceful-fs').lstat,
'readdir': function (dir, cb) {
readdir(dir, function (err, files) {
// Simulate racy removal by creating a file after vacuum
// thinks the directory is empty
if (dir === partialPath && files.length === 0) {
tmpFile({dir: dir}, function (err, path, fd) {
if (err) throw err
cb(err, files)
})
} else {
cb(err, files)
}
})
},
'rmdir': require('graceful-fs').rmdir,
'unlink': require('graceful-fs').unlink
}
})
// CONSTANTS
var TEMP_OPTIONS = {
unsafeCleanup: true,
mode: '0700'
}
var SHORT_PATH = path.join('i', 'am', 'a', 'path')
var PARTIAL_PATH = path.join(SHORT_PATH, 'that', 'ends', 'at', 'a')
var FULL_PATH = path.join(PARTIAL_PATH, 'file')
var messages = []
function log () { messages.push(Array.prototype.slice.call(arguments).join(' ')) }
var testBase, partialPath, fullPath
test('xXx setup xXx', function (t) {
mkdtemp(TEMP_OPTIONS, function (er, tmpdir) {
t.ifError(er, 'temp directory exists')
testBase = path.resolve(tmpdir, SHORT_PATH)
partialPath = path.resolve(tmpdir, PARTIAL_PATH)
fullPath = path.resolve(tmpdir, FULL_PATH)
mkdirp(partialPath, function (er) {
t.ifError(er, 'made test path')
writeFile(fullPath, new Buffer('hi'), function (er) {
t.ifError(er, 'made file')
t.end()
})
})
})
})
test('racy removal should quit gracefully', function (t) {
vacuum(fullPath, {purge: false, base: testBase, log: log}, function (er) {
t.ifError(er, 'cleaned up to base')
t.equal(messages.length, 3, 'got 2 removal & 1 quit message')
t.equal(messages[2], 'quitting because new (racy) entries in ' + partialPath)
var stat
var verifyPath = fullPath
function verify () { stat = statSync(verifyPath) }
// handle the file separately
t.throws(verify, verifyPath + ' cannot be statted')
t.notOk(stat && stat.isFile(), verifyPath + ' is totally gone')
verifyPath = path.dirname(verifyPath)
for (var i = 0; i < 4; i++) {
t.doesNotThrow(function () {
stat = statSync(verifyPath)
}, verifyPath + ' can be statted')
t.ok(stat && stat.isDirectory(), verifyPath + ' is still a directory')
verifyPath = path.dirname(verifyPath)
}
t.doesNotThrow(function () {
stat = statSync(testBase)
}, testBase + ' can be statted')
t.ok(stat && stat.isDirectory(), testBase + ' is still a directory')
var files = readdirSync(testBase)
t.equal(files.length, 1, 'base directory untouched')
t.end()
})
})

View File

@@ -0,0 +1,117 @@
var assert = require('assert')
var dirname = require('path').dirname
var resolve = require('path').resolve
var isInside = require('path-is-inside')
var rimraf = require('rimraf')
var lstat = require('graceful-fs').lstat
var readdir = require('graceful-fs').readdir
var rmdir = require('graceful-fs').rmdir
var unlink = require('graceful-fs').unlink
module.exports = vacuum
function vacuum (leaf, options, cb) {
assert(typeof leaf === 'string', 'must pass in path to remove')
assert(typeof cb === 'function', 'must pass in callback')
if (!options) options = {}
assert(typeof options === 'object', 'options must be an object')
var log = options.log ? options.log : function () {}
leaf = leaf && resolve(leaf)
var base = options.base && resolve(options.base)
if (base && !isInside(leaf, base)) {
return cb(new Error(leaf + ' is not a child of ' + base))
}
lstat(leaf, function (error, stat) {
if (error) {
if (error.code === 'ENOENT') return cb(null)
log(error.stack)
return cb(error)
}
if (!(stat && (stat.isDirectory() || stat.isSymbolicLink() || stat.isFile()))) {
log(leaf, 'is not a directory, file, or link')
return cb(new Error(leaf + ' is not a directory, file, or link'))
}
if (options.purge) {
log('purging', leaf)
rimraf(leaf, function (error) {
if (error) return cb(error)
next(dirname(leaf))
})
} else if (!stat.isDirectory()) {
log('removing', leaf)
unlink(leaf, function (error) {
if (error) return cb(error)
next(dirname(leaf))
})
} else {
next(leaf)
}
})
function next (branch) {
branch = branch && resolve(branch)
// either we've reached the base or we've reached the root
if ((base && branch === base) || branch === dirname(branch)) {
log('finished vacuuming up to', branch)
return cb(null)
}
readdir(branch, function (error, files) {
if (error) {
if (error.code === 'ENOENT') return cb(null)
log('unable to check directory', branch, 'due to', error.message)
return cb(error)
}
if (files.length > 0) {
log('quitting because other entries in', branch)
return cb(null)
}
if (branch === process.env.HOME) {
log('quitting because cannot remove home directory', branch)
return cb(null)
}
log('removing', branch)
lstat(branch, function (error, stat) {
if (error) {
if (error.code === 'ENOENT') return cb(null)
log('unable to lstat', branch, 'due to', error.message)
return cb(error)
}
var remove = stat.isDirectory() ? rmdir : unlink
remove(branch, function (error) {
if (error) {
if (error.code === 'ENOENT') {
log('quitting because lost the race to remove', branch)
return cb(null)
}
if (error.code === 'ENOTEMPTY' || error.code === 'EEXIST') {
log('quitting because new (racy) entries in', branch)
return cb(null)
}
log('unable to remove', branch, 'due to', error.message)
return cb(error)
}
next(dirname(branch))
})
})
})
}
}