delete-empty
Advanced tools
Comparing version 2.0.0 to 3.0.0
#!/usr/bin/env node | ||
var ok = require('log-ok'); | ||
var deleteEmpty = require('..'); | ||
var argv = process.argv.slice(2); | ||
var args = argv.filter(arg => (arg !== '-d' && arg !== '--dry-run')); | ||
var cwd = args[0] || process.cwd(); | ||
var dryRun = argv.length !== args.length; | ||
const os = require('os'); | ||
const path = require('path'); | ||
const { cyan, bold, dim, green, symbols } = require('ansi-colors'); | ||
const deleteEmpty = require('..'); | ||
const argv = require('minimist')(process.argv.slice(2), { | ||
boolean: true, | ||
number: true, | ||
alias: { d: 'dryRun' }, | ||
rename: { _: 'files' } | ||
}); | ||
deleteEmpty(cwd, { dryRun: dryRun }, function(err, files) { | ||
if (err) { | ||
console.error(err); | ||
process.exit(1); | ||
} | ||
const moduleDir = dim(`<${path.dirname(__dirname)}>`); | ||
const name = pkg => bold(`delete-empty v${pkg.version}`); | ||
const help = pkg => ` | ||
Path: <${path.dirname(__dirname)}> | ||
if (dryRun) { | ||
console.log('Dry run. Empty directories (not deleted):'); | ||
for (var file in files) ok(files[file]); | ||
console.log('Total: ', files.length); | ||
Usage: ${cyan('$ delete-empty <directory> [options]')} | ||
Directory: (optional) Initial directory to begin the search for empty | ||
directories. Otherwise, cwd is used. | ||
[Options]: | ||
-c, --cwd Set the current working directory for folders to search. | ||
-d, --dryRun Do a dry run without deleting any files. | ||
-h, --help Display this help menu | ||
-V, --version Display the current version of rename | ||
-v, --verbose Display all verbose logging messages (currently not used) | ||
`; | ||
if (argv.help) { | ||
console.log(help(require('../package'))); | ||
process.exit(); | ||
} | ||
const ok = green(symbols.check); | ||
const cwd = path.resolve(argv._[0] || argv.cwd || process.cwd()); | ||
const relative = filepath => { | ||
if (filepath.startsWith(cwd)) { | ||
return path.relative(cwd, filepath); | ||
} | ||
if (filepath.startsWith(os.homedir())) { | ||
return path.join('~', filepath.slice(os.homedir().length)); | ||
} | ||
return cwd; | ||
}; | ||
console.log('Done'); | ||
process.exit(); | ||
}); | ||
if (argv.dryRun) { | ||
argv.verbose = true; | ||
} | ||
let count = 0; | ||
argv.onDirectory = () => (count++); | ||
deleteEmpty(cwd, argv) | ||
.then(files => { | ||
console.log(ok, 'Deleted', files.length, 'of', count, 'directories'); | ||
process.exit(); | ||
}) | ||
.catch(err => { | ||
console.error(err); | ||
process.exit(1); | ||
}); |
205
index.js
/*! | ||
* delete-empty <https://github.com/jonschlinkert/delete-empty> | ||
* | ||
* Copyright (c) 2015, 2017-2018, Jon Schlinkert. | ||
* Copyright (c) 2015-present, Jon Schlinkert | ||
* Released under the MIT License. | ||
@@ -13,27 +12,39 @@ */ | ||
const path = require('path'); | ||
const ok = require('log-ok'); | ||
const relative = require('relative'); | ||
const rimraf = require('rimraf'); | ||
const startsWith = require('path-starts-with'); | ||
const colors = require('ansi-colors'); | ||
const readdir = util.promisify(fs.readdir); | ||
const del = util.promisify(rimraf); | ||
function deleteEmpty(cwd, options, callback) { | ||
if (typeof options === 'function') { | ||
callback = options; | ||
options = {}; | ||
/** | ||
* Helpers | ||
*/ | ||
const GARBAGE_REGEX = /(?:Thumbs\.db|\.DS_Store)$/i; | ||
const isGarbageFile = (file, regex = GARBAGE_REGEX) => regex.test(file); | ||
const filterGarbage = (file, regex) => !isGarbageFile(file, regex); | ||
const isValidDir = (cwd, dir, empty) => { | ||
return !empty.includes(dir) && startsWith(dir, cwd) && isDirectory(dir); | ||
}; | ||
const deleteDir = async (dirname, options = {}) => { | ||
if (options.dryRun !== true) { | ||
return new Promise((resolve, reject) => { | ||
rimraf(dirname, { ...options, glob: false }, err => { | ||
if (err) { | ||
reject(err); | ||
} else { | ||
resolve(); | ||
} | ||
}); | ||
}) | ||
} | ||
}; | ||
const promise = deleteEmpty.promise(cwd, options); | ||
if (typeof callback === 'function') { | ||
promise.then(acc => callback(null, acc)).catch(callback); | ||
return; | ||
const deleteDirSync = (dirname, options = {}) => { | ||
if (options.dryRun !== true) { | ||
return rimraf.sync(dirname, { ...options, glob: false }); | ||
} | ||
return promise; | ||
} | ||
}; | ||
deleteEmpty.promise = function(cwd, options) { | ||
const opts = Object.assign({}, options); | ||
const dirname = path.resolve(cwd); | ||
const acc = []; | ||
const deleteEmpty = (cwd, options, cb) => { | ||
if (typeof cwd !== 'string') { | ||
@@ -43,43 +54,53 @@ return Promise.reject(new TypeError('expected the first argument to be a string')); | ||
async function remove(filepath) { | ||
const dir = path.resolve(filepath); | ||
if (typeof options === 'function') { | ||
cb = options; | ||
options = null; | ||
} | ||
if (dir.indexOf(dirname) !== 0 || acc.indexOf(dir) !== -1 || !isDirectory(dir)) { | ||
return Promise.resolve(acc); | ||
} | ||
if (typeof cb === 'function') { | ||
return deleteEmpty(cwd, options) | ||
.then(res => cb(null, res)) | ||
.catch(cb); | ||
} | ||
return await readdir(dir) | ||
.then(async files => { | ||
if (isEmpty(files, dir, acc, opts)) { | ||
acc.push(dir); | ||
const opts = options || {}; | ||
const dirname = path.resolve(cwd); | ||
const onDirectory = opts.onDirectory || (() => {}); | ||
const empty = []; | ||
if (opts.dryRun === true) { | ||
return remove(path.dirname(dir)); | ||
} | ||
const remove = async filepath => { | ||
let dir = path.resolve(filepath); | ||
return del(dir) | ||
.then(() => { | ||
if (opts.verbose === true) { | ||
ok('deleted:', relative(dir)); | ||
} | ||
return remove(path.dirname(dir)); | ||
}); | ||
if (!isValidDir(cwd, dir, empty)) return; | ||
onDirectory(dir); | ||
} else { | ||
for (const file of files) { | ||
await remove(path.join(dir, file)); | ||
} | ||
return Promise.resolve(acc); | ||
} | ||
}); | ||
} | ||
let files = await readdir(dir); | ||
return remove(dirname).then(acc); | ||
if (isEmpty(dir, files, empty, opts)) { | ||
empty.push(dir); | ||
await deleteDir(dir, opts); | ||
if (opts.verbose === true) { | ||
console.log(colors.red('Deleted:'), path.relative(cwd, dir)); | ||
} | ||
if (typeof opts.onDelete === 'function') { | ||
await opts.onDelete(dir); | ||
} | ||
return remove(path.dirname(dir)); | ||
} | ||
for (const file of files) { | ||
await remove(path.join(dir, file)); | ||
} | ||
return empty; | ||
}; | ||
return remove(dirname); | ||
}; | ||
deleteEmpty.sync = function(cwd, options) { | ||
const opts = Object.assign({}, options); | ||
const dirname = path.resolve(cwd); | ||
const acc = []; | ||
deleteEmpty.sync = (cwd, options) => { | ||
if (typeof cwd !== 'string') { | ||
@@ -89,34 +110,40 @@ throw new TypeError('expected the first argument to be a string'); | ||
function remove(filepath) { | ||
const dir = path.resolve(filepath); | ||
const opts = options || {}; | ||
const dirname = path.resolve(cwd); | ||
const deleted = []; | ||
const empty = []; | ||
if (dir.indexOf(dirname) !== 0 || acc.indexOf(dir) !== -1 || !isDirectory(dir)) { | ||
return acc; | ||
const remove = filepath => { | ||
let dir = path.resolve(filepath); | ||
if (!isValidDir(cwd, dir, empty)) { | ||
return empty; | ||
} | ||
const files = fs.readdirSync(dir); | ||
let files = fs.readdirSync(dir); | ||
if (isEmpty(files, dir, acc, opts)) { | ||
acc.push(dir); | ||
if (isEmpty(dir, files, empty, opts)) { | ||
empty.push(dir); | ||
if (opts.dryRun === true) { | ||
return remove(path.dirname(dir)); | ||
} | ||
deleteDirSync(dir, opts); | ||
rimraf.sync(dir); | ||
if (opts.verbose === true) { | ||
ok('deleted:', relative(dir)); | ||
console.log(colors.red('Deleted:'), path.relative(cwd, dir)); | ||
} | ||
return remove(path.dirname(dir)); | ||
} else { | ||
for (const file of files) { | ||
remove(path.join(dir, file)); | ||
if (typeof opts.onDelete === 'function') { | ||
opts.onDelete(dir); | ||
} | ||
return acc; | ||
return remove(path.dirname(dir)); | ||
} | ||
} | ||
for (let filepath of files) { | ||
remove(path.join(dir, filepath)); | ||
} | ||
return empty; | ||
}; | ||
remove(dirname); | ||
return acc; | ||
return empty; | ||
}; | ||
@@ -129,11 +156,10 @@ | ||
function isEmpty(files, dir, acc, opts) { | ||
var filter = opts.filter || isGarbageFile; | ||
for (const file of files) { | ||
const fp = path.join(dir, file); | ||
const isEmpty = (dir, files, empty, options = {}) => { | ||
let filter = options.filter || filterGarbage; | ||
let regex = options.junkRegex; | ||
if (opts.dryRun && acc.indexOf(fp) !== -1) { | ||
continue; | ||
} | ||
if (filter(fp) === false) { | ||
for (let basename of files) { | ||
let filepath = path.join(dir, basename); | ||
if (!(options.dryRun && empty.includes(filepath)) && filter(filepath, regex) === true) { | ||
return false; | ||
@@ -143,3 +169,3 @@ } | ||
return true; | ||
} | ||
}; | ||
@@ -150,19 +176,10 @@ /** | ||
function isDirectory(dir) { | ||
const isDirectory = dir => { | ||
try { | ||
return fs.statSync(dir).isDirectory(); | ||
} catch (err) { | ||
return false; | ||
} | ||
} | ||
} catch (err) { /* do nothing */ } | ||
return false; | ||
}; | ||
/** | ||
* Returns true if the file is a garbage file that can be deleted | ||
*/ | ||
function isGarbageFile(filename) { | ||
return /(?:Thumbs\.db|\.DS_Store)$/i.test(filename); | ||
} | ||
/** | ||
* Expose deleteEmpty | ||
@@ -169,0 +186,0 @@ */ |
{ | ||
"name": "delete-empty", | ||
"description": "Recursively delete all empty folders in a directory and child directories.", | ||
"version": "2.0.0", | ||
"version": "3.0.0", | ||
"homepage": "https://github.com/jonschlinkert/delete-empty", | ||
@@ -26,3 +26,3 @@ "author": "Jon Schlinkert (https://github.com/jonschlinkert)", | ||
"engines": { | ||
"node": ">=6" | ||
"node": ">=10" | ||
}, | ||
@@ -33,11 +33,12 @@ "scripts": { | ||
"dependencies": { | ||
"log-ok": "^0.1.1", | ||
"relative": "^3.0.2", | ||
"ansi-colors": "^4.1.0", | ||
"minimist": "^1.2.0", | ||
"path-starts-with": "^2.0.0", | ||
"rimraf": "^2.6.2" | ||
}, | ||
"devDependencies": { | ||
"async-each-series": "^1.1.0", | ||
"@folder/readdir": "^2.0.0", | ||
"gulp-format-md": "^1.0.0", | ||
"matched": "^1.0.2", | ||
"mocha": "^3.5.3" | ||
"mocha": "^3.5.3", | ||
"write": "^1.0.3" | ||
}, | ||
@@ -58,3 +59,3 @@ "keywords": [ | ||
"run": true, | ||
"toc": false, | ||
"toc": true, | ||
"layout": "default", | ||
@@ -61,0 +62,0 @@ "tasks": [ |
@@ -1,2 +0,2 @@ | ||
# delete-empty [![NPM version](https://img.shields.io/npm/v/delete-empty.svg?style=flat)](https://www.npmjs.com/package/delete-empty) [![NPM monthly downloads](https://img.shields.io/npm/dm/delete-empty.svg?style=flat)](https://npmjs.org/package/delete-empty) [![NPM total downloads](https://img.shields.io/npm/dt/delete-empty.svg?style=flat)](https://npmjs.org/package/delete-empty) [![Linux Build Status](https://img.shields.io/travis/jonschlinkert/delete-empty.svg?style=flat&label=Travis)](https://travis-ci.org/jonschlinkert/delete-empty) | ||
# delete-empty [![Donate](https://img.shields.io/badge/Donate-PayPal-green.svg)](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=W8YFZ425KND68) [![NPM version](https://img.shields.io/npm/v/delete-empty.svg?style=flat)](https://www.npmjs.com/package/delete-empty) [![NPM monthly downloads](https://img.shields.io/npm/dm/delete-empty.svg?style=flat)](https://npmjs.org/package/delete-empty) [![NPM total downloads](https://img.shields.io/npm/dt/delete-empty.svg?style=flat)](https://npmjs.org/package/delete-empty) [![Linux Build Status](https://img.shields.io/travis/jonschlinkert/delete-empty.svg?style=flat&label=Travis)](https://travis-ci.org/jonschlinkert/delete-empty) | ||
@@ -7,2 +7,13 @@ > Recursively delete all empty folders in a directory and child directories. | ||
- [Install](#install) | ||
- [Usage](#usage) | ||
- [API](#api) | ||
* [async-await (promise)](#async-await-promise) | ||
* [async callback](#async-callback) | ||
* [sync](#sync) | ||
- [CLI](#cli) | ||
- [About](#about) | ||
_(TOC generated by [verb](https://github.com/verbose/verb) using [markdown-toc](https://github.com/jonschlinkert/markdown-toc))_ | ||
## Install | ||
@@ -22,19 +33,2 @@ | ||
## CLI | ||
To use the `delete-empty` command from any directory you must first install the package globally with the following command: | ||
```sh | ||
$ npm install --global delete-empty | ||
``` | ||
**Usage** | ||
```sh | ||
$ delete-empty [<cwd>] [--dry-run|-d] | ||
``` | ||
* `<cwd>` (optional) - initial directory to begin the search for empty directories. By default, the current working directory (`process.cwd()`) is used (note that `process.cwd()` is only used as the default by the CLI). | ||
* `-d, --dry-run` (optional) - output empty directories to the terminal, does not delete anything | ||
## API | ||
@@ -57,3 +51,3 @@ | ||
### async promise | ||
### async-await (promise) | ||
@@ -63,2 +57,8 @@ If no callback is passed, a promise is returned. Returns the array of deleted directories. | ||
```js | ||
(async () => { | ||
let deleted = await deleteEmpty('foo'); | ||
console.log(deleted); //=> ['foo/aa/', 'foo/a/cc/', 'foo/b/', 'foo/c/'] | ||
})(); | ||
// or | ||
deleteEmpty('foo/') | ||
@@ -74,3 +74,3 @@ .then(deleted => console.log(deleted)) //=> ['foo/aa/', 'foo/a/cc/', 'foo/b/', 'foo/c/'] | ||
```js | ||
deleteEmpty('foo/', function(err, deleted) { | ||
deleteEmpty('foo/', (err, deleted) => { | ||
console.log(deleted); //=> ['foo/aa/', 'foo/a/cc/', 'foo/b/', 'foo/c/'] | ||
@@ -88,2 +88,27 @@ }); | ||
## CLI | ||
To use the `delete-empty` command from any directory you must first install the package globally with the following command: | ||
```sh | ||
$ npm install --global delete-empty | ||
``` | ||
**Usage** | ||
``` | ||
Usage: $ delete-empty <directory> [options] | ||
Directory: (optional) Initial directory to begin the search for empty | ||
directories. Otherwise, cwd is used. | ||
[Options]: | ||
-c, --cwd Set the current working directory for folders to search. | ||
-d, --dryRun Do a dry run without deleting any files. | ||
-h, --help Display this help menu | ||
-V, --version Display the current version of rename | ||
-v, --verbose Display all verbose logging messages (currently not used) | ||
``` | ||
## About | ||
@@ -133,9 +158,9 @@ | ||
| **Commits** | **Contributor** | | ||
| --- | --- | | ||
| 27 | [jonschlinkert](https://github.com/jonschlinkert) | | ||
| 2 | [treble-snake](https://github.com/treble-snake) | | ||
| 1 | [doowb](https://github.com/doowb) | | ||
| 1 | [svenschoenung](https://github.com/svenschoenung) | | ||
| 1 | [vpalmisano](https://github.com/vpalmisano) | | ||
| **Commits** | **Contributor** | | ||
| --- | --- | | ||
| 31 | [jonschlinkert](https://github.com/jonschlinkert) | | ||
| 2 | [treble-snake](https://github.com/treble-snake) | | ||
| 1 | [doowb](https://github.com/doowb) | | ||
| 1 | [svenschoenung](https://github.com/svenschoenung) | | ||
| 1 | [vpalmisano](https://github.com/vpalmisano) | | ||
@@ -146,9 +171,9 @@ ### Author | ||
* [linkedin/in/jonschlinkert](https://linkedin.com/in/jonschlinkert) | ||
* [github/jonschlinkert](https://github.com/jonschlinkert) | ||
* [twitter/jonschlinkert](https://twitter.com/jonschlinkert) | ||
* [GitHub Profile](https://github.com/jonschlinkert) | ||
* [Twitter Profile](https://twitter.com/jonschlinkert) | ||
* [LinkedIn Profile](https://linkedin.com/in/jonschlinkert) | ||
### License | ||
Copyright © 2018, [Jon Schlinkert](https://github.com/jonschlinkert). | ||
Copyright © 2019, [Jon Schlinkert](https://github.com/jonschlinkert). | ||
Released under the [MIT License](LICENSE). | ||
@@ -158,2 +183,2 @@ | ||
_This file was generated by [verb-generate-readme](https://github.com/verbose/verb-generate-readme), v0.6.0, on February 16, 2018._ | ||
_This file was generated by [verb-generate-readme](https://github.com/verbose/verb-generate-readme), v0.8.0, on July 02, 2019._ |
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
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
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
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
14826
195
175
4
+ Addedansi-colors@^4.1.0
+ Addedminimist@^1.2.0
+ Addedpath-starts-with@^2.0.0
+ Addedansi-colors@4.1.3(transitive)
+ Addedminimist@1.2.8(transitive)
+ Addedpath-starts-with@2.0.1(transitive)
- Removedlog-ok@^0.1.1
- Removedrelative@^3.0.2
- Removedansi-green@0.1.1(transitive)
- Removedansi-wrap@0.1.0(transitive)
- Removedisarray@1.0.0(transitive)
- Removedisobject@2.1.0(transitive)
- Removedlog-ok@0.1.1(transitive)
- Removedrelative@3.0.2(transitive)
- Removedsuccess-symbol@0.1.0(transitive)